Skip to main content

ogc_cql2/geom/
collection.rs

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