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: Vec<DirectPosition>,
26}
27
28impl LinearRing {
29 pub fn new(points: impl IntoIterator<Item = DirectPosition>) -> Result<Self, Error> {
37 let points: Vec<DirectPosition> = points.into_iter().collect();
38 Self::validate_points(&points, None)?;
39
40 Ok(Self {
41 abstract_ring: AbstractRing::default(),
42 points,
43 })
44 }
45
46 pub fn from_abstract_ring(
47 abstract_ring: AbstractRing,
48 points: impl IntoIterator<Item = DirectPosition>,
49 ) -> Result<Self, Error> {
50 let points: Vec<DirectPosition> = points.into_iter().collect();
51 Self::validate_points(&points, None)?;
52 Ok(Self {
53 abstract_ring,
54 points,
55 })
56 }
57
58 fn validate_points(points: &[DirectPosition], id: Option<&Id>) -> Result<(), Error> {
59 if let Some((index, window)) = points.windows(2).enumerate().find(|(_, w)| w[0] == w[1]) {
60 return Err(Error::AdjacentDuplicatePositions {
61 index,
62 position: window[0],
63 });
64 }
65
66 if points.len() < MINIMUM_NUMBER_OF_POINTS {
67 let detail = if id.is_none() {
68 Some(format!(
69 "points: {}",
70 points
71 .iter()
72 .map(|p| p.to_string())
73 .collect::<Vec<String>>()
74 .join(", ")
75 ))
76 } else {
77 None
78 };
79
80 return Err(Error::TooFewElements {
81 geometry: "gml:LinearRing",
82 minimum: 3,
83 spec: Some("OGC 07-036 §10.5.8"),
84 id: id.cloned(),
85 detail,
86 });
87 }
88
89 let first = *points.first().expect("non-empty validated above");
90 if first == *points.last().expect("non-empty validated above") {
91 return Err(Error::RepeatedClosingVertex { position: first });
92 }
93
94 Ok(())
95 }
96
97 pub fn points(&self) -> &[DirectPosition] {
99 &self.points
100 }
101
102 pub fn set_points(&mut self, val: Vec<DirectPosition>) -> Result<(), Error> {
108 Self::validate_points(&val, self.id())?;
109
110 self.points = val;
111 Ok(())
112 }
113}
114
115impl AsAbstractRing for LinearRing {
116 fn abstract_ring(&self) -> &AbstractRing {
117 &self.abstract_ring
118 }
119}
120
121impl AsAbstractRingMut for LinearRing {
122 fn abstract_ring_mut(&mut self) -> &mut AbstractRing {
123 &mut self.abstract_ring
124 }
125}
126
127impl_abstract_ring_traits!(LinearRing);
128impl_abstract_ring_mut_traits!(LinearRing);
129impl_has_geometry_type!(LinearRing, LinearRing);
130
131impl LinearRing {
132 pub fn length_3d(&self) -> f64 {
133 todo!("needs to be implemented for LinearRing")
134 }
135
136 pub fn area_3d(&self) -> f64 {
141 let n = self.points.len();
142 let mut cross_sum = Vector3::zeros();
143 for i in 0..n {
144 let vi: Vector3<f64> = self.points[i].into();
145 let vj: Vector3<f64> = self.points[(i + 1) % n].into();
146 cross_sum += vi.cross(&vj);
147 }
148 cross_sum.norm() * 0.5
149 }
150}
151
152impl ApplyTransform for LinearRing {
153 fn apply_transform(&mut self, transform: Transform3<f64>) {
154 self.points.iter_mut().for_each(|p| {
155 p.apply_transform(transform);
156 });
157 }
158
159 fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
160 self.points.iter_mut().for_each(|p| {
161 p.apply_isometry(isometry);
162 });
163 }
164
165 fn apply_translation(&mut self, vector: Vector3<f64>) {
166 self.points.iter_mut().for_each(|p| {
167 p.apply_translation(vector);
168 });
169 }
170
171 fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
172 self.points.iter_mut().for_each(|p| {
173 p.apply_rotation(rotation);
174 });
175 }
176
177 fn apply_scale(&mut self, scale: Scale3<f64>) {
178 self.points.iter_mut().for_each(|p| {
179 p.apply_scale(scale);
180 });
181 }
182}
183
184impl ComputeEnvelope for LinearRing {
185 fn compute_envelope(&self) -> Option<Envelope> {
187 Some(Envelope::from_points(&self.points).expect("linear ring must have valid points"))
188 }
189}
190
191impl IterGeometries for LinearRing {
192 fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
193 Box::new(std::iter::once(self.into()))
194 }
195}
196
197#[cfg(test)]
198mod test {
199 use super::*;
200 use nalgebra::{Isometry3, Vector3};
201
202 #[test]
203 fn area_3d_unit_square_xy() {
204 let ring = LinearRing::new([
205 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
206 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
207 DirectPosition::new(1.0, 1.0, 0.0).unwrap(),
208 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
209 ])
210 .unwrap();
211 assert!((ring.area_3d() - 1.0).abs() < 1e-10);
212 }
213
214 #[test]
215 fn area_3d_tilted_rectangle() {
216 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, 2.0).unwrap(),
221 DirectPosition::new(0.0, 1.0, 2.0).unwrap(),
222 ])
223 .unwrap();
224 let expected = 5.0_f64.sqrt();
225 assert!((ring.area_3d() - expected).abs() < 1e-10);
226 }
227
228 #[test]
229 fn area_3d_triangle() {
230 let ring = LinearRing::new([
232 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
233 DirectPosition::new(3.0, 0.0, 0.0).unwrap(),
234 DirectPosition::new(0.0, 4.0, 0.0).unwrap(),
235 ])
236 .unwrap();
237 assert!((ring.area_3d() - 6.0).abs() < 1e-10);
238 }
239
240 #[test]
241 fn linear_ring_construction_test() {
242 let points = vec![
243 DirectPosition::new(601.92791444745251, 1130.4631113024607, 9.0130903915382347)
244 .unwrap(),
245 DirectPosition::new(601.92791832847342, 1130.4631032795705, 9.0130907233102739)
246 .unwrap(),
247 DirectPosition::new(601.92791832847342, 1130.4631032795705, 9.0130907233102739)
248 .unwrap(),
249 ];
250 let result = LinearRing::new(points);
251
252 assert!(matches!(
253 result,
254 Err(Error::AdjacentDuplicatePositions { .. })
255 ));
256 }
257
258 #[test]
259 fn offset_linear_ring_test() {
260 let mut linear_ring = LinearRing::new([
261 DirectPosition::new(1.0, 2.0, 3.0).unwrap(),
262 DirectPosition::new(2.0, 4.0, 6.0).unwrap(),
263 DirectPosition::new(4.0, 7.0, 9.0).unwrap(),
264 ])
265 .unwrap();
266 let isometry: Isometry3<f64> =
268 Isometry3::new(Vector3::new(1.0, -1.0, 3.0), Default::default());
269 let expected_linear_ring = LinearRing::new([
270 DirectPosition::new(2.0, 1.0, 6.0).unwrap(),
271 DirectPosition::new(3.0, 3.0, 9.0).unwrap(),
272 DirectPosition::new(5.0, 6.0, 12.0).unwrap(),
273 ])
274 .unwrap();
275
276 linear_ring.apply_isometry(isometry);
277
278 assert_eq!(linear_ring, expected_linear_ring);
279 }
280}