sfcgal/conversion/
geojson.rs

1use crate::{
2    conversion::coords::{CoordType, FromSFCGALGeom, ToSFCGALGeom},
3    CoordSeq, Point2d, Point3d, Result, SFCGeometry, ToCoordinates, ToSFCGAL,
4};
5use anyhow::Error;
6use geojson::Value as GeometryValue;
7
8/// Conversion from [`SFCGeometry`] (implemented on [geo-types](https://docs.rs/geo-types/) geometries)
9///
10/// [`SFCGeometry`]: struct.SFCGeometry.html
11pub trait TryIntoCoords<T> {
12    type Err;
13    fn try_into(&self) -> Result<T>;
14}
15
16/// Convert coordinates to `sfcgal_geometry_t`.
17pub trait FromSlice {
18    fn from_slice(pt: &[f64]) -> Result<Self>
19    where
20        Self: std::marker::Sized;
21}
22
23/// Convert coordinates to `sfcgal_geometry_t`.
24pub trait ToVec {
25    fn to_vec(&self) -> Vec<f64>;
26}
27
28impl ToVec for Point2d {
29    fn to_vec(&self) -> Vec<f64> {
30        vec![self.0, self.1]
31    }
32}
33
34impl ToVec for Point3d {
35    fn to_vec(&self) -> Vec<f64> {
36        vec![self.0, self.1, self.2]
37    }
38}
39
40impl FromSlice for Point2d {
41    fn from_slice(pt: &[f64]) -> Result<Point2d> {
42        let mut it = pt.iter();
43        Ok((*it.next().unwrap(), *it.next().unwrap()))
44    }
45}
46
47impl FromSlice for Point3d {
48    fn from_slice(pt: &[f64]) -> Result<Point3d> {
49        let mut it = pt.iter();
50        Ok((
51            *it.next().unwrap(),
52            *it.next().unwrap(),
53            *it.next().unwrap_or(&(0.0f64)),
54        ))
55    }
56}
57
58/// Implements conversion from CoordSeq to geo_types::Geometry
59/// (better use TryInto<geo_types::Geometry> for SFCGeometry if the intend
60/// is to convert SFCGAL Geometries to geo_types ones)
61impl<T> TryIntoCoords<CoordSeq<T>> for GeometryValue
62where
63    T: FromSlice + CoordType,
64{
65    type Err = Error;
66    fn try_into(&self) -> Result<CoordSeq<T>>
67    where
68        T: FromSlice + CoordType,
69    {
70        match *self {
71            GeometryValue::Point(ref pt) => Ok(CoordSeq::Point(T::from_slice(pt)?)),
72            GeometryValue::MultiPoint(ref pts) => {
73                let _pts = pts
74                    .iter()
75                    .map(|p| T::from_slice(p))
76                    .collect::<Result<Vec<T>>>();
77                Ok(CoordSeq::Multipoint(_pts?))
78            }
79            GeometryValue::LineString(ref pts) => {
80                let _pts = pts
81                    .iter()
82                    .map(|p| T::from_slice(p))
83                    .collect::<Result<Vec<T>>>();
84                Ok(CoordSeq::Linestring(_pts?))
85            }
86            GeometryValue::MultiLineString(ref lines) => {
87                let _pts = lines
88                    .iter()
89                    .map(|pts| pts.iter().map(|p| T::from_slice(p)).collect())
90                    .collect::<Result<Vec<Vec<T>>>>();
91                Ok(CoordSeq::Multilinestring(_pts?))
92            }
93            GeometryValue::Polygon(ref rings) => {
94                let _pts = rings
95                    .iter()
96                    .map(|pts| pts.iter().map(|p| T::from_slice(p)).collect())
97                    .collect::<Result<Vec<Vec<T>>>>();
98                Ok(CoordSeq::Polygon(_pts?))
99            }
100            GeometryValue::MultiPolygon(ref polygons) => {
101                let _pts = polygons
102                    .iter()
103                    .map(|rings| {
104                        rings
105                            .iter()
106                            .map(|pts| pts.iter().map(|p| T::from_slice(p)).collect())
107                            .collect()
108                    })
109                    .collect::<Result<Vec<Vec<Vec<T>>>>>();
110                Ok(CoordSeq::Multipolygon(_pts?))
111            }
112            GeometryValue::GeometryCollection(ref geoms) => {
113                let _geoms = geoms
114                    .iter()
115                    .map(|geom| TryIntoCoords::try_into(&geom.value))
116                    .collect::<Result<Vec<CoordSeq<T>>>>();
117                Ok(CoordSeq::Geometrycollection(_geoms?))
118            }
119        }
120    }
121}
122
123/// Conversion from GeoJson to SFCGAL Geometries.
124pub trait FromGeoJSON {
125    type Err;
126    fn from_geojson<T: FromSlice + CoordType + ToSFCGALGeom>(
127        geom: &GeometryValue,
128    ) -> Result<SFCGeometry>;
129}
130
131/// Conversion from SFCGAL Geometries to GeoJson.
132pub trait ToGeoJSON {
133    type Err;
134    fn to_geojson<T: FromSlice + CoordType + ToVec + FromSFCGALGeom>(
135        &self,
136    ) -> Result<GeometryValue>;
137}
138
139/// Conversion from SFCGAL Geometries to GeoJson.
140///
141/// Allows to choose if coordinates of constructed geojson have to be 2d or 3d.
142/// ``` rust
143/// use sfcgal::{SFCGeometry, ToGeoJSON};
144/// type Point3d = (f64, f64, f64);
145///
146/// let input_wkt = "POINT (0.1 0.9 1.0)";
147/// let pt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
148/// let pt_geojson = pt_sfcgal.to_geojson::<Point3d>().unwrap();
149/// ```
150impl ToGeoJSON for SFCGeometry {
151    type Err = Error;
152    fn to_geojson<T: FromSlice + CoordType + ToVec + FromSFCGALGeom>(
153        &self,
154    ) -> Result<GeometryValue> {
155        let cs: CoordSeq<T> = self.to_coordinates()?;
156        cs.to_geojson::<T>()
157    }
158}
159
160impl<U> ToGeoJSON for CoordSeq<U>
161where
162    U: FromSlice + CoordType + ToVec + FromSFCGALGeom,
163{
164    type Err = Error;
165    fn to_geojson<T: FromSlice + CoordType + ToVec + FromSFCGALGeom>(
166        &self,
167    ) -> Result<GeometryValue> {
168        match self {
169            CoordSeq::Point(pt) => Ok(GeometryValue::Point(pt.to_vec())),
170            CoordSeq::Multipoint(pts) => Ok(GeometryValue::MultiPoint(
171                pts.iter().map(|p| p.to_vec()).collect(),
172            )),
173            CoordSeq::Linestring(pts) => Ok(GeometryValue::LineString(
174                pts.iter().map(|p| p.to_vec()).collect(),
175            )),
176            CoordSeq::Multilinestring(lines) => Ok(GeometryValue::MultiLineString(
177                lines
178                    .iter()
179                    .map(|pts| pts.iter().map(|p| p.to_vec()).collect())
180                    .collect(),
181            )),
182            CoordSeq::Polygon(rings) => Ok(GeometryValue::Polygon(
183                rings
184                    .iter()
185                    .map(|pts| pts.iter().map(|p| p.to_vec()).collect())
186                    .collect(),
187            )),
188            CoordSeq::Multipolygon(polygons) => Ok(GeometryValue::MultiPolygon(
189                polygons
190                    .iter()
191                    .map(|rings| {
192                        rings
193                            .iter()
194                            .map(|pts| pts.iter().map(|p| p.to_vec()).collect())
195                            .collect()
196                    })
197                    .collect(),
198            )),
199            CoordSeq::Geometrycollection(geoms) => Ok(GeometryValue::GeometryCollection(
200                geoms
201                    .iter()
202                    .map(|geom| {
203                        Ok(geojson::Geometry {
204                            bbox: None,
205                            value: geom.to_geojson::<T>()?,
206                            foreign_members: None,
207                        })
208                    })
209                    .collect::<Result<Vec<_>>>()?,
210            )),
211            _ => unimplemented!(),
212        }
213    }
214}
215
216/// Conversion from GeoJson to SFCGAL geometries.
217///
218/// Allows to choose if the constructed SFCGAL geometries will be 2d or 3d.
219/// ``` rust
220/// use sfcgal::{SFCGeometry, FromGeoJSON};
221/// type Point2d = (f64, f64);
222///
223/// let geom = geojson::Geometry {
224///     bbox: None,
225///     value: geojson::Value::LineString(vec![vec![101.0, 0.0], vec![102.0, 1.0]]),
226///     foreign_members: None,
227/// };
228/// let line_sfcgal = SFCGeometry::from_geojson::<Point2d>(&geom.value).unwrap();
229/// ```
230impl FromGeoJSON for SFCGeometry {
231    type Err = Error;
232    fn from_geojson<T: FromSlice + CoordType + ToSFCGALGeom>(
233        geom: &GeometryValue,
234    ) -> Result<SFCGeometry> {
235        let coords: CoordSeq<T> = TryIntoCoords::try_into(geom)?;
236        coords.to_sfcgal()
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::{Point2d, Point3d, ToGeoJSON, TryIntoCoords};
243    use crate::*;
244
245    #[test]
246    fn point_2d_sfcgal_to_geojson_to_sfcgal() {
247        let input_wkt = "POINT (0.1 0.9)";
248        let pt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
249        let pt_geojson = pt_sfcgal.to_geojson::<Point2d>().unwrap();
250        let pt_sfcgal_new = SFCGeometry::from_geojson::<Point2d>(&pt_geojson).unwrap();
251        assert_eq!(
252            pt_sfcgal.to_wkt_decim(1).unwrap(),
253            pt_sfcgal_new.to_wkt_decim(1).unwrap()
254        );
255        let a: CoordSeq<Point2d> = TryIntoCoords::try_into(&pt_geojson).unwrap();
256        let b = a.to_sfcgal().unwrap();
257        assert_eq!(
258            pt_sfcgal.to_wkt_decim(1).unwrap(),
259            b.to_wkt_decim(1).unwrap()
260        );
261    }
262
263    #[test]
264    fn point_3d_sfcgal_to_geojson_to_sfcgal() {
265        let input_wkt = "POINT (0.1 0.9 1.0)";
266        let pt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
267        let pt_geojson = pt_sfcgal.to_geojson::<Point3d>().unwrap();
268        let pt_sfcgal_new = SFCGeometry::from_geojson::<Point3d>(&pt_geojson).unwrap();
269        assert_eq!(
270            pt_sfcgal.to_wkt_decim(1).unwrap(),
271            pt_sfcgal_new.to_wkt_decim(1).unwrap()
272        );
273        let a: CoordSeq<Point3d> = TryIntoCoords::try_into(&pt_geojson).unwrap();
274        let b = a.to_sfcgal().unwrap();
275        assert_eq!(
276            pt_sfcgal.to_wkt_decim(1).unwrap(),
277            b.to_wkt_decim(1).unwrap()
278        );
279    }
280    #[test]
281    fn multipoint_2d_sfcgal_to_geojson_to_sfcgal() {
282        let input_wkt = "MULTIPOINT ((0.1 0.9),(2.3 3.4))";
283        let multipt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
284        let multipt_geojson = multipt_sfcgal.to_geojson::<Point2d>().unwrap();
285        let multipt_sfcgal_new = SFCGeometry::from_geojson::<Point2d>(&multipt_geojson).unwrap();
286        assert_eq!(
287            multipt_sfcgal.to_wkt_decim(1).unwrap(),
288            multipt_sfcgal_new.to_wkt_decim(1).unwrap()
289        );
290    }
291
292    #[test]
293    fn multipoint_3d_sfcgal_to_geojson_to_sfcgal() {
294        let input_wkt = "MULTIPOINT ((0.1 0.9 1.1),(2.3 3.4 1.1))";
295        let multipt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
296        let multipt_geojson = multipt_sfcgal.to_geojson::<Point3d>().unwrap();
297        let multipt_sfcgal_new = SFCGeometry::from_geojson::<Point3d>(&multipt_geojson).unwrap();
298        assert_eq!(
299            multipt_sfcgal.to_wkt_decim(1).unwrap(),
300            multipt_sfcgal_new.to_wkt_decim(1).unwrap()
301        );
302    }
303    #[test]
304    fn line_2d_sfcgal_to_geojson_to_sfcgal() {
305        let input_wkt = "LINESTRING (10.0 1.0, 1.0 2.0)";
306        let line_sfcgal = SFCGeometry::new(input_wkt).unwrap();
307        let line_geojson = line_sfcgal.to_geojson::<Point2d>().unwrap();
308        let line_sfcgal_new = SFCGeometry::from_geojson::<Point2d>(&line_geojson).unwrap();
309        assert_eq!(
310            line_sfcgal.to_wkt_decim(1).unwrap(),
311            line_sfcgal_new.to_wkt_decim(1).unwrap()
312        );
313        let a: CoordSeq<Point2d> = TryIntoCoords::try_into(&line_geojson).unwrap();
314        let b = a.to_sfcgal().unwrap();
315        assert_eq!(
316            line_sfcgal.to_wkt_decim(1).unwrap(),
317            b.to_wkt_decim(1).unwrap()
318        );
319    }
320    #[test]
321    fn line_3d_sfcgal_to_geojson_to_sfcgal() {
322        let input_wkt = "LINESTRING (10.0 1.0 2.0, 1.0 2.0 1.7)";
323        let line_sfcgal = SFCGeometry::new(input_wkt).unwrap();
324        let line_geojson = line_sfcgal.to_geojson::<Point3d>().unwrap();
325        let line_sfcgal_new = SFCGeometry::from_geojson::<Point3d>(&line_geojson).unwrap();
326        assert_eq!(
327            line_sfcgal.to_wkt_decim(1).unwrap(),
328            line_sfcgal_new.to_wkt_decim(1).unwrap()
329        );
330        let a: CoordSeq<Point3d> = TryIntoCoords::try_into(&line_geojson).unwrap();
331        let b = a.to_sfcgal().unwrap();
332        assert_eq!(
333            line_sfcgal.to_wkt_decim(1).unwrap(),
334            b.to_wkt_decim(1).unwrap()
335        );
336    }
337    #[test]
338    fn multiline_2d_sfcgal_to_geojson_to_sfcgal() {
339        let input_wkt = "MULTILINESTRING (\
340                         (-0.0 -0.0,0.5 0.5),\
341                         (1.0 -0.0,0.5 0.5),\
342                         (1.0 1.0,0.5 0.5),\
343                         (-0.0 1.0,0.5 0.5))";
344        let multiline_sfcgal = SFCGeometry::new(input_wkt).unwrap();
345        let multiline_geojson = multiline_sfcgal.to_geojson::<Point2d>().unwrap();
346        let multiline_sfcgal_new =
347            SFCGeometry::from_geojson::<Point2d>(&multiline_geojson).unwrap();
348        assert_eq!(
349            multiline_sfcgal.to_wkt_decim(1).unwrap(),
350            multiline_sfcgal_new.to_wkt_decim(1).unwrap()
351        );
352        let a: CoordSeq<Point2d> = TryIntoCoords::try_into(&multiline_geojson).unwrap();
353        let b = a.to_sfcgal().unwrap();
354        assert_eq!(
355            multiline_sfcgal.to_wkt_decim(1).unwrap(),
356            b.to_wkt_decim(1).unwrap()
357        );
358    }
359    #[test]
360    fn multiline_3d_sfcgal_to_geojson_to_sfcgal() {
361        let input_wkt = "MULTILINESTRING (\
362                         (-0.0 -0.0 1.3,0.5 0.5 1.3),\
363                         (1.0 -0.0 1.3,0.5 0.5 1.3),\
364                         (1.0 1.0 1.3,0.5 0.5 1.3),\
365                         (-0.0 1.0 1.3,0.5 0.5 1.3))";
366        let multiline_sfcgal = SFCGeometry::new(input_wkt).unwrap();
367        let multiline_geojson = multiline_sfcgal.to_geojson::<Point3d>().unwrap();
368        let multiline_sfcgal_new =
369            SFCGeometry::from_geojson::<Point3d>(&multiline_geojson).unwrap();
370        assert_eq!(
371            multiline_sfcgal.to_wkt_decim(1).unwrap(),
372            multiline_sfcgal_new.to_wkt_decim(1).unwrap()
373        );
374        let a: CoordSeq<Point3d> = TryIntoCoords::try_into(&multiline_geojson).unwrap();
375        let b = a.to_sfcgal().unwrap();
376        assert_eq!(
377            multiline_sfcgal.to_wkt_decim(1).unwrap(),
378            b.to_wkt_decim(1).unwrap()
379        );
380    }
381    #[test]
382    fn polyg_2d_sfcgal_to_geojson_to_sfcgal() {
383        let input_wkt = "POLYGON ((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0))";
384        let polyg_sfcgal = SFCGeometry::new(input_wkt).unwrap();
385        let polyg_geojson = polyg_sfcgal.to_geojson::<Point2d>().unwrap();
386        let polyg_sfcgal_new = SFCGeometry::from_geojson::<Point2d>(&polyg_geojson).unwrap();
387        assert_eq!(
388            polyg_sfcgal.to_wkt_decim(1).unwrap(),
389            polyg_sfcgal_new.to_wkt_decim(1).unwrap()
390        );
391        let a: CoordSeq<Point2d> = TryIntoCoords::try_into(&polyg_geojson).unwrap();
392        let b = a.to_sfcgal().unwrap();
393        assert_eq!(
394            polyg_sfcgal.to_wkt_decim(1).unwrap(),
395            b.to_wkt_decim(1).unwrap()
396        );
397    }
398    #[test]
399    fn polyg_3d_sfcgal_to_geojson_to_sfcgal() {
400        let input_wkt = "POLYGON ((0.0 0.0 2.0, 1.0 0.0 2.0, 1.0 1.0 2.0, 0.0 0.0 2.0))";
401        let polyg_sfcgal = SFCGeometry::new(input_wkt).unwrap();
402        let polyg_geojson = polyg_sfcgal.to_geojson::<Point3d>().unwrap();
403        let polyg_sfcgal_new = SFCGeometry::from_geojson::<Point3d>(&polyg_geojson).unwrap();
404        assert_eq!(
405            polyg_sfcgal.to_wkt_decim(1).unwrap(),
406            polyg_sfcgal_new.to_wkt_decim(1).unwrap()
407        );
408        let a: CoordSeq<Point3d> = TryIntoCoords::try_into(&polyg_geojson).unwrap();
409        let b = a.to_sfcgal().unwrap();
410        assert_eq!(
411            polyg_sfcgal.to_wkt_decim(1).unwrap(),
412            b.to_wkt_decim(1).unwrap()
413        );
414    }
415    #[test]
416    fn multipolyg_2d_sfcgal_to_geojson_to_sfcgal() {
417        let input_wkt = "MULTIPOLYGON (((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0)))";
418        let multipolyg_sfcgal = SFCGeometry::new(input_wkt).unwrap();
419        let multipolyg_geojson = multipolyg_sfcgal.to_geojson::<Point2d>().unwrap();
420        let multipolyg_sfcgal_new =
421            SFCGeometry::from_geojson::<Point2d>(&multipolyg_geojson).unwrap();
422        assert_eq!(
423            multipolyg_sfcgal.to_wkt_decim(1).unwrap(),
424            multipolyg_sfcgal_new.to_wkt_decim(1).unwrap()
425        );
426    }
427    #[test]
428    fn multipolyg_3d_sfcgal_to_geojson_to_sfcgal() {
429        let input_wkt = "MULTIPOLYGON (((0.0 0.0 2.0, 1.0 0.0 2.0, 1.0 1.0 2.0, 0.0 0.0 2.0)))";
430        let multipolyg_sfcgal = SFCGeometry::new(input_wkt).unwrap();
431        let multipolyg_geojson = multipolyg_sfcgal.to_geojson::<Point3d>().unwrap();
432        let multipolyg_sfcgal_new =
433            SFCGeometry::from_geojson::<Point3d>(&multipolyg_geojson).unwrap();
434        assert_eq!(
435            multipolyg_sfcgal.to_wkt_decim(1).unwrap(),
436            multipolyg_sfcgal_new.to_wkt_decim(1).unwrap()
437        );
438        let a: CoordSeq<Point3d> = TryIntoCoords::try_into(&multipolyg_geojson).unwrap();
439        let b = a.to_sfcgal().unwrap();
440        assert_eq!(
441            multipolyg_sfcgal.to_wkt_decim(1).unwrap(),
442            b.to_wkt_decim(1).unwrap()
443        );
444    }
445    #[test]
446    fn geomcollection_2d_sfcgal_to_geojson_to_sfcgal() {
447        let input_wkt = "GEOMETRYCOLLECTION (POINT (1.0 1.0),LINESTRING (10.0 1.0,1.0 2.0))";
448        let multipolyg_sfcgal = SFCGeometry::new(input_wkt).unwrap();
449        let multipolyg_geojson = multipolyg_sfcgal.to_geojson::<Point2d>().unwrap();
450        let multipolyg_sfcgal_new =
451            SFCGeometry::from_geojson::<Point2d>(&multipolyg_geojson).unwrap();
452        assert_eq!(
453            multipolyg_sfcgal.to_wkt_decim(1).unwrap(),
454            multipolyg_sfcgal_new.to_wkt_decim(1).unwrap()
455        );
456    }
457    #[test]
458    fn geomcollection_3d_sfcgal_to_geojson_to_sfcgal() {
459        let input_wkt =
460            "GEOMETRYCOLLECTION (POINT (1.0 1.0 4.0),LINESTRING (10.0 1.0 4.0,1.0 2.0 4.0))";
461        let multipolyg_sfcgal = SFCGeometry::new(input_wkt).unwrap();
462        let multipolyg_geojson = multipolyg_sfcgal.to_geojson::<Point3d>().unwrap();
463        let multipolyg_sfcgal_new =
464            SFCGeometry::from_geojson::<Point3d>(&multipolyg_geojson).unwrap();
465        assert_eq!(
466            multipolyg_sfcgal.to_wkt_decim(1).unwrap(),
467            multipolyg_sfcgal_new.to_wkt_decim(1).unwrap()
468        );
469        let a: CoordSeq<Point3d> = TryIntoCoords::try_into(&multipolyg_geojson).unwrap();
470        let c = a.to_geojson::<Point3d>().unwrap();
471        let b = a.to_sfcgal().unwrap();
472        let d = SFCGeometry::from_geojson::<Point3d>(&c).unwrap();
473        assert_eq!(
474            multipolyg_sfcgal.to_wkt_decim(1).unwrap(),
475            b.to_wkt_decim(1).unwrap()
476        );
477        assert_eq!(
478            multipolyg_sfcgal.to_wkt_decim(1).unwrap(),
479            d.to_wkt_decim(1).unwrap()
480        );
481    }
482}