Skip to main content

ogc_cql2/geom/
polygon.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![warn(missing_docs)]
4
5//! Polygon geometry.
6//!
7
8use crate::{
9    CRS, GTrait, Line, MyError,
10    config::config,
11    geom::{XY2V, XY3V},
12    srid::SRID,
13};
14use core::fmt;
15use geos::{ConstGeometry, CoordSeq, Geom, Geometry};
16use std::slice::Iter;
17use tracing::{error, warn};
18
19/// 2D or 3D polygon geometry.
20#[derive(Debug, Clone, PartialEq, PartialOrd)]
21pub struct Polygon {
22    pub(crate) rings: XY3V,
23    srid: SRID,
24}
25
26impl GTrait for Polygon {
27    fn is_2d(&self) -> bool {
28        self.rings[0][0].len() == 2
29    }
30
31    fn to_wkt_fmt(&self, precision: usize) -> String {
32        if self.is_2d() {
33            format!("POLYGON {}", Self::coords_with_dp(&self.rings, precision))
34        } else {
35            format!("POLYGON Z {}", Self::coords_with_dp(&self.rings, precision))
36        }
37    }
38
39    fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
40        crs.check_polygon(&self.rings)
41    }
42
43    fn type_(&self) -> &str {
44        "Polygon"
45    }
46
47    fn srid(&self) -> SRID {
48        self.srid
49    }
50}
51
52impl Polygon {
53    /// Return the number of rings in this.
54    pub fn num_rings(&self) -> usize {
55        self.rings.len()
56    }
57
58    /// Return an iterator over the rings' coordinates.
59    pub fn rings(&self) -> Iter<'_, XY2V> {
60        self.rings.iter()
61    }
62
63    pub(crate) fn from_xy(rings: XY3V) -> Self {
64        Self::from_xy_and_srid(rings, *config().default_srid())
65    }
66
67    pub(crate) fn from_xy_and_srid(rings: XY3V, srid: SRID) -> Self {
68        let rings = Self::ensure_precision_xy(&rings);
69        Self::from_xy_and_srid_unchecked(rings, srid)
70    }
71
72    pub(crate) fn from_xy_and_srid_unchecked(rings: XY3V, srid: SRID) -> Self {
73        Polygon { rings, srid }
74    }
75
76    pub(crate) fn coords_as_txt(rings: &[XY2V]) -> String {
77        Self::coords_with_dp(rings, config().default_precision())
78    }
79
80    pub(crate) fn ensure_precision_xy(rings: &[XY2V]) -> XY3V {
81        rings.iter().map(|r| Line::ensure_precision_xy(r)).collect()
82    }
83
84    pub(crate) fn coords_with_dp(rings: &[XY2V], precision: usize) -> String {
85        let rings: Vec<String> = rings
86            .iter()
87            .map(|x| Line::coords_with_dp(x, precision))
88            .collect();
89        format!("({})", rings.join(", "))
90    }
91
92    pub(crate) fn to_geos(&self) -> Result<Geometry, MyError> {
93        Self::to_geos_xy(&self.rings, &self.srid)
94    }
95
96    pub(crate) fn to_geos_xy(rings: &[XY2V], srid: &SRID) -> Result<Geometry, MyError> {
97        let vertices: Vec<&[f64]> = rings[0].iter().map(|x| x.as_slice()).collect();
98        let xy = CoordSeq::new_from_vec(&vertices)?;
99        let mut exterior = Geometry::create_linear_ring(xy)?;
100        let srs_id = srid.into_inner();
101        exterior.set_srid(srs_id);
102
103        let mut interiors = vec![];
104        for hole in &rings[1..] {
105            let vertices: Vec<&[f64]> = hole.iter().map(|x| x.as_slice()).collect();
106            let xy = CoordSeq::new_from_vec(&vertices)?;
107            let mut hole = Geometry::create_linear_ring(xy)?;
108            hole.set_srid(srs_id);
109            interiors.push(hole);
110        }
111
112        let mut g = Geometry::create_polygon(exterior, interiors)?;
113        g.set_srid(srs_id);
114
115        Ok(g)
116    }
117
118    pub(crate) fn from_geos_xy<T: Geom>(gg: T) -> Result<XY3V, MyError> {
119        let num_inners = gg.get_num_interior_rings()?;
120        let mut result = Vec::with_capacity(num_inners + 1);
121
122        let outer = gg.get_exterior_ring()?;
123        let xy = Line::from_geos_xy(outer)?;
124        result.push(xy);
125
126        for ndx in 0..num_inners {
127            let inner = gg.get_interior_ring_n(ndx)?;
128            let xy = Line::from_geos_xy(inner)?;
129            result.push(xy);
130        }
131
132        Ok(result)
133    }
134
135    pub(crate) fn set_srid_unchecked(&mut self, srid: &SRID) {
136        if self.srid != *srid {
137            warn!("Replacing current SRID ({}) w/ {srid}", self.srid);
138            self.srid = srid.to_owned();
139        }
140    }
141
142    #[cfg(test)]
143    fn outer_as_ring(&self) -> Line {
144        Line::from_xy(self.rings[0].to_vec())
145    }
146
147    // Return TRUE if this has holes; i.e. more than 1 linear ring. Return
148    // FALSE otherwise.
149    #[cfg(test)]
150    fn has_holes(&self) -> bool {
151        self.rings.len() > 1
152    }
153
154    // Return the array of inner (holes) linear rings of this.
155    #[cfg(test)]
156    fn inners(&self) -> &[Vec<Vec<f64>>] {
157        &self.rings.as_slice()[1..]
158    }
159}
160
161impl fmt::Display for Polygon {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
163        write!(f, "Polygon (...)")
164    }
165}
166
167impl TryFrom<Geometry> for Polygon {
168    type Error = MyError;
169
170    fn try_from(value: Geometry) -> Result<Self, Self::Error> {
171        let srs_id = value.get_srid().unwrap_or_else(|x| {
172            error!(
173                "Failed get_srid for GEOS Polygon. Will use Undefined: {}",
174                x
175            );
176            Default::default()
177        });
178        let rings = Self::from_geos_xy(value)?;
179        let srid = SRID::try_from(srs_id)?;
180        Ok(Polygon::from_xy_and_srid(rings, srid))
181    }
182}
183
184impl TryFrom<ConstGeometry<'_>> for Polygon {
185    type Error = MyError;
186
187    fn try_from(value: ConstGeometry) -> Result<Self, Self::Error> {
188        let srs_id = value.get_srid().unwrap_or_else(|x| {
189            error!(
190                "Failed get_srid for GEOS Polygon. Will use Undefined: {}",
191                x
192            );
193            Default::default()
194        });
195        let rings = Self::from_geos_xy(value)?;
196        let srid = SRID::try_from(srs_id)?;
197        Ok(Polygon::from_xy_and_srid(rings, srid))
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::{G, expr::E, text::cql2};
205    use geos::Geom;
206    use std::error::Error;
207
208    #[test]
209    #[tracing_test::traced_test]
210    fn test_2d() {
211        const G: &str = r#"PolyGon ((-0.333333 89.0, -102.723546 -0.5, -179.0 -89.0, -1.9 89.0, -0.0 89.0, 2.00001 -1.9, -0.333333 89.0))"#;
212
213        let exp = cql2::geom_expression(G);
214        assert!(exp.is_ok());
215        let spa = exp.unwrap();
216        let g = match spa {
217            E::Spatial(G::Polygon(x)) => x,
218            _ => panic!("Not a Polygon..."),
219        };
220        assert_eq!(g.is_2d(), true);
221
222        let outer_ring = g.outer_as_ring();
223        assert!(outer_ring.is_ring());
224        assert!(outer_ring.is_closed());
225        assert_eq!(outer_ring.num_points(), 7);
226
227        // has no holes...
228        assert!(!g.has_holes());
229        assert!(g.inners().is_empty());
230    }
231
232    #[test]
233    #[tracing_test::traced_test]
234    fn test_3d() {
235        const G: &str = r#"POLYGON Z ((-49.88024 0.5 -75993.341684, -1.5 -0.99999 -100000.0, 0.0 0.5 -0.333333, -49.88024 0.5 -75993.341684), (-65.887123 2.00001 -100000.0, 0.333333 -53.017711 -79471.332949, 180.0 0.0 1852.616704, -65.887123 2.00001 -100000.0))"#;
236
237        let exp = cql2::geom_expression(G);
238        assert!(exp.is_ok());
239        let spa = exp.unwrap();
240        let g = match spa {
241            E::Spatial(G::Polygon(x)) => x,
242            _ => panic!("Not a Polygon..."),
243        };
244        assert_eq!(g.is_2d(), false);
245
246        let outer_ring = g.outer_as_ring();
247        assert!(outer_ring.is_ring());
248        assert!(outer_ring.is_closed());
249        assert_eq!(outer_ring.num_points(), 4);
250
251        // has 1 hole...
252        assert!(g.has_holes());
253        assert_eq!(g.inners().len(), 1);
254    }
255
256    #[test]
257    #[tracing_test::traced_test]
258    fn test_touches() -> Result<(), Box<dyn Error>> {
259        const WKT1: &str = "POLYGON ((0 -90, 0 0, 180 0, 180 -90, 0 -90))";
260        const WKT2: &str = "POLYGON ((-180 -90, -180 90, 180 90, 180 -90, -180 -90))";
261
262        let p1 = Geometry::new_from_wkt(WKT1)?;
263        let p2 = Geometry::new_from_wkt(WKT2)?;
264
265        // although p1 and p2 share a segment of their bottom side, their
266        // interiors are NOT disjoint and as such they are considered to
267        // not "touch" each other.
268        assert!(!p1.touches(&p2)?);
269
270        Ok(())
271    }
272}