Skip to main content

ogc_cql2/geom/
points.rs

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