egml_core/model/geometry/primitives/
linear_ring.rs1use crate::error::Error;
2use crate::model::base::{AsAbstractGml, Id};
3use crate::model::common::{ApplyTransform, ComputeEnvelope, IterGeometries};
4use crate::model::geometry::primitives::{AbstractRing, AsAbstractRing, AsAbstractRingMut};
5use crate::model::geometry::refs::AbstractGeometryKindRef;
6use crate::model::geometry::{DirectPosition, Envelope};
7use crate::{impl_abstract_ring_mut_traits, impl_abstract_ring_traits, impl_has_geometry_type};
8use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
9
10const MINIMUM_NUMBER_OF_POINTS: usize = 3;
11
12#[derive(Debug, Clone, PartialEq, Default)]
23pub struct LinearRing {
24 pub abstract_ring: AbstractRing,
25 points: Box<[DirectPosition]>,
28}
29
30impl LinearRing {
31 pub fn new(points: impl IntoIterator<Item = DirectPosition>) -> Result<Self, Error> {
39 let points: Vec<DirectPosition> = points.into_iter().collect();
40 Self::validate_points(&points, None)?;
41
42 Ok(Self {
43 abstract_ring: AbstractRing::default(),
44 points: points.into_boxed_slice(),
45 })
46 }
47
48 pub fn from_abstract_ring(
49 abstract_ring: AbstractRing,
50 points: impl IntoIterator<Item = DirectPosition>,
51 ) -> Result<Self, Error> {
52 let points: Vec<DirectPosition> = points.into_iter().collect();
53 Self::validate_points(&points, None)?;
54 Ok(Self {
55 abstract_ring,
56 points: points.into_boxed_slice(),
57 })
58 }
59
60 fn validate_points(points: &[DirectPosition], id: Option<&Id>) -> Result<(), Error> {
61 if let Some((index, window)) = points.windows(2).enumerate().find(|(_, w)| w[0] == w[1]) {
62 return Err(Error::AdjacentDuplicatePositions {
63 index,
64 position: window[0],
65 });
66 }
67
68 if points.len() < MINIMUM_NUMBER_OF_POINTS {
69 let detail = if id.is_none() {
70 Some(format!(
71 "points: {}",
72 points
73 .iter()
74 .map(|p| p.to_string())
75 .collect::<Vec<String>>()
76 .join(", ")
77 ))
78 } else {
79 None
80 };
81
82 return Err(Error::TooFewElements {
83 geometry: "gml:LinearRing",
84 minimum: 3,
85 spec: Some("OGC 07-036 §10.5.8"),
86 id: id.cloned(),
87 detail,
88 });
89 }
90
91 let first = *points.first().expect("non-empty validated above");
92 if first == *points.last().expect("non-empty validated above") {
93 return Err(Error::RepeatedClosingVertex { position: first });
94 }
95
96 Ok(())
97 }
98
99 pub fn points(&self) -> &[DirectPosition] {
101 &self.points
102 }
103
104 pub fn set_points(&mut self, val: Vec<DirectPosition>) -> Result<(), Error> {
110 Self::validate_points(&val, self.id())?;
111
112 self.points = val.into_boxed_slice();
113 Ok(())
114 }
115}
116
117impl AsAbstractRing for LinearRing {
118 fn abstract_ring(&self) -> &AbstractRing {
119 &self.abstract_ring
120 }
121}
122
123impl AsAbstractRingMut for LinearRing {
124 fn abstract_ring_mut(&mut self) -> &mut AbstractRing {
125 &mut self.abstract_ring
126 }
127}
128
129impl_abstract_ring_traits!(LinearRing);
130impl_abstract_ring_mut_traits!(LinearRing);
131impl_has_geometry_type!(LinearRing, LinearRing);
132
133impl LinearRing {
134 pub fn length_3d(&self) -> f64 {
139 let n = self.points.len();
140 (0..n)
141 .map(|i| {
142 let vi: Vector3<f64> = self.points[i].into();
143 let vj: Vector3<f64> = self.points[(i + 1) % n].into();
144 (vj - vi).norm()
145 })
146 .sum()
147 }
148
149 pub fn area_3d(&self) -> f64 {
154 let n = self.points.len();
155 let mut cross_sum = Vector3::zeros();
156 for i in 0..n {
157 let vi: Vector3<f64> = self.points[i].into();
158 let vj: Vector3<f64> = self.points[(i + 1) % n].into();
159 cross_sum += vi.cross(&vj);
160 }
161 cross_sum.norm() * 0.5
162 }
163}
164
165impl ApplyTransform for LinearRing {
166 fn apply_transform(&mut self, transform: Transform3<f64>) {
167 self.points.iter_mut().for_each(|p| {
168 p.apply_transform(transform);
169 });
170 }
171
172 fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
173 self.points.iter_mut().for_each(|p| {
174 p.apply_isometry(isometry);
175 });
176 }
177
178 fn apply_translation(&mut self, vector: Vector3<f64>) {
179 self.points.iter_mut().for_each(|p| {
180 p.apply_translation(vector);
181 });
182 }
183
184 fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
185 self.points.iter_mut().for_each(|p| {
186 p.apply_rotation(rotation);
187 });
188 }
189
190 fn apply_scale(&mut self, scale: Scale3<f64>) {
191 self.points.iter_mut().for_each(|p| {
192 p.apply_scale(scale);
193 });
194 }
195}
196
197impl ComputeEnvelope for LinearRing {
198 fn compute_envelope(&self) -> Option<Envelope> {
200 Some(Envelope::from_points(&self.points).expect("linear ring must have valid points"))
201 }
202}
203
204impl IterGeometries for LinearRing {
205 fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
206 Box::new(std::iter::once(self.into()))
207 }
208}
209
210#[cfg(test)]
211mod test {
212 use super::*;
213 use nalgebra::{Isometry3, Vector3};
214
215 #[test]
216 fn length_3d_unit_square_includes_closing_segment() {
217 let ring = LinearRing::new([
218 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
219 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
220 DirectPosition::new(1.0, 1.0, 0.0).unwrap(),
221 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
222 ])
223 .unwrap();
224 assert!((ring.length_3d() - 4.0).abs() < 1e-10);
225 }
226
227 #[test]
228 fn length_3d_right_triangle_in_3d() {
229 let ring = LinearRing::new([
231 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
232 DirectPosition::new(3.0, 0.0, 0.0).unwrap(),
233 DirectPosition::new(3.0, 0.0, 4.0).unwrap(),
234 ])
235 .unwrap();
236 assert!((ring.length_3d() - 12.0).abs() < 1e-10);
237 }
238
239 #[test]
240 fn area_3d_unit_square_xy() {
241 let ring = LinearRing::new([
242 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
243 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
244 DirectPosition::new(1.0, 1.0, 0.0).unwrap(),
245 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
246 ])
247 .unwrap();
248 assert!((ring.area_3d() - 1.0).abs() < 1e-10);
249 }
250
251 #[test]
252 fn area_3d_tilted_rectangle() {
253 let ring = LinearRing::new([
255 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
256 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
257 DirectPosition::new(1.0, 1.0, 2.0).unwrap(),
258 DirectPosition::new(0.0, 1.0, 2.0).unwrap(),
259 ])
260 .unwrap();
261 let expected = 5.0_f64.sqrt();
262 assert!((ring.area_3d() - expected).abs() < 1e-10);
263 }
264
265 #[test]
266 fn area_3d_triangle() {
267 let ring = LinearRing::new([
269 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
270 DirectPosition::new(3.0, 0.0, 0.0).unwrap(),
271 DirectPosition::new(0.0, 4.0, 0.0).unwrap(),
272 ])
273 .unwrap();
274 assert!((ring.area_3d() - 6.0).abs() < 1e-10);
275 }
276
277 #[test]
278 fn linear_ring_construction_test() {
279 let points = vec![
280 DirectPosition::new(601.92791444745251, 1130.4631113024607, 9.0130903915382347)
281 .unwrap(),
282 DirectPosition::new(601.92791832847342, 1130.4631032795705, 9.0130907233102739)
283 .unwrap(),
284 DirectPosition::new(601.92791832847342, 1130.4631032795705, 9.0130907233102739)
285 .unwrap(),
286 ];
287 let result = LinearRing::new(points);
288
289 assert!(matches!(
290 result,
291 Err(Error::AdjacentDuplicatePositions { .. })
292 ));
293 }
294
295 #[test]
296 fn offset_linear_ring_test() {
297 let mut linear_ring = LinearRing::new([
298 DirectPosition::new(1.0, 2.0, 3.0).unwrap(),
299 DirectPosition::new(2.0, 4.0, 6.0).unwrap(),
300 DirectPosition::new(4.0, 7.0, 9.0).unwrap(),
301 ])
302 .unwrap();
303 let isometry: Isometry3<f64> =
305 Isometry3::new(Vector3::new(1.0, -1.0, 3.0), Default::default());
306 let expected_linear_ring = LinearRing::new([
307 DirectPosition::new(2.0, 1.0, 6.0).unwrap(),
308 DirectPosition::new(3.0, 3.0, 9.0).unwrap(),
309 DirectPosition::new(5.0, 6.0, 12.0).unwrap(),
310 ])
311 .unwrap();
312
313 linear_ring.apply_isometry(isometry);
314
315 assert_eq!(linear_ring, expected_linear_ring);
316 }
317}