egml_core/model/geometry/primitives/
shell.rs1use crate::model::base::HasAssociationAttributes;
2use crate::model::common::{
3 ApplyTransform, ComputeEnvelope, IterGeometries, Triangulate, Triangulation,
4};
5use crate::model::geometry::primitives::{
6 AbstractSurface, AbstractSurfaceProperty, AsAbstractSurface, AsAbstractSurfaceMut,
7 TriangulatedSurface,
8};
9use crate::model::geometry::refs::AbstractGeometryKindRef;
10use crate::model::geometry::{DirectPosition, Envelope};
11use crate::{
12 Error, impl_abstract_surface_mut_traits, impl_abstract_surface_traits, impl_has_geometry_type,
13};
14use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
15use rayon::iter::IntoParallelRefMutIterator;
16use rayon::iter::ParallelIterator;
17
18#[derive(Debug, Clone, PartialEq)]
19pub struct Shell {
20 abstract_surface: AbstractSurface,
21 members: Vec<AbstractSurfaceProperty>,
22}
23
24impl Shell {
25 pub fn new(members: impl IntoIterator<Item = AbstractSurfaceProperty>) -> Result<Self, Error> {
26 let members: Vec<AbstractSurfaceProperty> = members.into_iter().collect();
27 Self::validate(&members)?;
28
29 Ok(Self {
30 abstract_surface: AbstractSurface::default(),
31 members,
32 })
33 }
34
35 pub fn from_abstract_surface(
36 abstract_surface: AbstractSurface,
37 members: impl IntoIterator<Item = AbstractSurfaceProperty>,
38 ) -> Result<Self, Error> {
39 let members: Vec<AbstractSurfaceProperty> = members.into_iter().collect();
40 Self::validate(&members)?;
41
42 Ok(Self {
43 abstract_surface,
44 members,
45 })
46 }
47
48 fn validate(members: &[AbstractSurfaceProperty]) -> Result<(), Error> {
49 if members.is_empty() {
50 return Err(Error::TooFewElements {
51 geometry: "gml:Solid",
52 minimum: 1,
53 spec: Some("OGC 07-036 §10.6.4"),
54 id: None,
55 detail: None,
56 });
57 }
58 Ok(())
59 }
60
61 pub fn members(&self) -> &[AbstractSurfaceProperty] {
63 &self.members
64 }
65
66 pub fn set_members(&mut self, val: Vec<AbstractSurfaceProperty>) -> Result<(), Error> {
72 Self::validate(&val)?;
73
74 self.members = val;
75 Ok(())
76 }
77
78 pub fn push_member(&mut self, member: AbstractSurfaceProperty) {
79 self.members.push(member);
80 }
81
82 pub fn extend_members(&mut self, members: impl IntoIterator<Item = AbstractSurfaceProperty>) {
83 self.members.extend(members);
84 }
85}
86
87impl AsAbstractSurface for Shell {
88 fn abstract_surface(&self) -> &AbstractSurface {
89 &self.abstract_surface
90 }
91}
92
93impl AsAbstractSurfaceMut for Shell {
94 fn abstract_surface_mut(&mut self) -> &mut AbstractSurface {
95 &mut self.abstract_surface
96 }
97}
98
99impl_abstract_surface_traits!(Shell);
100impl_abstract_surface_mut_traits!(Shell);
101impl_has_geometry_type!(Shell, Shell);
102
103impl IterGeometries for Shell {
104 fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
105 Box::new(
106 std::iter::once(self.into()).chain(
107 self.members
108 .iter()
109 .filter_map(|x| x.object())
110 .flat_map(|x| x.iter_geometries()),
111 ),
112 )
113 }
114}
115
116impl Shell {
117 pub fn points(&self) -> Vec<&DirectPosition> {
118 self.members
119 .iter()
120 .flat_map(|x| x.object())
121 .fold(Vec::new(), |mut acc, x| {
122 acc.extend(x.points().iter());
123 acc
124 })
125 }
126
127 pub fn area_3d(&self) -> Result<f64, Error> {
135 self.members
136 .iter()
137 .map(|s| {
138 s.object()
139 .ok_or_else(|| Error::UnresolvedSurfaceReference {
140 href: s.href().map(|h| h.to_string()),
141 })
142 .and_then(|kind| kind.area_3d())
143 })
144 .collect::<Result<Vec<f64>, Error>>()
145 .map(|area_3ds| area_3ds.into_iter().sum())
146 }
147
148 pub fn volume_3d(&self) -> Result<f64, Error> {
157 let triangulation = self.triangulate()?;
158 let triangles = triangulation.surface().triangles();
159 let Some(first) = triangles.first() else {
160 return Ok(0.0);
161 };
162
163 let origin: Vector3<f64> = first.a().into();
166 let signed_vol: f64 = triangles
167 .iter()
168 .map(|t| {
169 let a: Vector3<f64> = Into::<Vector3<f64>>::into(t.a()) - origin;
170 let b: Vector3<f64> = Into::<Vector3<f64>>::into(t.b()) - origin;
171 let c: Vector3<f64> = Into::<Vector3<f64>>::into(t.c()) - origin;
172 a.dot(&b.cross(&c))
173 })
174 .sum();
175 Ok(signed_vol.abs() / 6.0)
176 }
177}
178
179impl ApplyTransform for Shell {
180 fn apply_transform(&mut self, transform: Transform3<f64>) {
181 self.members.par_iter_mut().for_each(|p| {
182 if let Some(x) = p.object_mut() {
183 x.apply_transform(transform);
184 }
185 });
186 }
187
188 fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
189 self.members.par_iter_mut().for_each(|p| {
190 if let Some(x) = p.object_mut() {
191 x.apply_isometry(isometry);
192 }
193 });
194 }
195
196 fn apply_translation(&mut self, vector: Vector3<f64>) {
197 self.members.par_iter_mut().for_each(|p| {
198 if let Some(x) = p.object_mut() {
199 x.apply_translation(vector);
200 }
201 });
202 }
203
204 fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
205 self.members.par_iter_mut().for_each(|p| {
206 if let Some(x) = p.object_mut() {
207 x.apply_rotation(rotation);
208 }
209 });
210 }
211
212 fn apply_scale(&mut self, scale: Scale3<f64>) {
213 self.members.par_iter_mut().for_each(|p| {
214 if let Some(x) = p.object_mut() {
215 x.apply_scale(scale);
216 }
217 });
218 }
219}
220
221impl ComputeEnvelope for Shell {
222 fn compute_envelope(&self) -> Option<Envelope> {
224 let envelopes: Vec<Envelope> = self
225 .members
226 .iter()
227 .flat_map(|x| x.object())
228 .flat_map(|x| x.compute_envelope())
229 .collect();
230
231 Envelope::from_envelopes(&envelopes)
232 }
233}
234
235impl Triangulate for Shell {
236 fn triangulate(&self) -> Result<Triangulation, Error> {
244 let mut surfaces = Vec::new();
245 let mut skipped = Vec::new();
246
247 for member in self.members.iter().flat_map(|x| x.object()) {
248 match member.triangulate() {
249 Ok(triangulation) => {
250 let (surface, nested_skipped) = triangulation.into_parts();
251 surfaces.push(surface);
252 skipped.extend(nested_skipped);
253 }
254 Err(error) => {
255 skipped.push(error);
256 }
257 }
258 }
259
260 let combined = TriangulatedSurface::from_triangulated_surfaces(surfaces)?;
261 Ok(Triangulation::new(combined, skipped))
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use crate::model::geometry::primitives::{
269 AbstractRingKind, AbstractSurfaceKind, AbstractSurfacePatchKind, LinearRing, Polygon,
270 PolygonPatch, Surface,
271 };
272
273 fn square(corners: [[f64; 3]; 4]) -> AbstractSurfaceProperty {
274 let ring = LinearRing::new(corners.map(|[x, y, z]| DirectPosition::new(x, y, z).unwrap()))
275 .unwrap();
276 let polygon = Polygon::new(Some(AbstractRingKind::LinearRing(ring)), vec![]).unwrap();
277 AbstractSurfaceProperty::from_object(AbstractSurfaceKind::Polygon(polygon))
278 }
279
280 fn unit_cube_shell() -> Shell {
281 Shell::new([
282 square([[0., 0., 0.], [0., 1., 0.], [1., 1., 0.], [1., 0., 0.]]),
283 square([[0., 0., 1.], [1., 0., 1.], [1., 1., 1.], [0., 1., 1.]]),
284 square([[0., 0., 0.], [1., 0., 0.], [1., 0., 1.], [0., 0., 1.]]),
285 square([[0., 1., 0.], [0., 1., 1.], [1., 1., 1.], [1., 1., 0.]]),
286 square([[0., 0., 0.], [0., 0., 1.], [0., 1., 1.], [0., 1., 0.]]),
287 square([[1., 0., 0.], [1., 1., 0.], [1., 1., 1.], [1., 0., 1.]]),
288 ])
289 .unwrap()
290 }
291
292 #[test]
293 fn area_3d_unit_cube() {
294 assert!((unit_cube_shell().area_3d().unwrap() - 6.0).abs() < 1e-10);
295 }
296
297 #[test]
298 fn area_3d_via_abstract_surface_kind() {
299 let kind = AbstractSurfaceKind::Shell(unit_cube_shell());
300 assert!((kind.area_3d().unwrap() - 6.0).abs() < 1e-10);
301 }
302
303 #[test]
304 fn triangulate_skips_member_without_exterior_ring() {
305 let mut shell = unit_cube_shell();
306 let empty = Polygon::new(None, []).unwrap();
307 shell.push_member(AbstractSurfaceProperty::from_object(
308 AbstractSurfaceKind::Polygon(empty),
309 ));
310
311 let triangulation = shell.triangulate().unwrap();
312 assert_eq!(triangulation.skipped(), &[Error::MissingExteriorRing]);
313 assert!((shell.volume_3d().unwrap() - 1.0).abs() < 1e-10);
314 }
315
316 #[test]
317 fn points_with_surface_member() {
318 let ring = LinearRing::new([
319 DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
320 DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
321 DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
322 ])
323 .unwrap();
324 let patch = PolygonPatch::new(Some(AbstractRingKind::LinearRing(ring)), []);
325 let surface = Surface::new(vec![AbstractSurfacePatchKind::PolygonPatch(patch)]);
326 let shell = Shell::new([AbstractSurfaceProperty::from_object(
327 AbstractSurfaceKind::Surface(surface),
328 )])
329 .unwrap();
330 assert_eq!(shell.points().len(), 3);
331 }
332
333 #[test]
334 fn area_3d_unresolved_surface_reference() {
335 let shell = Shell::new([AbstractSurfaceProperty::from_href(
336 "urn:example:surface-1".into(),
337 )])
338 .unwrap();
339 assert_eq!(
340 shell.area_3d(),
341 Err(Error::UnresolvedSurfaceReference {
342 href: Some("urn:example:surface-1".to_string())
343 })
344 );
345 }
346}