Skip to main content

nativeclick/values/
geo.rs

1//! Geo types
2//! <https://clickhouse.com/docs/en/sql-reference/data-types/geo>
3use super::*;
4
5#[derive(Clone, Default, Debug, PartialEq)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7/// Geo point, represented by its x and y coordinates.
8///
9/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#point>
10pub struct Point(pub [f64; 2]);
11impl std::hash::Hash for Point {
12    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
13        for x in self.0 {
14            x.to_bits().hash(state);
15        }
16    }
17}
18impl std::ops::Index<u8> for Point {
19    type Output = f64;
20    fn index(&self, index: u8) -> &Self::Output {
21        &self.0[index as usize]
22    }
23}
24impl AsRef<[f64; 2]> for Point {
25    fn as_ref(&self) -> &[f64; 2] {
26        &self.0
27    }
28}
29#[derive(Clone, Hash, Default, Debug, PartialEq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31/// Polygon without holes.
32///
33/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#ring>
34pub struct Ring(pub Vec<Point>);
35#[derive(Clone, Hash, Default, Debug, PartialEq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37/// Polygon with holes. The first element is the outer polygon, and the following ones are the holes.
38///
39/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#polygon>
40pub struct Polygon(pub Vec<Ring>);
41#[derive(Clone, Hash, Default, Debug, PartialEq)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43/// Union of polygons.
44///
45/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#multipolygon>
46pub struct MultiPolygon(pub Vec<Polygon>);
47
48macro_rules! to_from_sql {
49    ($name:ident) => {
50        impl ToSql for $name {
51            fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
52                Ok(Value::$name(self))
53            }
54        }
55
56        impl FromSql for $name {
57            fn from_sql(type_: &Type, value: Value) -> Result<Self> {
58                if !matches!(type_, Type::$name) {
59                    return Err(unexpected_type(type_));
60                }
61                match value {
62                    Value::$name(x) => Ok(x),
63                    _ => unimplemented!(),
64                }
65            }
66        }
67    };
68}
69
70to_from_sql!(Point);
71to_from_sql!(Ring);
72to_from_sql!(Polygon);
73to_from_sql!(MultiPolygon);
74#[cfg(feature = "geo-types")]
75mod nav_types_conversions {
76    use super::*;
77
78    macro_rules! to_from_sql {
79        ($geo_t:path, $ch_t:ident) => {
80            impl ToSql for $geo_t {
81                fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
82                    Ok(Value::$ch_t(self.into()))
83                }
84            }
85            impl FromSql for $geo_t {
86                fn from_sql(type_: &Type, value: Value) -> Result<Self> {
87                    if !matches!(type_, Type::$ch_t) {
88                        return Err(unexpected_type(type_));
89                    }
90                    match value {
91                        Value::$ch_t(x) => Ok(x.into()),
92                        _ => unimplemented!(),
93                    }
94                }
95            }
96        };
97    }
98    // Points and coords
99    impl From<Point> for geo_types::Coord {
100        fn from(source: Point) -> Self {
101            Self {
102                x: source[0],
103                y: source[1],
104            }
105        }
106    }
107    impl From<geo_types::Coord> for Point {
108        fn from(source: geo_types::Coord) -> Self {
109            Self([source.x, source.y])
110        }
111    }
112    to_from_sql!(geo_types::Coord, Point);
113
114    // Points and points
115    impl From<Point> for geo_types::Point {
116        fn from(source: Point) -> Self {
117            geo_types::Point(source.into())
118        }
119    }
120    impl From<geo_types::Point> for Point {
121        fn from(source: geo_types::Point) -> Self {
122            source.0.into()
123        }
124    }
125    to_from_sql!(geo_types::Point, Point);
126    // Rings and Linestrings
127    impl From<Ring> for geo_types::LineString {
128        fn from(source: Ring) -> Self {
129            Self(source.0.into_iter().map(geo_types::Coord::from).collect())
130        }
131    }
132    impl From<geo_types::LineString> for Ring {
133        fn from(source: geo_types::LineString) -> Self {
134            Self(source.0.into_iter().map(Point::from).collect())
135        }
136    }
137    to_from_sql!(geo_types::LineString, Ring);
138    // Rings and polygons (with no holes)
139    // A Polygon -> Ring conversion is not provided, as the polygon might have holes.
140    impl From<Ring> for geo_types::Polygon {
141        fn from(source: Ring) -> Self {
142            geo_types::Polygon::new(source.0.into(), vec![])
143        }
144    }
145    // Polygons and polygons
146    impl From<geo_types::Polygon> for Polygon {
147        fn from(source: geo_types::Polygon) -> Self {
148            Self(
149                [source.exterior().clone().into()]
150                    .into_iter()
151                    .chain(
152                        source
153                            .interiors()
154                            .iter()
155                            .map(|linestring| Ring::from(linestring.clone())),
156                    )
157                    .collect(),
158            )
159        }
160    }
161    impl From<Polygon> for geo_types::Polygon {
162        fn from(mut source: Polygon) -> Self {
163            if source.0.is_empty() {
164                return Self::new(geo_types::LineString::new(vec![]), vec![]);
165            }
166            let exterior = source.0.remove(0);
167            geo_types::Polygon::new(
168                exterior.into(),
169                source
170                    .0
171                    .into_iter()
172                    .map(geo_types::LineString::from)
173                    .collect(),
174            )
175        }
176    }
177    to_from_sql!(geo_types::Polygon, Polygon);
178    // Multi polygons
179    impl From<MultiPolygon> for geo_types::MultiPolygon {
180        fn from(source: MultiPolygon) -> Self {
181            source.0.into_iter().map(geo_types::Polygon::from).collect()
182        }
183    }
184    impl From<geo_types::MultiPolygon> for MultiPolygon {
185        fn from(source: geo_types::MultiPolygon) -> Self {
186            Self(source.into_iter().map(Polygon::from).collect())
187        }
188    }
189    to_from_sql!(geo_types::MultiPolygon, MultiPolygon);
190    #[cfg(test)]
191    #[test]
192    fn roundtrip() {
193        let multipolygon_geo: geo_types::MultiPolygon = geo_types::wkt! {
194            // Example from https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
195            MULTIPOLYGON (((40.0 40.0, 20.0 45.0, 45.0 30.0, 40.0 40.0)),
196                          ((20.0 35.0, 10.0 30.0, 10.0 10.0, 30.0 5.0, 45.0 20.0, 20.0 35.0),
197                           (30.0 20.0, 20.0 15.0, 20.0 25.0, 30. 20.0)))
198        };
199        let multipolygon = MultiPolygon::from(multipolygon_geo.clone());
200        let multipolygon_geo2 = geo_types::MultiPolygon::from(multipolygon);
201        assert_eq!(multipolygon_geo, multipolygon_geo2);
202    }
203}