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