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, CoordDimensions, CoordSeq, Geom, Geometry};
16use std::slice::Iter;
17use tracing::{error, warn};
18
19#[derive(Debug, Clone, PartialEq, PartialOrd)]
21pub struct Line {
22 coord: XY2V,
23 srid: SRID,
24}
25
26impl GTrait for Line {
27 fn is_2d(&self) -> bool {
28 self.coord[0].len() == 2
29 }
30
31 fn to_wkt_fmt(&self, precision: usize) -> String {
32 if self.is_2d() {
33 format!(
34 "LINESTRING {}",
35 Self::coords_with_dp(&self.coord, precision)
36 )
37 } else {
38 format!(
39 "LINESTRING Z {}",
40 Self::coords_with_dp(&self.coord, precision)
41 )
42 }
43 }
44
45 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
46 crs.check_line(&self.coord)
47 }
48
49 fn type_(&self) -> &str {
50 "LineString"
51 }
52
53 fn srid(&self) -> SRID {
54 self.srid
55 }
56}
57
58impl Line {
59 pub fn num_points(&self) -> usize {
61 self.coord.len()
62 }
63
64 pub fn points(&self) -> Iter<'_, XY1V> {
66 self.coord.iter()
67 }
68
69 pub(crate) fn from_xy(coord: XY2V) -> Self {
70 Self::from_xy_and_srid(coord, *config().default_srid())
71 }
72
73 pub(crate) fn from_xy_and_srid(coord: XY2V, srid: SRID) -> Self {
74 let coord = Self::ensure_precision_xy(&coord);
75 Line { coord, srid }
76 }
77
78 pub(crate) fn coords_as_txt(coord: &[XY1V]) -> String {
79 Self::coords_with_dp(coord, config().default_precision())
80 }
81
82 pub(crate) fn ensure_precision_xy(coord: &[XY1V]) -> XY2V {
83 coord
84 .iter()
85 .map(|x| Point::ensure_precision_xy(x))
86 .collect()
87 }
88
89 pub(crate) fn coords_with_dp(coord: &[XY1V], precision: usize) -> String {
90 let points: Vec<String> = coord
91 .iter()
92 .map(|x| Point::coords_with_dp(x, precision))
93 .collect();
94 format!("({})", points.join(", "))
95 }
96
97 pub(crate) fn to_geos(&self) -> Result<Geometry, MyError> {
98 Self::to_geos_xy(&self.coord, &self.srid)
99 }
100
101 pub(crate) fn to_geos_xy(xy: &[XY1V], srid: &SRID) -> Result<Geometry, MyError> {
102 let vertices: Vec<&[f64]> = xy.iter().map(|x| x.as_slice()).collect();
103 let xy = CoordSeq::new_from_vec(&vertices)?;
104 let mut g = Geometry::create_line_string(xy)?;
105 let srs_id = srid.as_usize()?;
106 g.set_srid(srs_id);
107
108 Ok(g)
109 }
110
111 pub(crate) fn is_closed(&self) -> bool {
113 self.coord.first() == self.coord.last()
114 }
115
116 pub(crate) fn from_geos_xy<T: Geom>(gg: T) -> Result<XY2V, MyError> {
117 let cs = gg.get_coord_seq()?;
118 let is_3d = match cs.dimensions()? {
119 CoordDimensions::OneD => {
120 let msg = "Don't know how to handle 1D points";
121 error!("Failed: {msg}");
122 return Err(MyError::Runtime(msg.into()));
123 }
124 CoordDimensions::TwoD => false,
125 CoordDimensions::ThreeD => true,
126 };
127 let num_vertices = cs.size()?;
128 let mut xy = Vec::with_capacity(num_vertices);
129 for ndx in 0..num_vertices {
130 let vertex = if is_3d {
131 vec![cs.get_x(ndx)?, cs.get_y(ndx)?, cs.get_z(ndx)?]
132 } else {
133 vec![cs.get_x(ndx)?, cs.get_y(ndx)?]
134 };
135 xy.push(vertex);
136 }
137 Ok(xy)
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.srid = srid.to_owned();
144 }
145 }
146
147 #[cfg(test)]
150 pub(crate) fn is_ring(&self) -> bool {
151 self.num_points() > 3 && self.is_closed()
152 }
153
154 #[cfg(test)]
155 pub(crate) fn first(&self) -> Option<&XY1V> {
156 self.coord.first()
157 }
158
159 #[cfg(test)]
160 pub(crate) fn last(&self) -> Option<&XY1V> {
161 self.coord.last()
162 }
163}
164
165impl fmt::Display for Line {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
167 if self.is_closed() {
168 write!(f, "Ring (...)")
169 } else {
170 write!(f, "Line (...)")
171 }
172 }
173}
174
175impl TryFrom<Geometry> for Line {
176 type Error = MyError;
177
178 fn try_from(value: Geometry) -> Result<Self, Self::Error> {
179 let srs_id = value.get_srid().unwrap_or_else(|x| {
180 error!("Failed get_srid for GEOS Line. Will use Undefined: {}", x);
181 Default::default()
182 });
183 let xy = Line::from_geos_xy(value)?;
184 let srid = SRID::try_from(srs_id)?;
185 Ok(Line::from_xy_and_srid(xy, srid))
186 }
187}
188
189impl TryFrom<ConstGeometry<'_>> for Line {
190 type Error = MyError;
191
192 fn try_from(value: ConstGeometry) -> Result<Self, Self::Error> {
193 let srs_id = value.get_srid().unwrap_or_else(|x| {
194 error!("Failed get_srid for GEOS Line. Will use Undefined: {}", x);
195 Default::default()
196 });
197 let xy = Line::from_geos_xy(value)?;
198 let srid = SRID::try_from(srs_id)?;
199 Ok(Line::from_xy_and_srid(xy, srid))
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::{G, expr::E, text::cql2};
207 use approx::assert_relative_eq;
208 use std::error::Error;
209
210 const TOLERANCE: f64 = 1.0E-3;
211
212 #[test]
213 #[tracing_test::traced_test]
214 fn test() {
215 const G: &str = r#"LineString(43.72992 -79.2998, 43.73005 -79.2991, 43.73006 -79.2984,
216 43.73140 -79.2956, 43.73259 -79.2950, 43.73266 -79.2945,
217 43.73320 -79.2936, 43.73378 -79.2936, 43.73486 -79.2917)"#;
218
219 let exp = cql2::geom_expression(G);
220 assert!(exp.is_ok());
221 let spa = exp.unwrap();
222 let g = match spa {
223 E::Spatial(G::Line(x)) => x,
224 _ => panic!("Not a Line..."),
225 };
226 assert_eq!(g.is_2d(), true);
227 assert_eq!(g.num_points(), 9);
228
229 assert!(g.first().is_some());
230 let first = g.first().unwrap();
231 assert_relative_eq!(first[0], 43.729, epsilon = TOLERANCE);
232 assert_relative_eq!(first[1], -79.299, epsilon = TOLERANCE);
233 assert!(g.last().is_some());
234 let last = g.last().unwrap();
235 assert_relative_eq!(last[0], 43.734, epsilon = TOLERANCE);
236 assert_relative_eq!(last[1], -79.291, epsilon = TOLERANCE);
237 }
238
239 #[test]
240 #[should_panic]
241 fn test_invalid() {
242 let line = Line::from_xy(vec![vec![0.0, 45.0], vec![90.0, 180.0], vec![45.0, 45.0]]);
243 let crs = CRS::default();
244 line.check_coordinates(&crs).unwrap();
245 }
246
247 #[test]
248 fn test_precision() -> Result<(), Box<dyn Error>> {
249 const WKT: &str = "LINESTRING (82.400480 30.411477, 82.722734 30.365046)";
250
251 let line_xy = vec![
252 vec![82.400479770847, 30.4114773625851],
253 vec![82.7227340026191, 30.3650460881709],
254 ];
255
256 let line = Line::from_xy(line_xy);
257 let wkt = line.to_wkt_fmt(6);
258 assert_eq!(wkt, WKT);
259
260 Ok(())
261 }
262}