1#![warn(missing_docs)]
4
5use 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#[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 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, *config().default_srid())
62 }
63
64 pub(crate) fn from_xy_and_srid(coord: XY1V, srid: SRID) -> Self {
65 let coord = Self::ensure_precision_xy(&coord);
69 Point { coord, srid }
70 }
71
72 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 pub(crate) fn x(&self) -> f64 {
107 self.coord[0]
108 }
109
110 pub(crate) fn y(&self) -> f64 {
112 self.coord[1]
113 }
114
115 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 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 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 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}