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