1#![warn(missing_docs)]
4
5use 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#[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 pub fn num_points(&self) -> usize {
67 self.points.len()
68 }
69
70 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}