clickhouse_arrow/native/values/
geo.rs

1//! Geo types
2//! <https://clickhouse.com/docs/en/sql-reference/data-types/geo>
3use super::*;
4
5/// Geo point, represented by its x and y coordinates.
6///
7/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#point>
8#[derive(Clone, Copy, Default, Debug, PartialEq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct Point(pub [f64; 2]);
11
12impl Hash for Point {
13    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
14        for x in self.0 {
15            x.to_bits().hash(state);
16        }
17    }
18}
19impl std::ops::Index<u8> for Point {
20    type Output = f64;
21
22    fn index(&self, index: u8) -> &Self::Output { &self.0[index as usize] }
23}
24impl AsRef<[f64; 2]> for Point {
25    fn as_ref(&self) -> &[f64; 2] { &self.0 }
26}
27
28/// Polygon without holes.
29///
30/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#ring>
31#[derive(Clone, Hash, Default, Debug, PartialEq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Ring(pub Vec<Point>);
34
35/// Polygon with holes. The first element is the outer polygon, and the following ones are the
36/// holes.
37///
38/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#polygon>
39#[derive(Clone, Hash, Default, Debug, PartialEq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub struct Polygon(pub Vec<Ring>);
42
43/// Union of polygons.
44///
45/// <https://clickhouse.com/docs/en/sql-reference/data-types/geo#multipolygon>
46#[derive(Clone, Hash, Default, Debug, PartialEq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct MultiPolygon(pub Vec<Polygon>);
49
50macro_rules! to_from_sql {
51    ($name:ident) => {
52        impl ToSql for $name {
53            fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> { Ok(Value::$name(self)) }
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 { Self { x: source[0], y: source[1] } }
101    }
102    impl From<geo_types::Coord> for Point {
103        fn from(source: geo_types::Coord) -> Self { Self([source.x, source.y]) }
104    }
105    to_from_sql!(geo_types::Coord, Point);
106
107    // Points and points
108    impl From<Point> for geo_types::Point {
109        fn from(source: Point) -> Self { geo_types::Point(source.into()) }
110    }
111    impl From<geo_types::Point> for Point {
112        fn from(source: geo_types::Point) -> Self { source.0.into() }
113    }
114    to_from_sql!(geo_types::Point, Point);
115    // Rings and Linestrings
116    impl From<Ring> for geo_types::LineString {
117        fn from(source: Ring) -> Self {
118            Self(source.0.into_iter().map(geo_types::Coord::from).collect())
119        }
120    }
121    impl From<geo_types::LineString> for Ring {
122        fn from(source: geo_types::LineString) -> Self {
123            Self(source.0.into_iter().map(Point::from).collect())
124        }
125    }
126    to_from_sql!(geo_types::LineString, Ring);
127    // Rings and polygons (with no holes)
128    // A Polygon -> Ring conversion is not provided, as the polygon might have holes.
129    impl From<Ring> for geo_types::Polygon {
130        fn from(source: Ring) -> Self { geo_types::Polygon::new(source.0.into(), vec![]) }
131    }
132    // Polygons and polygons
133    impl From<geo_types::Polygon> for Polygon {
134        fn from(source: geo_types::Polygon) -> Self {
135            Self(
136                [source.exterior().clone().into()]
137                    .into_iter()
138                    .chain(
139                        source.interiors().iter().map(|linestring| Ring::from(linestring.clone())),
140                    )
141                    .collect(),
142            )
143        }
144    }
145    impl From<Polygon> for geo_types::Polygon {
146        fn from(mut source: Polygon) -> Self {
147            if source.0.is_empty() {
148                return Self::new(geo_types::LineString::new(vec![]), vec![]);
149            }
150            let exterior = source.0.remove(0);
151            geo_types::Polygon::new(
152                exterior.into(),
153                source.0.into_iter().map(geo_types::LineString::from).collect(),
154            )
155        }
156    }
157    to_from_sql!(geo_types::Polygon, Polygon);
158    // Multi polygons
159    impl From<MultiPolygon> for geo_types::MultiPolygon {
160        fn from(source: MultiPolygon) -> Self {
161            source.0.into_iter().map(geo_types::Polygon::from).collect()
162        }
163    }
164    impl From<geo_types::MultiPolygon> for MultiPolygon {
165        fn from(source: geo_types::MultiPolygon) -> Self {
166            Self(source.into_iter().map(Polygon::from).collect())
167        }
168    }
169    to_from_sql!(geo_types::MultiPolygon, MultiPolygon);
170    #[cfg(test)]
171    #[test]
172    fn roundtrip() {
173        let multipolygon_geo: geo_types::MultiPolygon = geo_types::wkt! {
174            // Example from https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
175            MULTIPOLYGON (((40.0 40.0, 20.0 45.0, 45.0 30.0, 40.0 40.0)),
176                          ((20.0 35.0, 10.0 30.0, 10.0 10.0, 30.0 5.0, 45.0 20.0, 20.0 35.0),
177                           (30.0 20.0, 20.0 15.0, 20.0 25.0, 30. 20.0)))
178        };
179        let multipolygon = MultiPolygon::from(multipolygon_geo.clone());
180        let multipolygon_geo2 = geo_types::MultiPolygon::from(multipolygon);
181        assert_eq!(multipolygon_geo, multipolygon_geo2);
182    }
183}