egml_core/model/geometry/primitives/
linear_ring.rs1use 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 }
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}