1use serde::de;
22
23use crate::{
24 Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point,
25 Polygon, Position,
26};
27
28fn position(coord: geo_types::Coord<f64>) -> Position {
32 vec![coord.x, coord.y]
33}
34
35fn line_string_positions(ls: geo_types::LineString<f64>) -> Vec<Position> {
37 ls.0.into_iter().map(position).collect()
38}
39
40fn polygon_rings(poly: geo_types::Polygon<f64>) -> Vec<Vec<Position>> {
43 let (exterior, interiors) = poly.into_inner();
44 let exterior = line_string_positions(exterior);
45 if exterior.is_empty() {
46 return vec![];
47 }
48 let mut rings = vec![exterior];
49 rings.extend(interiors.into_iter().map(line_string_positions));
50 rings
51}
52
53impl From<geo_types::Point<f64>> for Point {
54 fn from(p: geo_types::Point<f64>) -> Self {
55 Point::new(position(p.0))
56 }
57}
58
59impl From<geo_types::MultiPoint<f64>> for MultiPoint {
60 fn from(mp: geo_types::MultiPoint<f64>) -> Self {
61 MultiPoint::new(mp.0.into_iter().map(|p| position(p.0)).collect())
62 }
63}
64
65impl From<geo_types::LineString<f64>> for LineString {
66 fn from(ls: geo_types::LineString<f64>) -> Self {
67 LineString::new(line_string_positions(ls))
68 }
69}
70
71impl From<geo_types::MultiLineString<f64>> for MultiLineString {
72 fn from(mls: geo_types::MultiLineString<f64>) -> Self {
73 MultiLineString::new(mls.0.into_iter().map(line_string_positions).collect())
74 }
75}
76
77impl From<geo_types::Polygon<f64>> for Polygon {
78 fn from(poly: geo_types::Polygon<f64>) -> Self {
79 Polygon::new(polygon_rings(poly))
80 }
81}
82
83impl From<geo_types::MultiPolygon<f64>> for MultiPolygon {
84 fn from(mp: geo_types::MultiPolygon<f64>) -> Self {
85 MultiPolygon::new(mp.0.into_iter().map(polygon_rings).collect())
86 }
87}
88
89impl From<geo_types::GeometryCollection<f64>> for GeometryCollection {
90 fn from(gc: geo_types::GeometryCollection<f64>) -> Self {
91 GeometryCollection::new(gc.0.into_iter().map(Geometry::from).collect())
92 }
93}
94
95impl From<geo_types::Geometry<f64>> for Geometry {
96 fn from(g: geo_types::Geometry<f64>) -> Self {
97 use geo_types::Geometry as G;
98 match g {
99 G::Point(p) => Geometry::Point(p.into()),
100 G::MultiPoint(mp) => Geometry::MultiPoint(mp.into()),
101 G::LineString(ls) => Geometry::LineString(ls.into()),
102 G::MultiLineString(mls) => Geometry::MultiLineString(mls.into()),
103 G::Polygon(p) => Geometry::Polygon(p.into()),
104 G::MultiPolygon(mp) => Geometry::MultiPolygon(mp.into()),
105 G::GeometryCollection(gc) => Geometry::GeometryCollection(gc.into()),
106 G::Line(l) => {
108 Geometry::LineString(LineString::new(vec![position(l.start), position(l.end)]))
109 }
110 G::Rect(r) => Geometry::Polygon(r.to_polygon().into()),
111 G::Triangle(t) => Geometry::Polygon(t.to_polygon().into()),
112 }
113 }
114}
115
116fn arity_error(len: usize) -> serde_json::Error {
119 <serde_json::Error as de::Error>::custom(format!(
120 "geo_types needs at least 2 coordinates (x, y) per position, found {len}"
121 ))
122}
123
124fn coord(pos: &Position) -> Result<geo_types::Coord<f64>, serde_json::Error> {
127 match pos.as_slice() {
128 [x, y, ..] => Ok(geo_types::Coord { x: *x, y: *y }),
129 _ => Err(arity_error(pos.len())),
130 }
131}
132
133fn geo_line_string(ring: &[Position]) -> Result<geo_types::LineString<f64>, serde_json::Error> {
134 ring.iter()
135 .map(coord)
136 .collect::<Result<Vec<_>, _>>()
137 .map(geo_types::LineString)
138}
139
140fn geo_polygon(rings: &[Vec<Position>]) -> Result<geo_types::Polygon<f64>, serde_json::Error> {
141 let exterior = match rings.first() {
142 Some(ring) => geo_line_string(ring)?,
143 None => geo_types::LineString(vec![]),
144 };
145 let interiors = rings
146 .iter()
147 .skip(1)
148 .map(|ring| geo_line_string(ring))
149 .collect::<Result<Vec<_>, _>>()?;
150 Ok(geo_types::Polygon::new(exterior, interiors))
151}
152
153impl TryFrom<Point> for geo_types::Point<f64> {
154 type Error = serde_json::Error;
155 fn try_from(p: Point) -> Result<Self, Self::Error> {
156 Ok(geo_types::Point(coord(&p.coordinates)?))
157 }
158}
159
160impl TryFrom<MultiPoint> for geo_types::MultiPoint<f64> {
161 type Error = serde_json::Error;
162 fn try_from(mp: MultiPoint) -> Result<Self, Self::Error> {
163 mp.coordinates
164 .iter()
165 .map(|c| coord(c).map(geo_types::Point))
166 .collect::<Result<Vec<_>, _>>()
167 .map(geo_types::MultiPoint)
168 }
169}
170
171impl TryFrom<LineString> for geo_types::LineString<f64> {
172 type Error = serde_json::Error;
173 fn try_from(ls: LineString) -> Result<Self, Self::Error> {
174 geo_line_string(&ls.coordinates)
175 }
176}
177
178impl TryFrom<MultiLineString> for geo_types::MultiLineString<f64> {
179 type Error = serde_json::Error;
180 fn try_from(mls: MultiLineString) -> Result<Self, Self::Error> {
181 mls.coordinates
182 .iter()
183 .map(|ring| geo_line_string(ring))
184 .collect::<Result<Vec<_>, _>>()
185 .map(geo_types::MultiLineString)
186 }
187}
188
189impl TryFrom<Polygon> for geo_types::Polygon<f64> {
190 type Error = serde_json::Error;
191 fn try_from(p: Polygon) -> Result<Self, Self::Error> {
192 geo_polygon(&p.coordinates)
193 }
194}
195
196impl TryFrom<MultiPolygon> for geo_types::MultiPolygon<f64> {
197 type Error = serde_json::Error;
198 fn try_from(mp: MultiPolygon) -> Result<Self, Self::Error> {
199 mp.coordinates
200 .iter()
201 .map(|poly| geo_polygon(poly))
202 .collect::<Result<Vec<_>, _>>()
203 .map(geo_types::MultiPolygon)
204 }
205}
206
207impl TryFrom<GeometryCollection> for geo_types::GeometryCollection<f64> {
208 type Error = serde_json::Error;
209 fn try_from(gc: GeometryCollection) -> Result<Self, Self::Error> {
210 gc.geometries
211 .into_iter()
212 .map(geo_types::Geometry::try_from)
213 .collect::<Result<Vec<_>, _>>()
214 .map(geo_types::GeometryCollection)
215 }
216}
217
218impl TryFrom<Geometry> for geo_types::Geometry<f64> {
219 type Error = serde_json::Error;
220 fn try_from(g: Geometry) -> Result<Self, Self::Error> {
221 Ok(match g {
222 Geometry::Point(p) => geo_types::Geometry::Point(p.try_into()?),
223 Geometry::MultiPoint(mp) => geo_types::Geometry::MultiPoint(mp.try_into()?),
224 Geometry::LineString(ls) => geo_types::Geometry::LineString(ls.try_into()?),
225 Geometry::MultiLineString(mls) => geo_types::Geometry::MultiLineString(mls.try_into()?),
226 Geometry::Polygon(p) => geo_types::Geometry::Polygon(p.try_into()?),
227 Geometry::MultiPolygon(mp) => geo_types::Geometry::MultiPolygon(mp.try_into()?),
228 Geometry::GeometryCollection(gc) => {
229 geo_types::Geometry::GeometryCollection(gc.try_into()?)
230 }
231 })
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn point_round_trips_through_geo_types() {
241 let ours = Point::new(vec![-96.8, 32.8]);
242 let geo: geo_types::Point<f64> = ours.clone().try_into().unwrap();
243 assert_eq!(geo, geo_types::Point::new(-96.8, 32.8));
244 assert_eq!(Point::from(geo), ours);
246 }
247
248 #[test]
249 fn elevation_is_dropped_like_upstream() {
250 let p3d = Point::new(vec![1.0, 2.0, 3.0]);
252 let geo: geo_types::Point<f64> = p3d.try_into().unwrap();
253 assert_eq!(geo, geo_types::Point::new(1.0, 2.0));
254 }
255
256 #[test]
257 fn short_position_is_an_error() {
258 let bad = Point::new(vec![1.0]);
259 let err = geo_types::Point::<f64>::try_from(bad).unwrap_err();
260 assert!(err.to_string().contains("at least 2"));
261 }
262
263 #[test]
264 fn polygon_rings_survive_both_directions() {
265 let ours = Polygon::new(vec![
267 vec![
268 vec![0.0, 0.0],
269 vec![4.0, 0.0],
270 vec![4.0, 4.0],
271 vec![0.0, 4.0],
272 vec![0.0, 0.0],
273 ],
274 vec![
275 vec![1.0, 1.0],
276 vec![2.0, 1.0],
277 vec![1.0, 2.0],
278 vec![1.0, 1.0],
279 ],
280 ]);
281 let geo: geo_types::Polygon<f64> = ours.clone().try_into().unwrap();
282 assert_eq!(geo.interiors().len(), 1);
283 assert_eq!(Polygon::from(geo), ours);
284 }
285
286 #[test]
287 fn geo_line_and_triangle_degrade_to_geojson_shapes() {
288 let line = geo_types::Line::new(
289 geo_types::Coord { x: 0.0, y: 0.0 },
290 geo_types::Coord { x: 1.0, y: 1.0 },
291 );
292 assert!(matches!(
293 Geometry::from(geo_types::Geometry::Line(line)),
294 Geometry::LineString(_)
295 ));
296
297 let tri = geo_types::Triangle::new(
298 geo_types::Coord { x: 0.0, y: 0.0 },
299 geo_types::Coord { x: 2.0, y: 0.0 },
300 geo_types::Coord { x: 1.0, y: 1.0 },
301 );
302 assert!(matches!(
303 Geometry::from(geo_types::Geometry::Triangle(tri)),
304 Geometry::Polygon(_)
305 ));
306 }
307
308 #[test]
309 fn geometry_union_round_trips() {
310 let ours = Geometry::MultiPoint(MultiPoint::new(vec![vec![0.0, 0.0], vec![1.0, 1.0]]));
311 let geo: geo_types::Geometry<f64> = ours.clone().try_into().unwrap();
312 assert_eq!(Geometry::from(geo), ours);
313 }
314}