1use std::cmp::Ordering;
2
3use crate::{Error, Expr};
4use geo::*;
5use geo_types::Geometry as GGeom;
6use geozero::{wkt::Wkt, CoordDimensions, ToGeo, ToWkt};
7use serde::{Deserialize, Serialize, Serializer};
8
9const DEFAULT_NDIM: usize = 2;
10
11#[derive(Debug, Serialize, Deserialize, Clone)]
13#[serde(untagged)]
14pub enum Geometry {
15 GeoJSON(geojson::Geometry),
17
18 #[serde(skip_deserializing, serialize_with = "to_geojson")]
20 Wkt(String),
21}
22
23impl Geometry {
24 pub fn to_wkt(&self) -> Result<String, Error> {
37 match self {
38 Geometry::Wkt(wkt) => Ok(wkt.clone()),
39 Geometry::GeoJSON(geojson) => {
40 let dims = match geojson_ndims(geojson) {
41 3 => CoordDimensions::xyz(),
42 4 => CoordDimensions::xyzm(),
43 _ => CoordDimensions::xy(),
44 };
45 let geometry: geo_types::Geometry<f64> = geojson.clone().try_into()?;
46 geometry.to_wkt_ndim(dims).map_err(Error::from)
47 }
48 }
49 }
50}
51
52impl PartialEq for Geometry {
53 fn eq(&self, other: &Self) -> bool {
54 let left = Expr::Geometry(self.clone());
55 let right = Expr::Geometry(other.clone());
56 let v = spatial_op(left, right, "s_equals").unwrap_or(Expr::Bool(false));
57 match v {
58 Expr::Bool(v) => v,
59 _ => false,
60 }
61 }
62}
63
64impl PartialOrd for Geometry {
65 fn partial_cmp(&self, _other: &Self) -> Option<Ordering> {
66 None
67 }
68}
69
70fn to_geojson<S>(wkt: &String, serializer: S) -> Result<S::Ok, S::Error>
71where
72 S: Serializer,
73{
74 use serde::ser::Error;
75
76 let geometry = Wkt(wkt).to_geo().map_err(Error::custom)?;
77 geojson::ser::serialize_geometry(&geometry, serializer)
78}
79
80fn geojson_ndims(geojson: &geojson::Geometry) -> usize {
81 use geojson::Value::*;
82 match &geojson.value {
83 Point(coords) => coords.len(),
84 MultiPoint(v) => v.first().map(|v| v.len()).unwrap_or(DEFAULT_NDIM),
85 LineString(v) => v.first().map(|v| v.len()).unwrap_or(DEFAULT_NDIM),
86 MultiLineString(v) => v
87 .first()
88 .and_then(|v| v.first())
89 .map(|v| v.len())
90 .unwrap_or(DEFAULT_NDIM),
91 Polygon(v) => v
92 .first()
93 .and_then(|v| v.first())
94 .map(|v| v.len())
95 .unwrap_or(DEFAULT_NDIM),
96 MultiPolygon(v) => v
97 .first()
98 .and_then(|v| v.first())
99 .and_then(|v| v.first())
100 .map(|v| v.len())
101 .unwrap_or(DEFAULT_NDIM),
102 GeometryCollection(v) => v.first().map(geojson_ndims).unwrap_or(DEFAULT_NDIM),
103 }
104}
105
106pub fn spatial_op(left: Expr, right: Expr, op: &str) -> Result<Expr, Error> {
108 let left: GGeom = GGeom::try_from(left)?;
109 let right: GGeom = GGeom::try_from(right)?;
110 let rel = left.relate(&right);
111 let out = match op {
112 "s_equals" => rel.is_equal_topo(),
113 "s_intersects" | "intersects" => rel.is_intersects(),
114 "s_disjoint" => rel.is_disjoint(),
115 "s_touches" => rel.is_touches(),
116 "s_within" => rel.is_within(),
117 "s_overlaps" => rel.is_overlaps(),
118 "s_crosses" => rel.is_crosses(),
119 "s_contains" => rel.is_contains(),
120 &_ => todo!(),
121 };
122 Ok(Expr::Bool(out))
123}