egml_core/model/geometry/primitives/
linear_ring.rs1use crate::error::Error;
2use crate::impl_abstract_ring_traits;
3use crate::model::base::{AsAbstractGml, Id};
4use crate::model::geometry::primitives::{AbstractRing, AsAbstractRing, AsAbstractRingMut};
5use crate::model::geometry::{DirectPosition, Envelope};
6use nalgebra::{Isometry3, Vector3};
7
8const MINIMUM_NUMBER_OF_POINTS: usize = 3;
9
10#[derive(Debug, Clone, PartialEq, Default)]
21pub struct LinearRing {
22 pub(crate) abstract_ring: AbstractRing,
23 points: Vec<DirectPosition>,
24}
25
26impl LinearRing {
27 pub fn new(points: impl IntoIterator<Item = DirectPosition>) -> Result<Self, Error> {
35 let points: Vec<DirectPosition> = points.into_iter().collect();
36 Self::validate_points(&points, None)?;
37 Ok(Self {
38 abstract_ring: AbstractRing::default(),
39 points,
40 })
41 }
42
43 pub fn points(&self) -> &[DirectPosition] {
45 &self.points
46 }
47
48 pub fn set_points(&mut self, val: Vec<DirectPosition>) -> Result<(), Error> {
54 Self::validate_points(&val, self.id())?;
55 self.points = val;
56 Ok(())
57 }
58}
59
60impl LinearRing {
61 pub fn apply_transform(&mut self, m: &Isometry3<f64>) {
63 self.points.iter_mut().for_each(|p| {
64 p.apply_transform(m);
65 });
66 }
67
68 pub fn area_3d(&self) -> f64 {
73 let n = self.points.len();
74 let mut cross_sum = Vector3::zeros();
75 for i in 0..n {
76 let vi: Vector3<f64> = self.points[i].into();
77 let vj: Vector3<f64> = self.points[(i + 1) % n].into();
78 cross_sum += vi.cross(&vj);
79 }
80 cross_sum.norm() * 0.5
81 }
82
83 pub fn compute_envelope(&self) -> Envelope {
85 Envelope::from_points(&self.points).expect("linear ring must have valid points")
86 }
87
88 fn validate_points(points: &[DirectPosition], id: Option<&Id>) -> Result<(), Error> {
89 if let Some((index, window)) = points.windows(2).enumerate().find(|(_, w)| w[0] == w[1]) {
90 return Err(Error::AdjacentDuplicatePositions {
91 index,
92 position: window[0],
93 });
94 }
95 if points.len() < MINIMUM_NUMBER_OF_POINTS {
96 let detail = if id.is_none() {
97 Some(format!(
98 "points: {}",
99 points
100 .iter()
101 .map(|p| p.to_string())
102 .collect::<Vec<String>>()
103 .join(", ")
104 ))
105 } else {
106 None
107 };
108
109 return Err(Error::TooFewElements {
110 geometry: "gml:LinearRing",
111 minimum: 3,
112 spec: Some("OGC 07-036 §10.5.8"),
113 id: id.cloned(),
114 detail,
115 });
116 }
117 let first = *points.first().expect("non-empty validated above");
118 if first == *points.last().expect("non-empty validated above") {
119 return Err(Error::RepeatedClosingVertex { position: first });
120 }
121 Ok(())
122 }
123}
124
125impl AsAbstractRing for LinearRing {
126 fn abstract_ring(&self) -> &AbstractRing {
127 &self.abstract_ring
128 }
129}
130
131impl AsAbstractRingMut for LinearRing {
132 fn abstract_ring_mut(&mut self) -> &mut AbstractRing {
133 &mut self.abstract_ring
134 }
135}
136
137impl_abstract_ring_traits!(LinearRing);
138
139#[cfg(test)]
140mod test {
141 use super::*;
142 use nalgebra::Vector3;
143
144 #[test]
145 fn area_3d_unit_square_xy() {
146 let ring = LinearRing::new([
147 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
148 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
149 DirectPosition::new(1.0, 1.0, 0.0).unwrap(),
150 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
151 ])
152 .unwrap();
153 assert!((ring.area_3d() - 1.0).abs() < 1e-10);
154 }
155
156 #[test]
157 fn area_3d_tilted_rectangle() {
158 let ring = LinearRing::new([
160 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
161 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
162 DirectPosition::new(1.0, 1.0, 2.0).unwrap(),
163 DirectPosition::new(0.0, 1.0, 2.0).unwrap(),
164 ])
165 .unwrap();
166 let expected = 5.0_f64.sqrt();
167 assert!((ring.area_3d() - expected).abs() < 1e-10);
168 }
169
170 #[test]
171 fn area_3d_triangle() {
172 let ring = LinearRing::new([
174 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
175 DirectPosition::new(3.0, 0.0, 0.0).unwrap(),
176 DirectPosition::new(0.0, 4.0, 0.0).unwrap(),
177 ])
178 .unwrap();
179 assert!((ring.area_3d() - 6.0).abs() < 1e-10);
180 }
181
182 #[test]
183 fn linear_ring_construction_test() {
184 let points = vec![
185 DirectPosition::new(601.92791444745251, 1130.4631113024607, 9.0130903915382347)
186 .unwrap(),
187 DirectPosition::new(601.92791832847342, 1130.4631032795705, 9.0130907233102739)
188 .unwrap(),
189 DirectPosition::new(601.92791832847342, 1130.4631032795705, 9.0130907233102739)
190 .unwrap(),
191 ];
192 let result = LinearRing::new(points);
193
194 assert!(matches!(
195 result,
196 Err(Error::AdjacentDuplicatePositions { .. })
197 ));
198 }
199
200 #[test]
201 fn offset_linear_ring_test() {
202 let mut linear_ring = LinearRing::new([
203 DirectPosition::new(1.0, 2.0, 3.0).unwrap(),
204 DirectPosition::new(2.0, 4.0, 6.0).unwrap(),
205 DirectPosition::new(4.0, 7.0, 9.0).unwrap(),
206 ])
207 .unwrap();
208 let isometry: Isometry3<f64> =
210 Isometry3::new(Vector3::new(1.0, -1.0, 3.0), Default::default());
211 let expected_linear_ring = LinearRing::new([
212 DirectPosition::new(2.0, 1.0, 6.0).unwrap(),
213 DirectPosition::new(3.0, 3.0, 9.0).unwrap(),
214 DirectPosition::new(5.0, 6.0, 12.0).unwrap(),
215 ])
216 .unwrap();
217
218 linear_ring.apply_transform(&isometry);
219
220 assert_eq!(linear_ring, expected_linear_ring);
221 }
222}