Skip to main content

egml_core/model/geometry/primitives/
polygon.rs

1use crate::model::geometry::primitives::{
2    AbstractSurface, AsAbstractSurface, AsAbstractSurfaceMut, RingProperty, TriangulatedSurface,
3};
4use crate::model::geometry::{DirectPosition, Envelope};
5use crate::util::plane::Plane;
6use crate::util::triangulate::triangulate;
7use crate::{Error, impl_abstract_surface_traits};
8use nalgebra::{Isometry3, Vector3};
9use rayon::prelude::*;
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct Polygon {
13    pub(crate) abstract_surface: AbstractSurface,
14    exterior: Option<RingProperty>,
15    interior: Vec<RingProperty>,
16}
17
18impl Polygon {
19    pub fn new(
20        exterior: Option<RingProperty>,
21        interior: impl IntoIterator<Item = RingProperty>,
22    ) -> Result<Self, Error> {
23        Ok(Self {
24            abstract_surface: AbstractSurface::default(),
25            exterior,
26            interior: interior.into_iter().collect(),
27        })
28    }
29
30    pub fn exterior(&self) -> Option<&RingProperty> {
31        self.exterior.as_ref()
32    }
33
34    pub fn set_exterior(&mut self, exterior: Option<RingProperty>) {
35        self.exterior = exterior;
36    }
37
38    pub fn interior(&self) -> &[RingProperty] {
39        &self.interior
40    }
41
42    pub fn set_interior(&mut self, interior: Vec<RingProperty>) {
43        self.interior = interior;
44    }
45
46    pub fn push_interior(&mut self, ring: RingProperty) {
47        self.interior.push(ring);
48    }
49
50    pub fn extend_interiors(&mut self, rings: impl IntoIterator<Item = RingProperty>) {
51        self.interior.extend(rings);
52    }
53}
54
55impl Polygon {
56    pub fn compute_envelope(&self) -> Option<Envelope> {
57        if let Some(exterior) = &self.exterior
58            && let Some(object) = exterior.object.as_ref()
59            && let e = object.compute_envelope()
60        {
61            return Some(e);
62        }
63
64        let envelopes = self
65            .interior
66            .iter()
67            .filter_map(|x| x.object.as_ref())
68            .map(|x| x.compute_envelope())
69            .collect::<Vec<_>>();
70
71        Envelope::from_envelopes(&envelopes)
72    }
73
74    ///
75    /// See also <https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method>
76    fn normal(&self) -> Vector3<f64> {
77        let mut enclosed_boundary_points = self
78            .exterior()
79            .expect("should be there")
80            .object
81            .as_ref()
82            .expect("should be there")
83            .points()
84            .to_vec();
85        let first = enclosed_boundary_points
86            .first()
87            .copied()
88            .expect("should be there");
89        enclosed_boundary_points.push(first);
90
91        let mut normal = Vector3::new(0.0, 0.0, 0.0);
92        for current_point_pair in enclosed_boundary_points.windows(2) {
93            let current_first_point: Vector3<f64> = current_point_pair[0].into();
94            let current_second_point: Vector3<f64> = current_point_pair[1].into();
95
96            normal += (current_first_point - current_second_point)
97                .cross(&(current_first_point + current_second_point));
98        }
99
100        normal.normalize()
101    }
102
103    pub fn plane_equation(&self) -> Plane {
104        let envelope = self.compute_envelope().expect("should have envelope");
105        Plane::new(*envelope.lower_corner(), self.normal())
106    }
107
108    /// Returns the net 3D area_3d of this polygon: exterior area_3d minus the sum of all interior hole area_3ds.
109    ///
110    /// # Errors
111    ///
112    /// Returns [`Error::MissingExteriorRing`] if the polygon has no exterior ring property.
113    /// Returns [`Error::UnresolvedRingReference`] if the exterior ring or any interior hole
114    /// carries only an xlink:href that has not been resolved into an inline object.
115    pub fn area_3d(&self) -> Result<f64, Error> {
116        let exterior_ring = self.exterior.as_ref().ok_or(Error::MissingExteriorRing)?;
117        let exterior = exterior_ring
118            .object
119            .as_ref()
120            .ok_or_else(|| Error::UnresolvedRingReference {
121                href: exterior_ring.href.clone(),
122            })?
123            .area_3d();
124
125        let holes = self
126            .interior
127            .iter()
128            .map(|r| {
129                r.object
130                    .as_ref()
131                    .ok_or_else(|| Error::UnresolvedRingReference {
132                        href: r.href.clone(),
133                    })
134                    .map(|ring| ring.area_3d())
135            })
136            .collect::<Result<Vec<f64>, Error>>()?
137            .into_iter()
138            .sum::<f64>();
139
140        Ok(exterior - holes)
141    }
142
143    pub fn triangulate(&self) -> Result<TriangulatedSurface, Error> {
144        triangulate(self.exterior.clone(), self.interior.to_vec())
145    }
146
147    pub fn points(&self) -> Vec<&DirectPosition> {
148        let mut all_points = Vec::new();
149        if let Some(exterior) = &self.exterior
150            && let Some(object) = exterior.object.as_ref()
151        {
152            all_points.extend(object.points());
153        }
154
155        for ring in &self.interior {
156            if let Some(object) = ring.object.as_ref() {
157                all_points.extend(object.points());
158            }
159        }
160
161        all_points
162    }
163
164    pub fn apply_transform(&mut self, m: &Isometry3<f64>) {
165        if let Some(exterior) = &mut self.exterior
166            && let Some(object) = exterior.object.as_mut()
167        {
168            object.apply_transform(m);
169        }
170
171        self.interior.par_iter_mut().for_each(|p| {
172            if let Some(object) = p.object.as_mut() {
173                object.apply_transform(m);
174            }
175        });
176    }
177}
178
179impl AsAbstractSurface for Polygon {
180    fn abstract_surface(&self) -> &AbstractSurface {
181        &self.abstract_surface
182    }
183}
184
185impl AsAbstractSurfaceMut for Polygon {
186    fn abstract_surface_mut(&mut self) -> &mut AbstractSurface {
187        &mut self.abstract_surface
188    }
189}
190
191impl_abstract_surface_traits!(Polygon);
192
193#[cfg(test)]
194mod test {
195    use super::*;
196    use crate::model::geometry::DirectPosition;
197    use crate::model::geometry::primitives::{AsSurface, LinearRing, RingKind};
198    use nalgebra::Vector3;
199
200    #[test]
201    fn area_3d_unit_square() {
202        let ring = LinearRing::new([
203            DirectPosition::new(0.0, 0.0, 1.0).unwrap(),
204            DirectPosition::new(1.0, 0.0, 1.0).unwrap(),
205            DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
206            DirectPosition::new(0.0, 1.0, 1.0).unwrap(),
207        ])
208        .unwrap();
209        let polygon =
210            Polygon::new(Some(RingProperty::new(RingKind::LinearRing(ring))), []).unwrap();
211        assert!((polygon.area_3d().expect("has exterior ring") - 1.0).abs() < 1e-10);
212    }
213
214    #[test]
215    fn area_3d_with_hole() {
216        // 4×4 outer square with a 1×1 hole — net area_3d should be 15.
217        let exterior = LinearRing::new([
218            DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
219            DirectPosition::new(4.0, 0.0, 0.0).unwrap(),
220            DirectPosition::new(4.0, 4.0, 0.0).unwrap(),
221            DirectPosition::new(0.0, 4.0, 0.0).unwrap(),
222        ])
223        .unwrap();
224        let hole = LinearRing::new([
225            DirectPosition::new(1.0, 1.0, 0.0).unwrap(),
226            DirectPosition::new(2.0, 1.0, 0.0).unwrap(),
227            DirectPosition::new(2.0, 2.0, 0.0).unwrap(),
228            DirectPosition::new(1.0, 2.0, 0.0).unwrap(),
229        ])
230        .unwrap();
231        let polygon = Polygon::new(
232            Some(RingProperty::new(RingKind::LinearRing(exterior))),
233            vec![RingProperty::new(RingKind::LinearRing(hole))],
234        )
235        .unwrap();
236        assert!((polygon.area_3d().expect("has exterior ring") - 15.0).abs() < 1e-10);
237    }
238
239    #[test]
240    fn area_3d_no_exterior_ring() {
241        let polygon = Polygon::new(None, []).unwrap();
242        assert_eq!(polygon.area_3d(), Err(Error::MissingExteriorRing));
243    }
244
245    #[test]
246    fn area_3d_unresolved_exterior_ring() {
247        let exterior = RingProperty::new_href("urn:example:ring-1");
248        let polygon = Polygon::new(Some(exterior), []).unwrap();
249        assert_eq!(
250            polygon.area_3d(),
251            Err(Error::UnresolvedRingReference {
252                href: Some("urn:example:ring-1".to_string())
253            })
254        );
255    }
256
257    #[test]
258    fn area_3d_unresolved_interior_ring() {
259        let exterior = LinearRing::new([
260            DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
261            DirectPosition::new(4.0, 0.0, 0.0).unwrap(),
262            DirectPosition::new(4.0, 4.0, 0.0).unwrap(),
263            DirectPosition::new(0.0, 4.0, 0.0).unwrap(),
264        ])
265        .unwrap();
266        let hole = RingProperty::new_href("urn:example:hole-1");
267        let polygon = Polygon::new(
268            Some(RingProperty::new(RingKind::LinearRing(exterior))),
269            vec![hole],
270        )
271        .unwrap();
272        assert_eq!(
273            polygon.area_3d(),
274            Err(Error::UnresolvedRingReference {
275                href: Some("urn:example:hole-1".to_string())
276            })
277        );
278    }
279
280    #[test]
281    fn basic_normal_vector() {
282        let point_a = DirectPosition::new(0.0, 0.0, 1.0).unwrap();
283        let point_b = DirectPosition::new(1.0, 0.0, 1.0).unwrap();
284        let point_c = DirectPosition::new(1.0, 1.0, 1.0).unwrap();
285        let point_d = DirectPosition::new(0.0, 1.0, 1.0).unwrap();
286        let linear_ring = LinearRing::new([point_a, point_b, point_c, point_d]).unwrap();
287        let linear_ring = RingProperty::new(RingKind::LinearRing(linear_ring));
288        let polygon = Polygon::new(Some(linear_ring), []).unwrap();
289        let normal = polygon.normal();
290
291        assert_eq!(normal, Vector3::new(0.0, 0.0, 1.0));
292    }
293
294    #[test]
295    fn basic_plane_equation() {
296        let point_a = DirectPosition::new(0.0, 0.0, 1.0).unwrap();
297        let point_b = DirectPosition::new(1.0, 0.0, 1.0).unwrap();
298        let point_c = DirectPosition::new(1.0, 1.0, 1.0).unwrap();
299        let point_d = DirectPosition::new(0.0, 1.0, 1.0).unwrap();
300        let linear_ring = LinearRing::new([point_a, point_b, point_c, point_d]).unwrap();
301        let linear_ring = RingProperty::new(RingKind::LinearRing(linear_ring));
302        let polygon = Polygon::new(Some(linear_ring), []).unwrap();
303        let plane_equation = polygon.plane_equation();
304
305        assert_eq!(
306            plane_equation.point,
307            DirectPosition::new(0.0, 0.0, 1.0).unwrap()
308        );
309        assert_eq!(plane_equation.normal(), Vector3::new(0.0, 0.0, 1.0));
310    }
311
312    #[test]
313    fn test_polygon_triangulation() {
314        let linear_ring_exterior = LinearRing::new([
315            DirectPosition::new(0.0, 0.0, 0.0).expect("should work"),
316            DirectPosition::new(1.0, 0.0, 0.0).expect("should work"),
317            DirectPosition::new(1.0, 1.0, 2.0).expect("should work"),
318            DirectPosition::new(0.0, 1.0, 2.0).expect("should work"),
319        ])
320        .expect("should work");
321        let linear_ring_exterior = RingProperty::new(RingKind::LinearRing(linear_ring_exterior));
322
323        let polygon = Polygon::new(Some(linear_ring_exterior), vec![]).expect("should work");
324        let triangulated_surface = polygon.triangulate().expect("should work");
325        assert_eq!(triangulated_surface.patches_len(), 2);
326    }
327
328    #[test]
329    fn test_polygon_with_interior_triangulation() {
330        let linear_ring_exterior = LinearRing::new([
331            DirectPosition::new(0.0, 0.0, 0.0).expect("should work"),
332            DirectPosition::new(1.0, 0.0, 0.0).expect("should work"),
333            DirectPosition::new(1.0, 1.0, 2.0).expect("should work"),
334            DirectPosition::new(0.0, 1.0, 2.0).expect("should work"),
335            DirectPosition::new(0.0, 1.0, 3.0).expect("should work"),
336            DirectPosition::new(0.0, 1.0, 5.0).expect("should work"),
337        ])
338        .expect("should work");
339        let linear_ring_exterior = RingProperty::new(RingKind::LinearRing(linear_ring_exterior));
340
341        let linear_ring_interior = LinearRing::new([
342            DirectPosition::new(0.5, 0.0, 0.0).expect("should work"),
343            DirectPosition::new(1.0, 0.0, 0.0).expect("should work"),
344            DirectPosition::new(1.0, 1.0, 2.0).expect("should work"),
345            DirectPosition::new(0.5, 1.0, 2.0).expect("should work"),
346        ])
347        .expect("should work");
348        let linear_ring_interior = RingProperty::new(RingKind::LinearRing(linear_ring_interior));
349
350        let polygon = Polygon::new(
351            Some(linear_ring_exterior),
352            vec![linear_ring_interior.clone(), linear_ring_interior.clone()],
353        )
354        .expect("should work");
355        let triangulated_surface = polygon.triangulate().expect("should work");
356        // assert_eq!(triangulated_surface.patches_len(), 2);
357    }
358}