egml_core/model/geometry/primitives/
line_string.rs1use crate::model::common::{ApplyTransform, ComputeEnvelope, IterGeometries};
2use crate::model::geometry::primitives::{AbstractCurve, AsAbstractCurve, AsAbstractCurveMut};
3use crate::model::geometry::refs::AbstractGeometryKindRef;
4use crate::model::geometry::{DirectPosition, Envelope};
5use crate::{
6 Error, impl_abstract_curve_mut_traits, impl_abstract_curve_traits, impl_has_geometry_type,
7};
8use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
9
10#[derive(Debug, Clone, PartialEq, Default)]
14pub struct LineString {
15 pub abstract_curve: AbstractCurve,
16 points: Vec<DirectPosition>,
17}
18
19impl LineString {
20 pub fn new(points: impl IntoIterator<Item = DirectPosition>) -> Result<Self, Error> {
27 let points: Vec<DirectPosition> = points.into_iter().collect();
28 Self::validate(&points)?;
29
30 Ok(Self {
31 abstract_curve: AbstractCurve::default(),
32 points,
33 })
34 }
35
36 pub fn from_abstract_curve(
37 abstract_curve: AbstractCurve,
38 points: impl IntoIterator<Item = DirectPosition>,
39 ) -> Result<Self, Error> {
40 let points: Vec<DirectPosition> = points.into_iter().collect();
41 Self::validate(&points)?;
42
43 Ok(Self {
44 abstract_curve,
45 points,
46 })
47 }
48
49 fn validate(points: &[DirectPosition]) -> Result<(), Error> {
50 if let Some((index, window)) = points.windows(2).enumerate().find(|(_, w)| w[0] == w[1]) {
51 return Err(Error::AdjacentDuplicatePositions {
52 index,
53 position: window[0],
54 });
55 }
56
57 if points.len() < 2 {
58 return Err(Error::TooFewElements {
59 geometry: "gml:LineString",
60 minimum: 2,
61 spec: Some("OGC 07-036 §10.4.4"),
62 id: None,
63 detail: None,
64 });
65 }
66
67 Ok(())
68 }
69
70 pub fn points(&self) -> &[DirectPosition] {
72 &self.points
73 }
74
75 pub fn set_points(
81 &mut self,
82 points: impl IntoIterator<Item = DirectPosition>,
83 ) -> Result<(), Error> {
84 let points: Vec<DirectPosition> = points.into_iter().collect();
85 Self::validate(&points)?;
86 self.points = points;
87 Ok(())
88 }
89}
90
91impl AsAbstractCurve for LineString {
92 fn abstract_curve(&self) -> &AbstractCurve {
93 &self.abstract_curve
94 }
95}
96
97impl AsAbstractCurveMut for LineString {
98 fn abstract_curve_mut(&mut self) -> &mut AbstractCurve {
99 &mut self.abstract_curve
100 }
101}
102
103impl_abstract_curve_traits!(LineString);
104impl_abstract_curve_mut_traits!(LineString);
105impl_has_geometry_type!(LineString, LineString);
106
107impl LineString {
108 pub fn length_3d(&self) -> f64 {
110 self.points
111 .windows(2)
112 .map(|w| {
113 let a: Vector3<f64> = w[0].into();
114 let b: Vector3<f64> = w[1].into();
115 (b - a).norm()
116 })
117 .sum()
118 }
119}
120
121impl ApplyTransform for LineString {
122 fn apply_transform(&mut self, transform: Transform3<f64>) {
123 self.points.iter_mut().for_each(|p| {
124 p.apply_transform(transform);
125 });
126 }
127
128 fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
129 self.points.iter_mut().for_each(|p| {
130 p.apply_isometry(isometry);
131 });
132 }
133
134 fn apply_translation(&mut self, vector: Vector3<f64>) {
135 self.points.iter_mut().for_each(|p| {
136 p.apply_translation(vector);
137 });
138 }
139
140 fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
141 self.points.iter_mut().for_each(|p| {
142 p.apply_rotation(rotation);
143 });
144 }
145
146 fn apply_scale(&mut self, scale: Scale3<f64>) {
147 self.points.iter_mut().for_each(|p| {
148 p.apply_scale(scale);
149 });
150 }
151}
152
153impl ComputeEnvelope for LineString {
154 fn compute_envelope(&self) -> Option<Envelope> {
155 Some(Envelope::from_points(&self.points).expect("line string must have valid points"))
156 }
157}
158
159impl IterGeometries for LineString {
160 fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
161 Box::new(std::iter::once(self.into()))
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn length_3d_axis_aligned() {
171 let ls = LineString::new([
172 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
173 DirectPosition::new(3.0, 0.0, 0.0).unwrap(),
174 DirectPosition::new(3.0, 4.0, 0.0).unwrap(),
175 ])
176 .unwrap();
177 assert!((ls.length_3d() - 7.0).abs() < 1e-10);
178 }
179
180 #[test]
181 fn length_3d_diagonal() {
182 let ls = LineString::new([
184 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
185 DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
186 ])
187 .unwrap();
188 let expected = 3.0_f64.sqrt();
189 assert!((ls.length_3d() - expected).abs() < 1e-10);
190 }
191}