egml_core/model/geometry/primitives/
linear_ring.rs

1use crate::error::Error;
2use crate::error::Error::NotEnoughElements;
3
4use crate::Error::{ContainsDuplicateElements, ContainsEqualStartAndLastElement};
5use crate::model::base::AbstractGml;
6use crate::model::geometry::{DirectPosition, Triangle, TriangulatedSurface};
7use crate::operations::geometry::Geometry;
8use crate::operations::surface::Surface;
9use crate::operations::triangulate::Triangulate;
10use nalgebra::Isometry3;
11use rayon::prelude::*;
12
13const MINIMUM_NUMBER_OF_POINTS: usize = 3;
14
15#[derive(Debug, Clone, PartialEq)]
16pub struct LinearRing {
17    pub abstract_gml: AbstractGml,
18    points: Vec<DirectPosition>,
19}
20
21impl LinearRing {
22    pub fn new(abstract_gml: AbstractGml, points: Vec<DirectPosition>) -> Result<Self, Error> {
23        let duplicates_count = points.windows(2).filter(|x| x[0] == x[1]).count();
24        if duplicates_count >= 1 {
25            return Err(ContainsDuplicateElements);
26        }
27        if points.len() < MINIMUM_NUMBER_OF_POINTS {
28            return Err(NotEnoughElements(
29                "Linear ring must at least have three unique points",
30            ));
31        }
32        if points.first().expect("") == points.last().expect("") {
33            return Err(ContainsEqualStartAndLastElement);
34        }
35
36        Ok(Self {
37            abstract_gml,
38            points,
39        })
40    }
41
42    pub fn set_points(&mut self, val: Vec<DirectPosition>) -> Result<(), Error> {
43        let duplicates_count = val.windows(2).filter(|x| x[0] == x[1]).count();
44        if duplicates_count >= 1 {
45            return Err(ContainsDuplicateElements);
46        }
47        if val.len() < MINIMUM_NUMBER_OF_POINTS {
48            return Err(NotEnoughElements(
49                "Linear ring must at least have three unique points",
50            ));
51        }
52        self.points = val;
53        Ok(())
54    }
55}
56
57impl Geometry for LinearRing {
58    fn points(&self) -> Vec<&DirectPosition> {
59        self.points.iter().collect()
60    }
61
62    fn apply_transform(&mut self, m: &Isometry3<f64>) {
63        self.points.iter_mut().for_each(|p| {
64            p.apply_transform(m);
65        });
66        // self.points.dedup(); would need error handling
67    }
68}
69
70impl Surface for LinearRing {
71    fn outer_boundary_points(&self) -> Vec<&DirectPosition> {
72        self.points.iter().collect()
73    }
74}
75
76impl Triangulate for LinearRing {
77    fn triangulate(&self) -> Result<TriangulatedSurface, Error> {
78        let vertices_3d = self.points.iter().map(|p| p.coords()).collect::<Vec<_>>();
79        let mut vertices_2d_buf = Vec::new();
80        earcut::utils3d::project3d_to_2d(&vertices_3d, vertices_3d.len(), &mut vertices_2d_buf);
81
82        let mut triangle_indices: Vec<usize> = vec![];
83        let mut earcut = earcut::Earcut::new();
84        earcut.earcut(vertices_2d_buf.iter().copied(), &[], &mut triangle_indices);
85
86        let triangles: Vec<Triangle> = triangle_indices
87            .chunks(3)
88            .map(|x| {
89                let vertex_a = self.points[x[0]];
90                let vertex_b = self.points[x[1]];
91                let vertex_c = self.points[x[2]];
92                Triangle::new(vertex_a, vertex_b, vertex_c).expect("should work")
93            })
94            .collect::<Vec<_>>();
95
96        let triangulated_surface = TriangulatedSurface::new(triangles)?;
97        Ok(triangulated_surface)
98    }
99}
100
101#[cfg(test)]
102mod test {
103    use super::*;
104    use crate::model::base::Id;
105
106    #[test]
107    fn triangulate() {
108        let abstract_gml = AbstractGml::new(Id::try_from("test_id").expect("must work"));
109        let linear_ring = LinearRing::new(
110            abstract_gml,
111            vec![
112                DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
113                DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
114                DirectPosition::new(1.0, 1.0, 0.0).unwrap(),
115                DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
116            ],
117        )
118        .unwrap();
119
120        let result = &linear_ring.triangulate().unwrap();
121
122        assert_eq!(result.number_of_patches(), 2);
123        assert!(result.patches()[0].area() > 0.0);
124        assert!(result.patches()[1].area() > 0.0);
125    }
126}