Skip to main content

ogc_cql2/geom/
polygons.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![warn(missing_docs)]
4
5//! Collection of polygon geometries.
6//!
7
8use crate::{
9    CRS, GTrait, MyError, Polygon,
10    config::config,
11    geom::{XY3V, XY4V},
12    srid::SRID,
13};
14use core::fmt;
15use geos::{ConstGeometry, Geom, Geometry};
16use std::slice::Iter;
17use tracing::{error, warn};
18
19/// Collection of polygon geometries.
20#[derive(Debug, Clone, PartialEq, PartialOrd)]
21pub struct Polygons {
22    polygons: XY4V,
23    srid: SRID,
24}
25
26impl GTrait for Polygons {
27    fn is_2d(&self) -> bool {
28        self.polygons[0][0][0].len() == 2
29    }
30
31    fn to_wkt_fmt(&self, precision: usize) -> String {
32        if self.is_2d() {
33            format!(
34                "MULTIPOLYGON {}",
35                Self::coords_with_dp(self.polygons.as_slice(), precision)
36            )
37        } else {
38            format!(
39                "MULTIPOLYGON Z {}",
40                Self::coords_with_dp(self.polygons.as_slice(), precision)
41            )
42        }
43    }
44
45    fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
46        if self
47            .polygons
48            .iter()
49            .all(|poly| crs.check_polygon(poly).is_ok())
50        {
51            Ok(())
52        } else {
53            Err(MyError::Runtime(
54                "At least one polygon has invalid coordinates".into(),
55            ))
56        }
57    }
58
59    fn type_(&self) -> &str {
60        "MultiPolygon"
61    }
62
63    fn srid(&self) -> SRID {
64        self.srid
65    }
66}
67
68impl Polygons {
69    /// Return the number of polygons in this.
70    pub fn num_polygons(&self) -> usize {
71        self.polygons.len()
72    }
73
74    /// Return an iterator over this polygons' coordinates.
75    pub fn polygons(&self) -> Iter<'_, XY3V> {
76        self.polygons.iter()
77    }
78
79    pub(crate) fn from_xy(polygons: XY4V) -> Self {
80        Self::from_xy_and_srid(polygons, *config().default_srid())
81    }
82
83    pub(crate) fn from_xy_and_srid(polygons: XY4V, srid: SRID) -> Self {
84        let polygons = polygons
85            .iter()
86            .map(|x| Polygon::ensure_precision_xy(x))
87            .collect();
88        Polygons { polygons, srid }
89    }
90
91    pub(crate) fn coords_as_txt(polygons: &[XY3V]) -> String {
92        Self::coords_with_dp(polygons, config().default_precision())
93    }
94
95    pub(crate) fn to_geos(&self) -> Result<Geometry, MyError> {
96        let mut polygons: Vec<Geometry> = vec![];
97        for p in &self.polygons {
98            let g = Polygon::to_geos_xy(p, &self.srid)?;
99            polygons.push(g);
100        }
101        let mut g = Geometry::create_multipolygon(polygons)?;
102        let srs_id = self.srid.as_usize()?;
103        g.set_srid(srs_id);
104
105        Ok(g)
106    }
107
108    pub(crate) fn from_geos_xy<T: Geom>(gg: T) -> Result<XY4V, MyError> {
109        let num_polygons = gg.get_num_geometries()?;
110        let mut result = Vec::with_capacity(num_polygons);
111        for ndx in 0..num_polygons {
112            let polygon = gg.get_geometry_n(ndx)?;
113            let xy = Polygon::from_geos_xy(polygon)?;
114            result.push(xy);
115        }
116        Ok(result)
117    }
118
119    pub(crate) fn set_srid_unchecked(&mut self, srid: &SRID) {
120        if self.srid != *srid {
121            warn!("Replacing current SRID ({}) w/ {srid}", self.srid);
122            self.srid = srid.to_owned();
123        }
124    }
125
126    fn coords_with_dp(polygons: &[XY3V], precision: usize) -> String {
127        let polygons: Vec<String> = polygons
128            .iter()
129            .map(|x| Polygon::coords_with_dp(x.as_slice(), precision))
130            .collect();
131        format!("({})", polygons.join(", "))
132    }
133}
134
135impl fmt::Display for Polygons {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
137        write!(f, "Polygons (...)")
138    }
139}
140
141impl TryFrom<Geometry> for Polygons {
142    type Error = MyError;
143
144    fn try_from(value: Geometry) -> Result<Self, Self::Error> {
145        let srs_id = value.get_srid().unwrap_or_else(|x| {
146            error!(
147                "Failed get_srid for GEOS MultiPolygon. Will use Undefined: {}",
148                x
149            );
150            Default::default()
151        });
152        let polygons = Polygons::from_geos_xy(value)?;
153        let srid = SRID::try_from(srs_id)?;
154        Ok(Polygons::from_xy_and_srid(polygons, srid))
155    }
156}
157
158impl TryFrom<ConstGeometry<'_>> for Polygons {
159    type Error = MyError;
160
161    fn try_from(value: ConstGeometry) -> Result<Self, Self::Error> {
162        let srs_id = value.get_srid().unwrap_or_else(|x| {
163            error!(
164                "Failed get_srid for GEOS MultiPolygon. Will use Undefined: {}",
165                x
166            );
167            Default::default()
168        });
169        let polygons = Polygons::from_geos_xy(value)?;
170        let srid = SRID::try_from(srs_id)?;
171        Ok(Polygons::from_xy_and_srid(polygons, srid))
172    }
173}