Skip to main content

ogc_cql2/geom/
lines.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![warn(missing_docs)]
4
5//! Collection of line-string geometries.
6//!
7
8use crate::{
9    CRS, GTrait, Line, MyError,
10    config::config,
11    geom::{XY2V, XY3V},
12    srid::SRID,
13};
14use core::fmt;
15use geos::{ConstGeometry, Geom, Geometry};
16use std::slice::Iter;
17use tracing::{error, warn};
18
19/// Collection of line-string geometries.
20#[derive(Debug, Clone, PartialEq, PartialOrd)]
21pub struct Lines {
22    lines: XY3V,
23    srid: SRID,
24}
25
26impl GTrait for Lines {
27    fn is_2d(&self) -> bool {
28        self.lines[0][0].len() == 2
29    }
30
31    fn to_wkt_fmt(&self, precision: usize) -> String {
32        if self.is_2d() {
33            format!(
34                "MULTILINESTRING {}",
35                Self::coords_with_dp(self.lines.as_slice(), precision)
36            )
37        } else {
38            format!(
39                "MULTILINESTRING Z {}",
40                Self::coords_with_dp(self.lines.as_slice(), precision)
41            )
42        }
43    }
44
45    fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
46        if self.lines.iter().all(|l| crs.check_line(l).is_ok()) {
47            Ok(())
48        } else {
49            Err(MyError::Runtime(
50                "At least one line has invalid coordinates".into(),
51            ))
52        }
53    }
54
55    fn type_(&self) -> &str {
56        "MultiLineString"
57    }
58
59    fn srid(&self) -> SRID {
60        self.srid
61    }
62}
63
64impl Lines {
65    /// Return the number of lines in this.
66    pub fn num_lines(&self) -> usize {
67        self.lines.len()
68    }
69
70    /// Return an iterator over the lines' coordinates.
71    pub fn lines(&self) -> Iter<'_, XY2V> {
72        self.lines.iter()
73    }
74
75    pub(crate) fn from_xy(lines: XY3V) -> Self {
76        Self::from_xy_and_srid(lines, *config().default_srid())
77    }
78
79    pub(crate) fn from_xy_and_srid(lines: XY3V, srid: SRID) -> Self {
80        let lines = lines.iter().map(|x| Line::ensure_precision_xy(x)).collect();
81        Lines { lines, srid }
82    }
83
84    pub(crate) fn coords_as_txt(lines: &[XY2V]) -> String {
85        Self::coords_with_dp(lines, config().default_precision())
86    }
87
88    pub(crate) fn to_geos(&self) -> Result<Geometry, MyError> {
89        let mut lines: Vec<Geometry> = vec![];
90        for l in &self.lines {
91            let g = Line::to_geos_xy(l, &self.srid)?;
92            lines.push(g);
93        }
94        let mut g = Geometry::create_multiline_string(lines)?;
95        let srs_id = self.srid.as_usize()?;
96        g.set_srid(srs_id);
97
98        Ok(g)
99    }
100
101    pub(crate) fn from_geos_xy<T: Geom>(gg: T) -> Result<XY3V, MyError> {
102        let num_lines = gg.get_num_geometries()?;
103        let mut result = Vec::with_capacity(num_lines);
104        for ndx in 0..num_lines {
105            let line = gg.get_geometry_n(ndx)?;
106            let xy = Line::from_geos_xy(line)?;
107            result.push(xy);
108        }
109        Ok(result)
110    }
111
112    pub(crate) fn set_srid_unchecked(&mut self, srid: &SRID) {
113        if self.srid != *srid {
114            warn!("Replacing current SRID ({}) w/ {srid}", self.srid);
115            self.srid = srid.to_owned();
116        }
117    }
118
119    fn coords_with_dp(lines: &[XY2V], precision: usize) -> String {
120        let lines: Vec<String> = lines
121            .iter()
122            .map(|x| Line::coords_with_dp(x.as_slice(), precision))
123            .collect();
124        format!("({})", lines.join(", "))
125    }
126}
127
128impl fmt::Display for Lines {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "Lines (...)")
131    }
132}
133
134impl TryFrom<Geometry> for Lines {
135    type Error = MyError;
136
137    fn try_from(value: Geometry) -> Result<Self, Self::Error> {
138        let srs_id = value.get_srid().unwrap_or_else(|x| {
139            error!(
140                "Failed get_srid for GEOS MultiLine. Will use Undefined: {}",
141                x
142            );
143            Default::default()
144        });
145        let lines = Lines::from_geos_xy(value)?;
146        let srid = SRID::try_from(srs_id)?;
147        Ok(Lines::from_xy_and_srid(lines, srid))
148    }
149}
150
151impl TryFrom<ConstGeometry<'_>> for Lines {
152    type Error = MyError;
153
154    fn try_from(value: ConstGeometry) -> Result<Self, Self::Error> {
155        let srs_id = value.get_srid().unwrap_or_else(|x| {
156            error!(
157                "Failed get_srid for GEOS MultiLine. Will use Undefined: {}",
158                x
159            );
160            Default::default()
161        });
162        let lines = Lines::from_geos_xy(value)?;
163        let srid = SRID::try_from(srs_id)?;
164        Ok(Lines::from_xy_and_srid(lines, srid))
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use crate::{G, expr::E, text::cql2};
172
173    #[test]
174    #[tracing_test::traced_test]
175    fn test() {
176        const G: &str = r#"MultiLineString(
177        ( -1.9 -0.99999, 75.292574 1.5,     -0.5 -4.016458, -31.708594 -74.743801, 179.0 -90.0 ),
178        (-1.9 -1.1,      1.5      8.547371))"#;
179
180        let exp = cql2::geom_expression(G);
181        assert!(exp.is_ok());
182        let spa = exp.unwrap();
183        let g = match spa {
184            E::Spatial(G::Lines(x)) => x,
185            _ => panic!("Not a Polygon..."),
186        };
187        assert_eq!(g.is_2d(), true);
188        let lines = g.lines();
189        assert_eq!(lines.len(), 2);
190        // or...
191        assert_eq!(lines.len(), g.num_lines());
192    }
193}