ogc_cql2/geom/
point.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![warn(missing_docs)]
4
5//! Point geometry.
6//!
7
8use crate::{
9    CRS, GTrait, MyError,
10    config::config,
11    geom::{XY1V, ensure_precision},
12    srid::SRID,
13};
14use core::fmt;
15use geos::{ConstGeometry, CoordSeq, Geom, Geometry};
16use tracing::{error, warn};
17
18/// 2D or 3D point geometry.
19#[derive(Debug, Clone, PartialEq, PartialOrd)]
20pub struct Point {
21    coord: XY1V,
22    srid: SRID,
23}
24
25impl GTrait for Point {
26    fn is_2d(&self) -> bool {
27        self.coord.len() == 2
28    }
29
30    fn to_wkt_fmt(&self, precision: usize) -> String {
31        if self.is_2d() {
32            format!("POINT ({})", Self::coords_with_dp(self.as_2d(), precision))
33        } else {
34            format!(
35                "POINT Z ({})",
36                Self::coords_with_dp(self.as_3d(), precision)
37            )
38        }
39    }
40
41    fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
42        crs.check_point(&self.coord)
43    }
44
45    fn type_(&self) -> &str {
46        "Point"
47    }
48
49    fn srid(&self) -> SRID {
50        self.srid
51    }
52}
53
54impl Point {
55    /// Return a reference to the point's coordinates.
56    pub fn xy(&self) -> &Vec<f64> {
57        &self.coord
58    }
59
60    pub(crate) fn from_xy(coord: XY1V) -> Self {
61        Self::from_xy_and_srid(coord, SRID::default())
62    }
63
64    pub(crate) fn from_xy_and_srid(coord: XY1V, srid: SRID) -> Self {
65        // shape the input coordinates to a fixed precision; i.e. a fixed
66        // number of decimals so `geos` can reliably assert equality of
67        // coordinate values.
68        let coord = Self::ensure_precision_xy(&coord);
69        Point { coord, srid }
70    }
71
72    // Output given coordinates sequentially seperated by a space.
73    pub(crate) fn coords_as_txt(coord: &[f64]) -> String {
74        Self::coords_with_dp(coord, config().default_precision())
75    }
76
77    pub(crate) fn ensure_precision_xy(coord: &[f64]) -> XY1V {
78        coord.iter().map(ensure_precision).collect()
79    }
80
81    pub(crate) fn coords_with_dp(coord: &[f64], precision: usize) -> String {
82        if coord.len() == 2 {
83            format!("{:.2$} {:.2$}", coord[0], coord[1], precision)
84        } else {
85            format!(
86                "{:.3$} {:.3$} {:.3$}",
87                coord[0], coord[1], coord[2], precision
88            )
89        }
90    }
91
92    pub(crate) fn to_geos(&self) -> Result<Geometry, MyError> {
93        Self::to_geos_xy(&self.coord, &self.srid)
94    }
95
96    pub(crate) fn to_geos_xy(xy: &[f64], srid: &SRID) -> Result<Geometry, MyError> {
97        let xy = CoordSeq::new_from_vec(&[xy])?;
98        let mut g = Geometry::create_point(xy)?;
99        let srs_id = srid.as_usize()?;
100        g.set_srid(srs_id);
101
102        Ok(g)
103    }
104
105    // Return the 1st coordinate of this.
106    pub(crate) fn x(&self) -> f64 {
107        self.coord[0]
108    }
109
110    // Return the 2nd coordinate of this.
111    pub(crate) fn y(&self) -> f64 {
112        self.coord[1]
113    }
114
115    // Return the 3rd coordinate of this if it's a 3D one. Return `None` otherwise.
116    pub(crate) fn z(&self) -> Option<f64> {
117        if self.coord.len() == 2 {
118            None
119        } else {
120            Some(self.coord[2])
121        }
122    }
123
124    pub(crate) fn from_geos_xy<T: Geom>(gg: T) -> Result<XY1V, MyError> {
125        let result = if gg.has_z()? {
126            vec![gg.get_x()?, gg.get_y()?, gg.get_z()?]
127        } else {
128            vec![gg.get_x()?, gg.get_y()?]
129        };
130        Ok(result)
131    }
132
133    pub(crate) fn set_srid_unchecked(&mut self, srid: &SRID) {
134        if self.srid != *srid {
135            warn!("Replacing current SRID ({}) w/ {srid}", self.srid);
136            self.srid = srid.to_owned();
137        }
138    }
139
140    // Return the 2D coordinates of this point.
141    fn as_2d(&self) -> &[f64; 2] {
142        self.coord
143            .as_slice()
144            .try_into()
145            .expect("Failed coercing Point to 2D")
146    }
147
148    // Return the 3D coordinates of this point.
149    fn as_3d(&self) -> &[f64; 3] {
150        self.coord
151            .as_slice()
152            .try_into()
153            .expect("Failed coercing Point to 3D")
154    }
155}
156
157impl fmt::Display for Point {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
159        write!(f, "Point (...)")
160    }
161}
162
163impl TryFrom<Geometry> for Point {
164    type Error = MyError;
165
166    fn try_from(value: Geometry) -> Result<Self, Self::Error> {
167        let srs_id = value.get_srid().unwrap_or_else(|x| {
168            error!("Failed get_srid for GEOS Point. Will use Undefined: {}", x);
169            Default::default()
170        });
171        let xy = Point::from_geos_xy(value)?;
172        let srid = SRID::try_from(srs_id)?;
173        Ok(Point::from_xy_and_srid(xy, srid))
174    }
175}
176
177impl TryFrom<ConstGeometry<'_>> for Point {
178    type Error = MyError;
179
180    fn try_from(value: ConstGeometry) -> Result<Self, Self::Error> {
181        let srs_id = value.get_srid().unwrap_or_else(|x| {
182            error!("Failed get_srid for GEOS Point. Will use Undefined: {}", x);
183            Default::default()
184        });
185        let xy = Point::from_geos_xy(value)?;
186        let srid = SRID::try_from(srs_id)?;
187        Ok(Point::from_xy_and_srid(xy, srid))
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use crate::{G, expr::E, text::cql2};
195    use approx::assert_relative_eq;
196    use std::error::Error;
197
198    const TOLERANCE: f64 = 1.0E-3;
199
200    #[test]
201    fn test_equality() {
202        let p1 = Point {
203            coord: vec![1., 1.],
204            srid: SRID::default(),
205        };
206        let p2 = Point {
207            coord: vec![1.0, 1.0],
208            srid: SRID::default(),
209        };
210        let p3 = Point {
211            coord: vec![1.0, 1.1],
212            srid: SRID::default(),
213        };
214        let p4 = Point {
215            coord: vec![1.1, 1.0],
216            srid: SRID::default(),
217        };
218
219        assert_eq!(p1, p2);
220        assert_ne!(p1, p3);
221        assert_ne!(p1, p4);
222        assert!(p1 == p2);
223        assert!(p1 != p3);
224        assert!(p2 != p4);
225        assert!(p3 != p4);
226    }
227
228    #[test]
229    fn test_comparison() {
230        let p1 = Point {
231            coord: vec![1.0, 1.0],
232            srid: SRID::default(),
233        };
234        let p2 = Point {
235            coord: vec![1.0, 1.1],
236            srid: SRID::default(),
237        };
238        let p3 = Point {
239            coord: vec![1.1, 1.0],
240            srid: SRID::default(),
241        };
242
243        assert!(p1 < p2);
244        assert!(p1 < p3);
245        assert!(p2 < p3);
246    }
247
248    #[test]
249    #[tracing_test::traced_test]
250    fn test() {
251        const G: &str = r#"point (-3.508362 -1.754181)"#;
252
253        let exp = cql2::geom_expression(G);
254        assert!(exp.is_ok());
255        let spa = exp.unwrap();
256        let g = match spa {
257            E::Spatial(G::Point(x)) => x,
258            _ => panic!("Not a Point..."),
259        };
260        assert_eq!(g.is_2d(), true);
261        // or...
262        assert!(g.z().is_none());
263        assert_relative_eq!(g.x(), -3.508, epsilon = TOLERANCE);
264        assert_relative_eq!(g.y(), -1.754, epsilon = TOLERANCE);
265    }
266
267    #[test]
268    #[should_panic]
269    fn test_invalid() {
270        let pt = Point::from_xy(vec![90.0, 180.0]);
271        let crs = CRS::default();
272        pt.check_coordinates(&crs).unwrap();
273    }
274
275    #[test]
276    fn test_precision() -> Result<(), Box<dyn Error>> {
277        const XYZ: [f64; 3] = [-16.0671326636424, -17.012041674368, 179.096609362997];
278        const WKT: &str = "POINT Z (-16.067133 -17.012042 179.096609)";
279
280        let pt = Point::from_xy(XYZ.to_vec());
281        let wkt = pt.to_wkt_fmt(6);
282        assert_eq!(wkt, WKT);
283
284        Ok(())
285    }
286}