1#![warn(missing_docs)]
4
5use 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#[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 pub fn num_rings(&self) -> usize {
55 self.rings.len()
56 }
57
58 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, SRID::default())
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.as_usize()?;
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 let n = u32::try_from(num_inners)?;
127 for ndx in 0..n {
128 let inner = gg.get_interior_ring_n(ndx)?;
129 let xy = Line::from_geos_xy(inner)?;
130 result.push(xy);
131 }
132
133 Ok(result)
134 }
135
136 pub(crate) fn set_srid_unchecked(&mut self, srid: &SRID) {
137 if self.srid != *srid {
138 warn!("Replacing current SRID ({}) w/ {srid}", self.srid);
139 self.srid = srid.to_owned();
140 }
141 }
142
143 #[cfg(test)]
144 fn outer_as_ring(&self) -> Line {
145 Line::from_xy(self.rings[0].to_vec())
146 }
147
148 #[cfg(test)]
151 fn has_holes(&self) -> bool {
152 self.rings.len() > 1
153 }
154
155 #[cfg(test)]
157 fn inners(&self) -> &[Vec<Vec<f64>>] {
158 &self.rings.as_slice()[1..]
159 }
160}
161
162impl fmt::Display for Polygon {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "Polygon (...)")
165 }
166}
167
168impl TryFrom<Geometry> for Polygon {
169 type Error = MyError;
170
171 fn try_from(value: Geometry) -> Result<Self, Self::Error> {
172 let srs_id = value.get_srid().unwrap_or_else(|x| {
173 error!(
174 "Failed get_srid for GEOS Polygon. Will use Undefined: {}",
175 x
176 );
177 Default::default()
178 });
179 let rings = Self::from_geos_xy(value)?;
180 let srid = SRID::try_from(srs_id)?;
181 Ok(Polygon::from_xy_and_srid(rings, srid))
182 }
183}
184
185impl TryFrom<ConstGeometry<'_>> for Polygon {
186 type Error = MyError;
187
188 fn try_from(value: ConstGeometry) -> Result<Self, Self::Error> {
189 let srs_id = value.get_srid().unwrap_or_else(|x| {
190 error!(
191 "Failed get_srid for GEOS Polygon. Will use Undefined: {}",
192 x
193 );
194 Default::default()
195 });
196 let rings = Self::from_geos_xy(value)?;
197 let srid = SRID::try_from(srs_id)?;
198 Ok(Polygon::from_xy_and_srid(rings, srid))
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::{G, expr::E, text::cql2};
206 use geos::Geom;
207 use std::error::Error;
208
209 #[test]
210 #[tracing_test::traced_test]
211 fn test_2d() {
212 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))"#;
213
214 let exp = cql2::geom_expression(G);
215 assert!(exp.is_ok());
216 let spa = exp.unwrap();
217 let g = match spa {
218 E::Spatial(G::Polygon(x)) => x,
219 _ => panic!("Not a Polygon..."),
220 };
221 assert_eq!(g.is_2d(), true);
222
223 let outer_ring = g.outer_as_ring();
224 assert!(outer_ring.is_ring());
225 assert!(outer_ring.is_closed());
226 assert_eq!(outer_ring.num_points(), 7);
227
228 assert!(!g.has_holes());
230 assert!(g.inners().is_empty());
231 }
232
233 #[test]
234 #[tracing_test::traced_test]
235 fn test_3d() {
236 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))"#;
237
238 let exp = cql2::geom_expression(G);
239 assert!(exp.is_ok());
240 let spa = exp.unwrap();
241 let g = match spa {
242 E::Spatial(G::Polygon(x)) => x,
243 _ => panic!("Not a Polygon..."),
244 };
245 assert_eq!(g.is_2d(), false);
246
247 let outer_ring = g.outer_as_ring();
248 assert!(outer_ring.is_ring());
249 assert!(outer_ring.is_closed());
250 assert_eq!(outer_ring.num_points(), 4);
251
252 assert!(g.has_holes());
254 assert_eq!(g.inners().len(), 1);
255 }
256
257 #[test]
258 #[tracing_test::traced_test]
259 fn test_touches() -> Result<(), Box<dyn Error>> {
260 const WKT1: &str = "POLYGON ((0 -90, 0 0, 180 0, 180 -90, 0 -90))";
261 const WKT2: &str = "POLYGON ((-180 -90, -180 90, 180 90, 180 -90, -180 -90))";
262
263 let p1 = Geometry::new_from_wkt(WKT1)?;
264 let p2 = Geometry::new_from_wkt(WKT2)?;
265
266 assert!(!p1.touches(&p2)?);
270
271 Ok(())
272 }
273}