sfcgal/conversion/
coords.rs

1use crate::{
2    utils::check_null_geom, GeomType, Point2d, Point3d, Result, SFCGeometry, ToCoordinates,
3    ToSFCGAL,
4};
5use sfcgal_sys::{
6    sfcgal_geometry_collection_add_geometry, sfcgal_geometry_collection_create,
7    sfcgal_geometry_collection_geometry_n, sfcgal_geometry_collection_num_geometries,
8    sfcgal_geometry_delete, sfcgal_geometry_t, sfcgal_linestring_add_point,
9    sfcgal_linestring_create, sfcgal_linestring_num_points, sfcgal_linestring_point_n,
10    sfcgal_multi_linestring_create, sfcgal_multi_point_create, sfcgal_multi_polygon_create,
11    sfcgal_point_create_from_xy, sfcgal_point_create_from_xyz, sfcgal_point_x, sfcgal_point_y,
12    sfcgal_point_z, sfcgal_polygon_add_interior_ring, sfcgal_polygon_create_from_exterior_ring,
13    sfcgal_polygon_exterior_ring, sfcgal_polygon_interior_ring_n,
14    sfcgal_polygon_num_interior_rings, sfcgal_polyhedral_surface_add_polygon,
15    sfcgal_polyhedral_surface_create, sfcgal_polyhedral_surface_num_polygons,
16    sfcgal_polyhedral_surface_polygon_n, sfcgal_solid_add_interior_shell, sfcgal_solid_create,
17    sfcgal_solid_create_from_exterior_shell, sfcgal_solid_num_shells, sfcgal_solid_shell_n,
18    sfcgal_triangle_create, sfcgal_triangle_set_vertex, sfcgal_triangle_vertex,
19    sfcgal_triangulated_surface_add_triangle, sfcgal_triangulated_surface_create,
20    sfcgal_triangulated_surface_num_triangles, sfcgal_triangulated_surface_triangle_n,
21};
22
23pub trait CoordType {}
24impl CoordType for Point2d {}
25impl CoordType for Point3d {}
26
27/// Convert a point passed as a raw `sfcgal_geometry_t` to a tuple of coordinates.
28pub trait FromSFCGALGeom {
29    fn from_sfcgeometry(geom: *const sfcgal_geometry_t) -> Self;
30}
31
32/// Convert coordinates to `sfcgal_geometry_t`.
33pub trait ToSFCGALGeom {
34    fn to_sfcgeometry(&self) -> Result<*mut sfcgal_geometry_t>;
35}
36
37impl FromSFCGALGeom for Point2d {
38    fn from_sfcgeometry(c_geom: *const sfcgal_geometry_t) -> Point2d {
39        let x = unsafe { sfcgal_point_x(c_geom) };
40        let y = unsafe { sfcgal_point_y(c_geom) };
41        (x, y)
42    }
43}
44
45impl FromSFCGALGeom for Point3d {
46    fn from_sfcgeometry(c_geom: *const sfcgal_geometry_t) -> Point3d {
47        let x = unsafe { sfcgal_point_x(c_geom) };
48        let y = unsafe { sfcgal_point_y(c_geom) };
49        let z = unsafe { sfcgal_point_z(c_geom) };
50        (x, y, z)
51    }
52}
53
54impl ToSFCGALGeom for Point2d {
55    fn to_sfcgeometry(&self) -> Result<*mut sfcgal_geometry_t> {
56        let g = unsafe { sfcgal_point_create_from_xy(self.0, self.1) };
57        check_null_geom(g)?;
58        Ok(g)
59    }
60}
61
62impl ToSFCGALGeom for Point3d {
63    fn to_sfcgeometry(&self) -> Result<*mut sfcgal_geometry_t> {
64        let g = unsafe { sfcgal_point_create_from_xyz(self.0, self.1, self.2) };
65        check_null_geom(g)?;
66        Ok(g)
67    }
68}
69
70impl<T> ToSFCGALGeom for Vec<T>
71where
72    T: ToSFCGALGeom + CoordType,
73{
74    fn to_sfcgeometry(&self) -> Result<*mut sfcgal_geometry_t> {
75        let out_linestring = unsafe { sfcgal_linestring_create() };
76        check_null_geom(out_linestring)?;
77        for point in self {
78            let sf_pt_geom: *mut sfcgal_geometry_t = point.to_sfcgeometry()?;
79            unsafe { sfcgal_linestring_add_point(out_linestring, sf_pt_geom) };
80        }
81        Ok(out_linestring)
82    }
83}
84
85/// Coordinates corresponding to the shapes described by SFCGAL Geometry types.
86///
87/// Used to construct SFCGeometry from coordinates or to retrieve coordinates from SFCGeometry.
88/// ``` rust
89/// use sfcgal::{CoordSeq, ToSFCGAL};
90///
91/// let coordinates = CoordSeq::Linestring(vec![(-0.5, -0.5, 2.5), (0., 0., 4.0)]);
92/// let line_3d = coordinates.to_sfcgal().unwrap();
93/// assert!(line_3d.is_valid().unwrap());
94/// ```
95/// ``` rust
96/// use sfcgal::{SFCGeometry, CoordSeq, Point3d, ToCoordinates};
97///
98/// let line_3d = SFCGeometry::new("LINESTRING (3.5 5.6 1.0,4.8 10.5 1.0)").unwrap();
99/// let coordinates = line_3d.to_coordinates::<Point3d>().unwrap();
100/// // assert_eq!(coordinates, CoordSeq::Linestring(vec![(3.5, 5.6, 1.0), (4.8, 10.5, 1.0)]));
101/// ```
102#[derive(Debug, Clone, Hash, PartialEq)]
103pub enum CoordSeq<T> {
104    /// A Point is described by a tuple of 2 or 3 coordinates.
105    Point(T),
106    /// A Linestring is described by Vec of Point.
107    Linestring(Vec<T>),
108    /// A Polygon is described by a Vec of closed Linestring : the first one
109    /// is the exterior ring and the others are interior rings.
110    Polygon(Vec<Vec<T>>),
111    /// A Multipoint is described by a Vec of Points.
112    Multipoint(Vec<T>),
113    /// A Multilinestring is described by a Vec of Linestrings.
114    Multilinestring(Vec<Vec<T>>),
115    /// A Multipolygon is described by a Vec of Polygons.
116    Multipolygon(Vec<Vec<Vec<T>>>),
117    /// A Geometrycollection is described by a Vec of any of these CoordSeq geometries.
118    Geometrycollection(Vec<CoordSeq<T>>),
119    /// A Polyhedralsurface is described by a Vec of Polygons.
120    Polyhedralsurface(Vec<Vec<Vec<T>>>),
121    /// A Triangulatedsurface is described by a Vec of Triangles.
122    Triangulatedsurface(Vec<Vec<T>>),
123    /// A Triangle is described by a Vec of 3 Points.
124    Triangle(Vec<T>),
125    /// A Solid is described by a Vec of Polyhedralsurfaces : the first one
126    /// is the exterior shell and the others are interior shells.
127    Solid(Vec<Vec<Vec<Vec<T>>>>),
128    /// A Multisolid is described by a Vec of Solids.
129    Multisolid(Vec<Vec<Vec<Vec<Vec<T>>>>>),
130}
131
132fn coords_polygon_to_sfcgal<T>(rings: &[Vec<T>]) -> Result<*mut sfcgal_geometry_t>
133where
134    T: ToSFCGALGeom + CoordType,
135{
136    let out_polygon =
137        unsafe { sfcgal_polygon_create_from_exterior_ring(rings[0].to_sfcgeometry()?) };
138    check_null_geom(out_polygon)?;
139    for ring in rings.iter().skip(1) {
140        unsafe { sfcgal_polygon_add_interior_ring(out_polygon, ring.to_sfcgeometry()?) };
141    }
142    Ok(out_polygon)
143}
144
145fn coords_polyhedralsurface_to_sfcgal<T>(
146    polyhedres: &[Vec<Vec<T>>],
147) -> Result<*mut sfcgal_geometry_t>
148where
149    T: ToSFCGALGeom + CoordType,
150{
151    let out_surf = unsafe { sfcgal_polyhedral_surface_create() };
152    check_null_geom(out_surf)?;
153    for poly in polyhedres {
154        unsafe { sfcgal_polyhedral_surface_add_polygon(out_surf, coords_polygon_to_sfcgal(poly)?) };
155    }
156    Ok(out_surf)
157}
158
159unsafe fn pts_to_triangle_sfcgal<T>(pts: &[T]) -> Result<*mut sfcgal_geometry_t>
160where
161    T: ToSFCGALGeom,
162{
163    let out_triangle = sfcgal_triangle_create();
164    check_null_geom(out_triangle)?;
165    let p0: *mut sfcgal_geometry_t = pts[0].to_sfcgeometry()?;
166    let p1: *mut sfcgal_geometry_t = pts[1].to_sfcgeometry()?;
167    let p2: *mut sfcgal_geometry_t = pts[2].to_sfcgeometry()?;
168    sfcgal_triangle_set_vertex(out_triangle, 0, p0);
169    sfcgal_triangle_set_vertex(out_triangle, 1, p1);
170    sfcgal_triangle_set_vertex(out_triangle, 2, p2);
171    sfcgal_geometry_delete(p0);
172    sfcgal_geometry_delete(p1);
173    sfcgal_geometry_delete(p2);
174    Ok(out_triangle)
175}
176
177/// Convert coordinates (tuple of 2 or 3 members) to [`SFCGeometry`] using
178/// the corresponding [`CoordSeq`] variant of the wanted geometry.
179///
180/// [`SFCGeometry`]: struct.SFCGeometry.html
181/// [`CoordSeq`]: enum.CoordSeq.html
182impl<T: ToSFCGALGeom + CoordType> ToSFCGAL for CoordSeq<T> {
183    /// Convert the coordinates of this [`CoordSeq`] to [`SFCGeometry`].
184    ///
185    /// [`SFCGeometry`]: struct.SFCGeometry.html
186    /// [`CoordSeq`]: enum.CoordSeq.html
187    fn to_sfcgal(&self) -> Result<SFCGeometry> {
188        match self {
189            CoordSeq::Point(pt) => unsafe { SFCGeometry::new_from_raw(pt.to_sfcgeometry()?, true) },
190            CoordSeq::Multipoint(pts) => make_sfcgal_multi_geom!(
191                sfcgal_multi_point_create(),
192                pts.iter()
193                    .map(|p| p.to_sfcgeometry())
194                    .collect::<Result<Vec<_>>>()?
195            ),
196            CoordSeq::Linestring(pts) => {
197                let out_linestring = pts.to_sfcgeometry()?;
198                unsafe { SFCGeometry::new_from_raw(out_linestring, true) }
199            }
200            CoordSeq::Multilinestring(ref linestring_list) => make_sfcgal_multi_geom!(
201                sfcgal_multi_linestring_create(),
202                linestring_list
203                    .iter()
204                    .map(|l| l.to_sfcgeometry())
205                    .collect::<Result<Vec<_>>>()?
206            ),
207            CoordSeq::Polygon(ref rings) => {
208                let out_polygon = coords_polygon_to_sfcgal(rings)?;
209                unsafe { SFCGeometry::new_from_raw(out_polygon, true) }
210            }
211            CoordSeq::Multipolygon(ref polygons) => make_sfcgal_multi_geom!(
212                sfcgal_multi_polygon_create(),
213                polygons
214                    .iter()
215                    .map(|p| coords_polygon_to_sfcgal(p))
216                    .collect::<Result<Vec<_>>>()?
217            ),
218            CoordSeq::Geometrycollection(ref geoms) => {
219                let out_geom_collection = unsafe { sfcgal_geometry_collection_create() };
220                check_null_geom(out_geom_collection)?;
221                for g_geom in geoms {
222                    let mut sfcgeom = g_geom.to_sfcgal()?;
223                    unsafe {
224                        // ^^ sfcgeom is a SFCGeometry struct on which we own the inner C geometry,
225                        // as we are passing it to `sfcgal_geometry_collection_add_geometry`
226                        // we are not responsible for its deallocation anymore :
227                        sfcgeom.owned = false;
228                        sfcgal_geometry_collection_add_geometry(
229                            out_geom_collection,
230                            sfcgeom.c_geom.as_ptr(),
231                        );
232                    };
233                }
234                unsafe { SFCGeometry::new_from_raw(out_geom_collection, true) }
235            }
236            CoordSeq::Triangle(ref pts) => unsafe {
237                SFCGeometry::new_from_raw(pts_to_triangle_sfcgal(pts)?, true)
238            },
239            CoordSeq::Triangulatedsurface(ref triangles) => unsafe {
240                let out_surface = sfcgal_triangulated_surface_create();
241                check_null_geom(out_surface)?;
242                triangles
243                    .iter()
244                    .map(|pts| {
245                        sfcgal_triangulated_surface_add_triangle(
246                            out_surface,
247                            pts_to_triangle_sfcgal(pts)?,
248                        );
249                        Ok(())
250                    })
251                    .collect::<Result<Vec<_>>>()?;
252                SFCGeometry::new_from_raw(out_surface, true)
253            },
254            CoordSeq::Polyhedralsurface(ref polygons) => {
255                let out_surface = coords_polyhedralsurface_to_sfcgal(polygons)?;
256                unsafe { SFCGeometry::new_from_raw(out_surface, true) }
257            }
258            CoordSeq::Solid(ref polyhedres) => {
259                let n_part = polyhedres.len();
260                let out_solid = if n_part == 0 {
261                    unsafe { sfcgal_solid_create() }
262                } else if n_part == 1 {
263                    let exterior = coords_polyhedralsurface_to_sfcgal(&polyhedres[0])?;
264                    unsafe { sfcgal_solid_create_from_exterior_shell(exterior) }
265                } else {
266                    let exterior = coords_polyhedralsurface_to_sfcgal(&polyhedres[0])?;
267                    let r_solid = unsafe { sfcgal_solid_create_from_exterior_shell(exterior) };
268                    polyhedres
269                        .iter()
270                        .skip(1)
271                        .map(|poly| {
272                            unsafe {
273                                sfcgal_solid_add_interior_shell(
274                                    r_solid,
275                                    coords_polyhedralsurface_to_sfcgal(poly)?,
276                                )
277                            };
278                            Ok(())
279                        })
280                        .collect::<Result<Vec<_>>>()?;
281                    r_solid
282                };
283                unsafe { SFCGeometry::new_from_raw(out_solid, true) }
284            }
285            _ => unimplemented!(),
286        }
287    }
288}
289
290macro_rules! sfcgal_pt_to_coords {
291    ($geom: expr, T) => {{
292        T::from_sfcgeometry($geom)
293    }};
294}
295
296macro_rules! sfcgal_line_to_coords {
297    ($geom: expr, $type_pt: ident) => {{
298        let g = $geom;
299        let n_points = unsafe { sfcgal_linestring_num_points(g) };
300        let mut v_points = Vec::with_capacity(n_points as usize);
301        for i in 0..n_points {
302            let pt_sfcgal = unsafe { sfcgal_linestring_point_n(g, i) };
303            // check_null_geom(g)?;
304            v_points.push(sfcgal_pt_to_coords!(pt_sfcgal, $type_pt));
305        }
306        v_points
307    }};
308}
309
310macro_rules! sfcgal_polygon_to_coords {
311    ($geom: expr, $type_pt: ident) => {{
312        let g = $geom;
313        let nrings = unsafe { sfcgal_polygon_num_interior_rings(g) };
314        let exterior_sfcgal = unsafe { sfcgal_polygon_exterior_ring(g) };
315        let mut rings = Vec::with_capacity(nrings as usize + 1);
316        rings.push(sfcgal_line_to_coords!(exterior_sfcgal, $type_pt));
317        for ix in 0..nrings {
318            let line_sfcgal = unsafe { sfcgal_polygon_interior_ring_n(g, ix) };
319            rings.push(sfcgal_line_to_coords!(line_sfcgal, $type_pt));
320        }
321        rings
322    }};
323}
324
325macro_rules! sfcgal_triangle_to_coords {
326    ($geom: expr, $type_pt: ident) => {{
327        let g = $geom;
328        let (p0, p1, p2) = unsafe {
329            (
330                sfcgal_pt_to_coords!(sfcgal_triangle_vertex(g, 0), $type_pt),
331                sfcgal_pt_to_coords!(sfcgal_triangle_vertex(g, 1), $type_pt),
332                sfcgal_pt_to_coords!(sfcgal_triangle_vertex(g, 2), $type_pt),
333            )
334        };
335        vec![p0, p1, p2]
336    }};
337}
338
339macro_rules! sfcgal_polyhedral_surface_to_coords {
340    ($geom: expr, $type_pt: ident) => {{
341        let g = $geom;
342        let ngeoms = unsafe { sfcgal_polyhedral_surface_num_polygons(g) };
343        let mut polygons = Vec::with_capacity(ngeoms as usize);
344        for ix in 0..ngeoms {
345            let poly = unsafe { sfcgal_polyhedral_surface_polygon_n(g, ix) };
346            polygons.push(sfcgal_polygon_to_coords!(poly, $type_pt));
347        }
348        polygons
349    }};
350}
351
352fn get_nb_geometry(geom: *const sfcgal_geometry_t) -> usize {
353    unsafe { sfcgal_geometry_collection_num_geometries(geom) }
354}
355
356fn get_geom_at_index(geom: *const sfcgal_geometry_t, ix: usize) -> *const sfcgal_geometry_t {
357    unsafe { sfcgal_geometry_collection_geometry_n(geom, ix) }
358}
359
360/// Convert a [`SFCGeometry`], given it's internal [`GeomType`], to the corresponding [`CoordSeq`]
361/// holding its coordinates (as tuple of 2 or 3 members).
362///
363/// [`SFCGeometry`]: struct.SFCGeometry.html
364/// [`GeomType`]: enum.GeomType.html
365/// [`CoordSeq`]: enum.CoordSeq.html
366impl ToCoordinates for SFCGeometry {
367    fn to_coordinates<T>(&self) -> Result<CoordSeq<T>>
368    where
369        T: FromSFCGALGeom + CoordType,
370    {
371        match self._type()? {
372            GeomType::Point => Ok(CoordSeq::Point(sfcgal_pt_to_coords!(
373                unsafe { self.c_geom.as_ref() },
374                T
375            ))),
376            GeomType::Multipoint => {
377                let geom = unsafe { self.c_geom.as_ref() };
378                let ngeoms = get_nb_geometry(geom);
379                let mut pts = Vec::with_capacity(ngeoms);
380                for ix in 0..ngeoms {
381                    pts.push(sfcgal_pt_to_coords!(get_geom_at_index(geom, ix), T));
382                }
383                Ok(CoordSeq::Multipoint(pts))
384            }
385            GeomType::Linestring => Ok(CoordSeq::Linestring(sfcgal_line_to_coords!(
386                unsafe { self.c_geom.as_ref() },
387                T
388            ))),
389            GeomType::Multilinestring => {
390                let geom = unsafe { self.c_geom.as_ref() };
391                let ngeoms = get_nb_geometry(geom);
392                let mut lines = Vec::with_capacity(ngeoms);
393                for ix in 0..ngeoms {
394                    lines.push(sfcgal_line_to_coords!(get_geom_at_index(geom, ix), T));
395                }
396                Ok(CoordSeq::Multilinestring(lines))
397            }
398            GeomType::Polygon => {
399                let geom = unsafe { self.c_geom.as_ref() };
400                Ok(CoordSeq::Polygon(sfcgal_polygon_to_coords!(geom, T)))
401            }
402            GeomType::Multipolygon => {
403                let geom = unsafe { self.c_geom.as_ref() };
404                let ngeoms = get_nb_geometry(geom);
405                let mut polygons = Vec::with_capacity(ngeoms);
406                for ix in 0..ngeoms {
407                    polygons.push(sfcgal_polygon_to_coords!(get_geom_at_index(geom, ix), T));
408                }
409                Ok(CoordSeq::Multipolygon(polygons))
410            }
411            GeomType::Geometrycollection => {
412                let geom = unsafe { self.c_geom.as_ref() };
413                let ngeoms = get_nb_geometry(geom);
414                let mut geoms = Vec::with_capacity(ngeoms);
415                for ix in 0..ngeoms {
416                    let inner_geom = unsafe {
417                        // Todo : document what we are doing that
418                        SFCGeometry::new_from_raw(
419                            sfcgal_geometry_collection_geometry_n(geom, ix)
420                                as *mut sfcgal_geometry_t,
421                            false,
422                        )?
423                    };
424                    let coords: CoordSeq<T> = inner_geom.to_coordinates()?;
425                    geoms.push(coords);
426                }
427                Ok(CoordSeq::Geometrycollection(geoms))
428            }
429            GeomType::Triangle => Ok(CoordSeq::Triangle(sfcgal_triangle_to_coords!(
430                unsafe { self.c_geom.as_ref() },
431                T
432            ))),
433            GeomType::Triangulatedsurface => {
434                let geom = unsafe { self.c_geom.as_ref() };
435                let ngeoms = unsafe { sfcgal_triangulated_surface_num_triangles(geom) };
436                let mut triangles = Vec::with_capacity(ngeoms);
437                for ix in 0..ngeoms {
438                    let triangle = unsafe { sfcgal_triangulated_surface_triangle_n(geom, ix) };
439                    triangles.push(sfcgal_triangle_to_coords!(triangle, T));
440                }
441                Ok(CoordSeq::Triangulatedsurface(triangles))
442            }
443            GeomType::Polyhedralsurface => Ok(CoordSeq::Polyhedralsurface(
444                sfcgal_polyhedral_surface_to_coords!(unsafe { self.c_geom.as_ref() }, T),
445            )),
446            GeomType::Solid => {
447                let geom = unsafe { self.c_geom.as_ref() };
448                let ngeoms = unsafe { sfcgal_solid_num_shells(geom) };
449                let mut polyhedres = Vec::with_capacity(ngeoms);
450                for ix in 0..ngeoms {
451                    let poly = unsafe { sfcgal_solid_shell_n(geom, ix) };
452                    polyhedres.push(sfcgal_polyhedral_surface_to_coords!(poly, T));
453                }
454                Ok(CoordSeq::Solid(polyhedres))
455            }
456            GeomType::Multisolid => {
457                let geom_ms = unsafe { self.c_geom.as_ref() };
458                let n_solides = get_nb_geometry(geom_ms);
459                let mut solides = Vec::with_capacity(n_solides);
460                for ix_geom in 0..n_solides {
461                    let solid = get_geom_at_index(geom_ms, ix_geom);
462                    let n_shell = unsafe { sfcgal_solid_num_shells(solid) };
463                    let mut polyhedres = Vec::with_capacity(n_shell);
464                    for ix in 0..n_shell {
465                        let poly = unsafe { sfcgal_solid_shell_n(solid, ix) };
466                        polyhedres.push(sfcgal_polyhedral_surface_to_coords!(poly, T));
467                    }
468                    solides.push(polyhedres);
469                }
470                Ok(CoordSeq::Multisolid(solides))
471            }
472        }
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use super::{Point2d, Point3d};
479    use crate::*;
480
481    #[test]
482    fn point_2d_sfcgal_to_coordinates_to_sfcgal() {
483        let input_wkt = "POINT (0.1 0.9)";
484        let pt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
485        let coords: CoordSeq<Point2d> = pt_sfcgal.to_coordinates().unwrap();
486        if let CoordSeq::Point(ref coords) = coords {
487            assert_ulps_eq!(coords.0, 0.1);
488            assert_ulps_eq!(coords.1, 0.9);
489        } else {
490            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
491        }
492        let pt_sfcgal = coords.to_sfcgal().unwrap();
493        assert_eq!(input_wkt, pt_sfcgal.to_wkt_decim(1).unwrap())
494    }
495
496    #[test]
497    fn point_3d_sfcgal_to_coordinates_to_sfcgal() {
498        let input_wkt = "POINT Z (0.1 0.9 1.0)";
499        let pt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
500        let coords: CoordSeq<Point3d> = pt_sfcgal.to_coordinates().unwrap();
501        if let CoordSeq::Point(ref coords) = coords {
502            assert_ulps_eq!(coords.0, 0.1);
503            assert_ulps_eq!(coords.1, 0.9);
504            assert_ulps_eq!(coords.2, 1.0);
505        } else {
506            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
507        }
508        let pt_sfcgal = coords.to_sfcgal().unwrap();
509        assert_eq!(input_wkt, pt_sfcgal.to_wkt_decim(1).unwrap())
510    }
511
512    #[test]
513    fn multipoint_2d_sfcgal_to_coordinates_to_sfcgal() {
514        let input_wkt = "MULTIPOINT ((3.5 5.6),(4.8 10.5))";
515        let multipt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
516        let coords: CoordSeq<Point2d> = multipt_sfcgal.to_coordinates().unwrap();
517        if let CoordSeq::Multipoint(ref coords) = coords {
518            assert_ulps_eq!(coords[0].0, 3.5);
519            assert_ulps_eq!(coords[0].1, 5.6);
520            assert_ulps_eq!(coords[1].0, 4.8);
521            assert_ulps_eq!(coords[1].1, 10.5);
522        } else {
523            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
524        }
525        let mpt_sfcgal = coords.to_sfcgal().unwrap();
526        assert_eq!(input_wkt, mpt_sfcgal.to_wkt_decim(1).unwrap());
527    }
528
529    #[test]
530    fn multipoint_3d_sfcgal_to_coordinates_to_sfcgal() {
531        let input_wkt = "MULTIPOINT Z ((3.5 5.6 1.0),(4.8 10.5 1.0))";
532        let multipt_sfcgal = SFCGeometry::new(input_wkt).unwrap();
533        let coords: CoordSeq<Point3d> = multipt_sfcgal.to_coordinates().unwrap();
534        if let CoordSeq::Multipoint(ref coords) = coords {
535            assert_ulps_eq!(coords[0].0, 3.5);
536            assert_ulps_eq!(coords[0].1, 5.6);
537            assert_ulps_eq!(coords[0].2, 1.0);
538            assert_ulps_eq!(coords[1].0, 4.8);
539            assert_ulps_eq!(coords[1].1, 10.5);
540            assert_ulps_eq!(coords[1].2, 1.0);
541        } else {
542            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
543        }
544        let mpt_sfcgal = coords.to_sfcgal().unwrap();
545        assert_eq!(input_wkt, mpt_sfcgal.to_wkt_decim(1).unwrap());
546    }
547
548    #[test]
549    fn linestring_2d_sfcgal_to_coordinates() {
550        let input_wkt = "LINESTRING (3.5 5.6,4.8 10.5)";
551        let linestring_sfcgal = SFCGeometry::new(input_wkt).unwrap();
552        let coords: CoordSeq<Point2d> = linestring_sfcgal.to_coordinates().unwrap();
553        if let CoordSeq::Linestring(ref coords) = coords {
554            assert_ulps_eq!(coords[0].0, 3.5);
555            assert_ulps_eq!(coords[0].1, 5.6);
556            assert_ulps_eq!(coords[1].0, 4.8);
557            assert_ulps_eq!(coords[1].1, 10.5);
558        } else {
559            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
560        }
561        let line_sfcgal = coords.to_sfcgal().unwrap();
562        assert_eq!(input_wkt, line_sfcgal.to_wkt_decim(1).unwrap());
563    }
564
565    #[test]
566    fn linestring_3d_sfcgal_to_coordinates() {
567        let input_wkt = "LINESTRING Z (3.5 5.6 1.0,4.8 10.5 1.0)";
568        let linestring_sfcgal = SFCGeometry::new(input_wkt).unwrap();
569        let coords: CoordSeq<Point3d> = linestring_sfcgal.to_coordinates().unwrap();
570        if let CoordSeq::Linestring(ref coords) = coords {
571            assert_ulps_eq!(coords[0].0, 3.5);
572            assert_ulps_eq!(coords[0].1, 5.6);
573            assert_ulps_eq!(coords[0].2, 1.0);
574            assert_ulps_eq!(coords[1].0, 4.8);
575            assert_ulps_eq!(coords[1].1, 10.5);
576            assert_ulps_eq!(coords[1].2, 1.0);
577        } else {
578            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
579        }
580        let line_sfcgal = coords.to_sfcgal().unwrap();
581        assert_eq!(input_wkt, line_sfcgal.to_wkt_decim(1).unwrap());
582    }
583
584    #[test]
585    fn multilinestring_2d_sfcgal_to_coordinates() {
586        let input_wkt = "MULTILINESTRING ((3.5 5.6,4.8 10.5),(8.9 12.9,2.1 3.5),(1.1 4.8,6.2 7.5))";
587        let mls_sfcgal = SFCGeometry::new(input_wkt).unwrap();
588        let coords: CoordSeq<Point2d> = mls_sfcgal.to_coordinates().unwrap();
589        if let CoordSeq::Multilinestring(ref coords) = coords {
590            assert_ulps_eq!(coords[0][0].0, 3.5);
591            assert_ulps_eq!(coords[0][0].1, 5.6);
592            assert_ulps_eq!(coords[1][1].0, 2.1);
593            assert_ulps_eq!(coords[1][1].1, 3.5);
594            assert_ulps_eq!(coords[2][0].0, 1.1);
595            assert_ulps_eq!(coords[2][0].1, 4.8);
596        } else {
597            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
598        }
599        let mls_sfcgal = coords.to_sfcgal().unwrap();
600        assert_eq!(input_wkt, mls_sfcgal.to_wkt_decim(1).unwrap());
601    }
602
603    #[test]
604    fn multilinestring_3d_sfcgal_to_coordinates() {
605        let input_wkt = "MULTILINESTRING Z ((3.5 5.6 1.0,4.8 10.5 1.0),(8.9 12.9 4.0,2.1 3.5 4.0),(1.1 4.8 4.0,6.2 7.5 4.0))";
606        let multilinestring_sfcgal = SFCGeometry::new(input_wkt).unwrap();
607        let coords: CoordSeq<Point3d> = multilinestring_sfcgal.to_coordinates().unwrap();
608        if let CoordSeq::Multilinestring(ref coords) = coords {
609            assert_ulps_eq!(coords[0][0].0, 3.5);
610            assert_ulps_eq!(coords[0][0].1, 5.6);
611            assert_ulps_eq!(coords[0][0].2, 1.0);
612            assert_ulps_eq!(coords[1][1].0, 2.1);
613            assert_ulps_eq!(coords[1][1].1, 3.5);
614            assert_ulps_eq!(coords[1][1].2, 4.0);
615            assert_ulps_eq!(coords[2][0].0, 1.1);
616            assert_ulps_eq!(coords[2][0].1, 4.8);
617            assert_ulps_eq!(coords[2][0].2, 4.0);
618        } else {
619            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
620        }
621        let mls_sfcgal = coords.to_sfcgal().unwrap();
622        assert_eq!(input_wkt, mls_sfcgal.to_wkt_decim(1).unwrap());
623    }
624
625    #[test]
626    fn triangle_2d_sfcgal_to_coordinates_to_sfcgal() {
627        let input_wkt = "TRIANGLE ((0.0 1.0,1.0 0.0,1.0 1.0,0.0 1.0))";
628        let triangle_sfcgal = SFCGeometry::new(input_wkt).unwrap();
629        let coords: CoordSeq<Point2d> = triangle_sfcgal.to_coordinates().unwrap();
630        if let CoordSeq::Triangle(ref pts) = coords {
631            assert_ulps_eq!(pts[0].0, 0.0);
632            assert_ulps_eq!(pts[0].1, 1.0);
633            assert_ulps_eq!(pts[1].0, 1.0);
634            assert_ulps_eq!(pts[1].1, 0.0);
635            assert_ulps_eq!(pts[2].0, 1.0);
636            assert_ulps_eq!(pts[2].1, 1.0);
637        } else {
638            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
639        }
640        let triangle_sfcgal = coords.to_sfcgal().unwrap();
641        assert_eq!(input_wkt, triangle_sfcgal.to_wkt_decim(1).unwrap());
642    }
643
644    #[test]
645    fn solid_3d_sfcgal_to_coordinates() {
646        let input_wkt = "SOLID Z ((((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),\
647                         ((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),\
648                         ((0 0 0,1 0 0,1 0 1,0 0 1,0 0 0)),\
649                         ((1 0 0,1 1 0,1 1 1,1 0 1,1 0 0)),\
650                         ((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)),\
651                         ((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0))))";
652        let cube = SFCGeometry::new(input_wkt).unwrap();
653        let coords: CoordSeq<Point3d> = cube.to_coordinates().unwrap();
654        if let CoordSeq::Solid(ref polys) = coords {
655            let coords_1st_face_polygon = &polys[0][0];
656            assert_eq!(
657                format!("{:?}", coords_1st_face_polygon),
658                "[[(0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 1.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0)]]");
659            assert_eq!(polys[0].len(), 6usize);
660        } else {
661            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
662        }
663        let cube_sfcgal = coords.to_sfcgal().unwrap();
664        assert_eq!(input_wkt, cube_sfcgal.to_wkt_decim(0).unwrap());
665    }
666
667    #[test]
668    fn solid_with_interior_shell_3d_sfcgal_to_coordinates() {
669        let input_wkt = "SOLID Z ((\
670                         ((0.0 0.0 0.0,0.0 1.0 0.0,1.0 1.0 0.0,1.0 0.0 0.0,0.0 0.0 0.0)),\
671                         ((1.0 0.0 0.0,1.0 1.0 0.0,1.0 1.0 1.0,1.0 0.0 1.0,1.0 0.0 0.0)),\
672                         ((0.0 1.0 0.0,0.0 1.0 1.0,1.0 1.0 1.0,1.0 1.0 0.0,0.0 1.0 0.0)),\
673                         ((0.0 0.0 1.0,0.0 1.0 1.0,0.0 1.0 0.0,0.0 0.0 0.0,0.0 0.0 1.0)),\
674                         ((1.0 0.0 1.0,1.0 1.0 1.0,0.0 1.0 1.0,0.0 0.0 1.0,1.0 0.0 1.0)),\
675                         ((1.0 0.0 0.0,1.0 0.0 1.0,0.0 0.0 1.0,0.0 0.0 0.0,1.0 0.0 0.0))\
676                         ),(\
677                         ((0.0 0.0 0.0,0.0 0.5 0.0,0.5 0.5 0.0,0.5 0.0 0.0,0.0 0.0 0.0)),\
678                         ((0.5 0.0 0.0,0.5 0.5 0.0,0.5 0.5 0.5,0.5 0.0 0.5,0.5 0.0 0.0)),\
679                         ((0.0 0.5 0.0,0.0 0.5 0.5,0.5 0.5 0.5,0.5 0.5 0.0,0.0 0.5 0.0)),\
680                         ((0.0 0.0 0.5,0.0 0.5 0.5,0.0 0.5 0.0,0.0 0.0 0.0,0.0 0.0 0.5)),\
681                         ((0.5 0.0 0.5,0.5 0.5 0.5,0.0 0.5 0.5,0.0 0.0 0.5,0.5 0.0 0.5)),\
682                         ((0.5 0.0 0.0,0.5 0.0 0.5,0.0 0.0 0.5,0.0 0.0 0.0,0.5 0.0 0.0))\
683                         ))";
684        let cube = SFCGeometry::new(input_wkt).unwrap();
685        let coords: CoordSeq<Point3d> = cube.to_coordinates().unwrap();
686        if let CoordSeq::Solid(ref polys) = coords {
687            let num_shell = polys.len();
688            assert_eq!(num_shell, 2usize);
689            let coords_1st_face_polygon = &polys[0][0];
690            assert_eq!(
691                format!("{:?}", coords_1st_face_polygon),
692                "[[(0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0)]]");
693            assert_eq!(polys[0].len(), 6usize);
694        } else {
695            panic!("Unexpected coordinates when converting from SFCGAL to coordinates as tuples.")
696        }
697        let cube_sfcgal = coords.to_sfcgal().unwrap();
698        assert_eq!(input_wkt, cube_sfcgal.to_wkt_decim(1).unwrap());
699    }
700
701    #[test]
702    fn geometrycollection_3d_sfcgal_to_coordinates() {
703        let input_wkt =
704            "GEOMETRYCOLLECTION Z (POINT Z (4.0 6.0 1.0),LINESTRING Z (4.0 6.0 1.0,7.0 10.0 1.0))";
705        let gc_sfcgal = SFCGeometry::new(input_wkt).unwrap();
706        let coords: CoordSeq<Point3d> = gc_sfcgal.to_coordinates().unwrap();
707        if let CoordSeq::Geometrycollection(ref seq_coords) = coords {
708            if let CoordSeq::Point(ref coords) = &seq_coords[0] {
709                assert_ulps_eq!(coords.0, 4.0);
710                assert_ulps_eq!(coords.1, 6.0);
711                assert_ulps_eq!(coords.2, 1.0);
712            } else {
713                panic!("Error with first member of geometry collection");
714            }
715            if let CoordSeq::Linestring(ref coords) = &seq_coords[1] {
716                assert_ulps_eq!(coords[0].0, 4.0);
717                assert_ulps_eq!(coords[0].1, 6.0);
718                assert_ulps_eq!(coords[0].2, 1.0);
719                assert_ulps_eq!(coords[1].0, 7.0);
720                assert_ulps_eq!(coords[1].1, 10.0);
721                assert_ulps_eq!(coords[1].2, 1.0);
722            } else {
723                panic!("Error with second member of geometry collection");
724            }
725        }
726        let gc_sfcgal = coords.to_sfcgal().unwrap();
727        assert_eq!(input_wkt, gc_sfcgal.to_wkt_decim(1).unwrap());
728    }
729
730    #[test]
731    fn sfcgal_geometry_construction_from_coordinates() {
732        let coords_point = (1.1, 1.1);
733        let coords_multipoint = vec![(1.1, 1.1), (2.2, 2.2)];
734        let coords_linestring = vec![(1.1, 1.1), (2.2, 2.2)];
735        let coords_multilinestring = vec![
736            vec![(1.1, 1.1), (2.2, 2.2)],
737            vec![(3.1, 3.1), (5.2, 5.2), (5.2, 5.2)],
738            vec![(1.1, 1.1), (2.2, 2.2), (5.2, 5.2)],
739        ];
740        let coords_polygon = vec![
741            vec![(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)], // Exterior ring
742            vec![(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)], // 1 interior ring
743        ];
744        let coords_multipolygon = vec![
745            vec![
746                // First polygon
747                vec![(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)], // Exterior ring
748                vec![(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)], // 1 interior ring
749            ],
750            vec![
751                // second polygon
752                vec![(2., 2.), (6., 2.), (6., 6.), (2., 6.), (2., 2.)], // Exterior ring
753            ],
754        ];
755        let pt_sfcgal = CoordSeq::Point(coords_point).to_sfcgal().unwrap();
756        let multipt_sfcgal = CoordSeq::Multipoint(coords_multipoint).to_sfcgal().unwrap();
757        let line_sfcgal = CoordSeq::Linestring(coords_linestring).to_sfcgal().unwrap();
758        let multiline_sfcgal = CoordSeq::Multilinestring(coords_multilinestring)
759            .to_sfcgal()
760            .unwrap();
761        let polygon_sfcgal = CoordSeq::Polygon(coords_polygon).to_sfcgal().unwrap();
762        let multipolygon_sfcgal = CoordSeq::Multipolygon(coords_multipolygon)
763            .to_sfcgal()
764            .unwrap();
765        assert_eq!("POINT (1.1 1.1)", pt_sfcgal.to_wkt_decim(1).unwrap());
766        assert_eq!(
767            "MULTIPOINT ((1.1 1.1),(2.2 2.2))",
768            multipt_sfcgal.to_wkt_decim(1).unwrap()
769        );
770        assert_eq!(
771            "LINESTRING (1.1 1.1,2.2 2.2)",
772            line_sfcgal.to_wkt_decim(1).unwrap()
773        );
774        assert_eq!(
775            "MULTILINESTRING ((1.1 1.1,2.2 2.2),\
776             (3.1 3.1,5.2 5.2,5.2 5.2),\
777             (1.1 1.1,2.2 2.2,5.2 5.2))",
778            multiline_sfcgal.to_wkt_decim(1).unwrap(),
779        );
780        assert_eq!(
781            "POLYGON ((0.0 0.0,1.0 0.0,1.0 1.0,0.0 1.0,0.0 0.0),\
782             (0.1 0.1,0.1 0.9,0.9 0.9,0.9 0.1,0.1 0.1))",
783            polygon_sfcgal.to_wkt_decim(1).unwrap(),
784        );
785        assert_eq!(
786            "MULTIPOLYGON (((0.0 0.0,1.0 0.0,1.0 1.0,0.0 1.0,0.0 0.0),\
787             (0.1 0.1,0.1 0.9,0.9 0.9,0.9 0.1,0.1 0.1)),\
788             ((2.0 2.0,6.0 2.0,6.0 6.0,2.0 6.0,2.0 2.0)))",
789            multipolygon_sfcgal.to_wkt_decim(1).unwrap(),
790        );
791    }
792
793    #[test]
794    fn sfcgal_geometry_construction_from_coordinates_3d() {
795        let coords_point = (1.1, 1.1, 1.1);
796        let coords_multipoint = vec![(1.1, 1.1, 5.0), (2.2, 2.2, 5.0)];
797        let coords_linestring = vec![(1.1, 1.1, 5.0), (2.2, 2.2, 5.0)];
798        let coords_multilinestring = vec![
799            vec![(1.1, 1.1, 3.0), (2.2, 2.2, 3.0)],
800            vec![(3.1, 3.1, 3.0), (5.2, 5.2, 3.0), (5.2, 5.2, 3.0)],
801            vec![(1.1, 1.1, 3.0), (2.2, 2.2, 3.0), (5.2, 5.2, 3.0)],
802        ];
803        let coords_polygon = vec![
804            vec![
805                (0., 0., 3.0),
806                (1., 0., 3.0),
807                (1., 1., 3.0),
808                (0., 1., 3.0),
809                (0., 0., 3.0),
810            ], // Exterior ring
811            vec![
812                (0.1, 0.1, 3.0),
813                (0.1, 0.9, 3.0),
814                (0.9, 0.9, 3.0),
815                (0.9, 0.1, 3.0),
816                (0.1, 0.1, 3.0),
817            ], // 1 interior ring
818        ];
819        let coords_multipolygon = vec![
820            vec![
821                // First polygon
822                vec![
823                    (0., 0., 3.0),
824                    (1., 0., 3.0),
825                    (1., 1., 3.0),
826                    (0., 1., 3.0),
827                    (0., 0., 3.0),
828                ], // Exterior ring
829                vec![
830                    (0.1, 0.1, 3.0),
831                    (0.1, 0.9, 3.0),
832                    (0.9, 0.9, 3.0),
833                    (0.9, 0.1, 3.0),
834                    (0.1, 0.1, 3.0),
835                ], // 1 interior ring
836            ],
837            vec![
838                // second polygon
839                vec![
840                    (2., 2., 3.),
841                    (6., 2., 3.),
842                    (6., 6., 3.),
843                    (2., 6., 3.),
844                    (2., 2., 3.),
845                ], // Exterior ring
846            ],
847        ];
848        let pt_sfcgal = CoordSeq::Point(coords_point).to_sfcgal().unwrap();
849        let multipt_sfcgal = CoordSeq::Multipoint(coords_multipoint).to_sfcgal().unwrap();
850        let line_sfcgal = CoordSeq::Linestring(coords_linestring).to_sfcgal().unwrap();
851        let multiline_sfcgal = CoordSeq::Multilinestring(coords_multilinestring)
852            .to_sfcgal()
853            .unwrap();
854        let polygon_sfcgal = CoordSeq::Polygon(coords_polygon).to_sfcgal().unwrap();
855        let multipolygon_sfcgal = CoordSeq::Multipolygon(coords_multipolygon)
856            .to_sfcgal()
857            .unwrap();
858        assert_eq!("POINT Z (1.1 1.1 1.1)", pt_sfcgal.to_wkt_decim(1).unwrap());
859        assert_eq!(
860            "MULTIPOINT Z ((1.1 1.1 5.0),(2.2 2.2 5.0))",
861            multipt_sfcgal.to_wkt_decim(1).unwrap()
862        );
863        assert_eq!(
864            "LINESTRING Z (1.1 1.1 5.0,2.2 2.2 5.0)",
865            line_sfcgal.to_wkt_decim(1).unwrap()
866        );
867        assert_eq!(
868            "MULTILINESTRING Z ((1.1 1.1 3.0,2.2 2.2 3.0),(3.1 3.1 3.0,5.2 5.2 3.0,5.2 5.2 3.0),\
869             (1.1 1.1 3.0,2.2 2.2 3.0,5.2 5.2 3.0))",
870            multiline_sfcgal.to_wkt_decim(1).unwrap(),
871        );
872        assert_eq!(
873            "POLYGON Z ((0.0 0.0 3.0,1.0 0.0 3.0,1.0 1.0 3.0,0.0 1.0 3.0,0.0 0.0 3.0),\
874             (0.1 0.1 3.0,0.1 0.9 3.0,0.9 0.9 3.0,0.9 0.1 3.0,0.1 0.1 3.0))",
875            polygon_sfcgal.to_wkt_decim(1).unwrap(),
876        );
877        assert_eq!(
878            "MULTIPOLYGON Z (((0.0 0.0 3.0,1.0 0.0 3.0,1.0 1.0 3.0,0.0 1.0 3.0,0.0 0.0 3.0),\
879             (0.1 0.1 3.0,0.1 0.9 3.0,0.9 0.9 3.0,0.9 0.1 3.0,0.1 0.1 3.0)),\
880             ((2.0 2.0 3.0,6.0 2.0 3.0,6.0 6.0 3.0,2.0 6.0 3.0,2.0 2.0 3.0)))",
881            multipolygon_sfcgal.to_wkt_decim(1).unwrap(),
882        );
883    }
884}