egml_core/model/geometry/primitives/
line_string.rs1use crate::model::geometry::primitives::{AbstractCurve, AsAbstractCurve, AsAbstractCurveMut};
2use crate::model::geometry::{DirectPosition, Envelope};
3use crate::{Error, impl_abstract_curve_traits};
4use nalgebra::{Isometry3, Vector3};
5
6#[derive(Debug, Clone, PartialEq, Default)]
10pub struct LineString {
11 pub(crate) abstract_curve: AbstractCurve,
12 points: Vec<DirectPosition>,
13}
14
15impl LineString {
16 pub fn new(points: impl IntoIterator<Item = DirectPosition>) -> Result<Self, Error> {
23 let points: Vec<DirectPosition> = points.into_iter().collect();
24 if let Some((index, window)) = points.windows(2).enumerate().find(|(_, w)| w[0] == w[1]) {
25 return Err(Error::AdjacentDuplicatePositions {
26 index,
27 position: window[0],
28 });
29 }
30 if points.len() < 2 {
31 return Err(Error::TooFewElements {
32 geometry: "gml:LineString",
33 minimum: 2,
34 spec: Some("OGC 07-036 §10.4.4"),
35 id: None,
36 detail: None,
37 });
38 }
39
40 Ok(Self {
41 abstract_curve: AbstractCurve::default(),
42 points,
43 })
44 }
45
46 pub fn points(&self) -> &[DirectPosition] {
48 &self.points
49 }
50
51 pub fn set_points(
57 &mut self,
58 points: impl IntoIterator<Item = DirectPosition>,
59 ) -> Result<(), crate::Error> {
60 let points: Vec<DirectPosition> = points.into_iter().collect();
61 if let Some((index, window)) = points.windows(2).enumerate().find(|(_, w)| w[0] == w[1]) {
62 return Err(crate::Error::AdjacentDuplicatePositions {
63 index,
64 position: window[0],
65 });
66 }
67 if points.len() < 2 {
68 return Err(crate::Error::TooFewElements {
69 geometry: "gml:LineString",
70 minimum: 2,
71 spec: Some("OGC 07-036 §10.4.4"),
72 id: None,
73 detail: None,
74 });
75 }
76 self.points = points;
77 Ok(())
78 }
79}
80
81impl LineString {
82 pub fn length_3d(&self) -> f64 {
84 self.points
85 .windows(2)
86 .map(|w| {
87 let a: Vector3<f64> = w[0].into();
88 let b: Vector3<f64> = w[1].into();
89 (b - a).norm()
90 })
91 .sum()
92 }
93
94 pub fn apply_transform(&mut self, m: &Isometry3<f64>) {
96 self.points.iter_mut().for_each(|p| {
97 p.apply_transform(m);
98 });
99 }
100
101 pub fn compute_envelope(&self) -> Envelope {
102 Envelope::from_points(&self.points).expect("line string must have valid points")
103 }
104}
105
106impl AsAbstractCurve for LineString {
107 fn abstract_curve(&self) -> &AbstractCurve {
108 &self.abstract_curve
109 }
110}
111
112impl AsAbstractCurveMut for LineString {
113 fn abstract_curve_mut(&mut self) -> &mut AbstractCurve {
114 &mut self.abstract_curve
115 }
116}
117
118impl_abstract_curve_traits!(LineString);
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn length_3d_axis_aligned() {
126 let ls = LineString::new([
127 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
128 DirectPosition::new(3.0, 0.0, 0.0).unwrap(),
129 DirectPosition::new(3.0, 4.0, 0.0).unwrap(),
130 ])
131 .unwrap();
132 assert!((ls.length_3d() - 7.0).abs() < 1e-10);
133 }
134
135 #[test]
136 fn length_3d_diagonal() {
137 let ls = LineString::new([
139 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
140 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
141 ])
142 .unwrap();
143 let expected = 3.0_f64.sqrt();
144 assert!((ls.length_3d() - expected).abs() < 1e-10);
145 }
146}