Skip to main content

egml_core/model/geometry/aggregates/
multi_surface.rs

1use crate::error::Error;
2use crate::impl_abstract_geometric_aggregate_traits;
3use crate::model::geometry::aggregates::{
4    AbstractGeometricAggregate, AsAbstractGeometricAggregate, AsAbstractGeometricAggregateMut,
5};
6use crate::model::geometry::primitives::SurfaceProperty;
7use crate::model::geometry::primitives::TriangulatedSurface;
8use crate::model::geometry::{DirectPosition, Envelope};
9use nalgebra::Isometry3;
10use rayon::prelude::*;
11
12/// An unordered collection of [`SurfaceKind`] members.
13///
14/// Corresponds to `gml:MultiSurface` in [OGC 07-036 §11.3.4.1](https://docs.ogc.org/is/07-036/07-036.pdf).
15#[derive(Debug, Clone, PartialEq)]
16pub struct MultiSurface {
17    pub(crate) abstract_geometric_aggregate: AbstractGeometricAggregate,
18    surface_member: Vec<SurfaceProperty>,
19}
20
21impl MultiSurface {
22    /// Creates a new `MultiSurface` from a list of surface members.
23    ///
24    /// # Errors
25    ///
26    /// Returns [`Error::TooFewElements`] if `members` is empty.
27    pub fn new(members: impl IntoIterator<Item = SurfaceProperty>) -> Result<Self, Error> {
28        let members: Vec<SurfaceProperty> = members.into_iter().collect();
29        if members.is_empty() {
30            return Err(Error::TooFewElements {
31                geometry: "gml:MultiSurface",
32                minimum: 1,
33                spec: Some("OGC 07-036 §11.3.4.1"),
34                id: None,
35                detail: None,
36            });
37        }
38
39        Ok(Self {
40            abstract_geometric_aggregate: AbstractGeometricAggregate::default(),
41            surface_member: members,
42        })
43    }
44
45    /// Returns the surface members as a slice.
46    pub fn surface_member(&self) -> &[SurfaceProperty] {
47        &self.surface_member
48    }
49
50    /// Replaces the surface members.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`Error::TooFewElements`] if `val` is empty.
55    pub fn set_surface_member(&mut self, val: Vec<SurfaceProperty>) -> Result<(), Error> {
56        if val.is_empty() {
57            return Err(Error::TooFewElements {
58                geometry: "gml:MultiSurface",
59                minimum: 1,
60                spec: Some("OGC 07-036 §11.3.4.1"),
61                id: None,
62                detail: None,
63            });
64        }
65        self.surface_member = val;
66        Ok(())
67    }
68
69    pub fn push_surface_member(&mut self, member: SurfaceProperty) {
70        self.surface_member.push(member);
71    }
72
73    pub fn extend_surface_members(&mut self, members: impl IntoIterator<Item = SurfaceProperty>) {
74        self.surface_member.extend(members);
75    }
76}
77
78impl MultiSurface {
79    /// Triangulates all surface members and merges them into a single [`TriangulatedSurface`].
80    ///
81    /// # Errors
82    ///
83    /// Returns [`Error::TriangulationFailed`] if any member cannot be triangulated.
84    pub fn triangulate(&self) -> Result<TriangulatedSurface, Error> {
85        let triangulated_surfaces: Vec<TriangulatedSurface> = self
86            .surface_member
87            .iter()
88            .flat_map(|x| x.object.as_ref())
89            .map(|x| x.triangulate())
90            .collect::<Result<Vec<TriangulatedSurface>, Error>>()?;
91
92        let combined_triangulated_surface =
93            TriangulatedSurface::from_triangulated_surfaces(triangulated_surfaces)?;
94        Ok(combined_triangulated_surface)
95    }
96
97    pub fn points(&self) -> Vec<&DirectPosition> {
98        self.surface_member
99            .iter()
100            .flat_map(|x| x.object.as_ref())
101            .fold(Vec::new(), |mut acc, x| {
102                acc.extend(x.points().iter());
103                acc
104            })
105    }
106
107    /// Returns the total 3D area_3d of all surface members.
108    ///
109    /// # Errors
110    ///
111    /// Returns [`Error::UnresolvedSurfaceReference`] if any member carries only an
112    /// xlink:href that has not been resolved into an inline object.
113    /// Propagates any error from [`SurfaceKind::area_3d`].
114    pub fn area_3d(&self) -> Result<f64, Error> {
115        self.surface_member
116            .iter()
117            .map(|s| {
118                s.object
119                    .as_ref()
120                    .ok_or_else(|| Error::UnresolvedSurfaceReference {
121                        href: s.href.clone(),
122                    })
123                    .and_then(|kind| kind.area_3d())
124            })
125            .collect::<Result<Vec<f64>, Error>>()
126            .map(|area_3ds| area_3ds.into_iter().sum())
127    }
128
129    pub fn apply_transform(&mut self, m: &Isometry3<f64>) {
130        self.surface_member.par_iter_mut().for_each(|p| {
131            if let Some(x) = p.object.as_mut() {
132                x.apply_transform(m);
133            }
134        });
135    }
136
137    /// Returns the union of the bounding boxes of all surface members.
138    pub fn compute_envelope(&self) -> Option<Envelope> {
139        let envelopes: Vec<Envelope> = self
140            .surface_member
141            .iter()
142            .flat_map(|x| x.object.as_ref())
143            .flat_map(|x| x.compute_envelope())
144            .collect();
145
146        Envelope::from_envelopes(&envelopes)
147    }
148}
149
150impl AsAbstractGeometricAggregate for MultiSurface {
151    fn abstract_geometric_aggregate(&self) -> &AbstractGeometricAggregate {
152        &self.abstract_geometric_aggregate
153    }
154}
155
156impl AsAbstractGeometricAggregateMut for MultiSurface {
157    fn abstract_geometric_aggregate_mut(&mut self) -> &mut AbstractGeometricAggregate {
158        &mut self.abstract_geometric_aggregate
159    }
160}
161
162impl_abstract_geometric_aggregate_traits!(MultiSurface);
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::Error;
168    use crate::model::geometry::DirectPosition;
169    use crate::model::geometry::primitives::{LinearRing, RingKind, RingProperty, SurfaceKind};
170
171    fn unit_square_polygon(z: f64) -> SurfaceKind {
172        let ring = LinearRing::new([
173            DirectPosition::new(0.0, 0.0, z).unwrap(),
174            DirectPosition::new(1.0, 0.0, z).unwrap(),
175            DirectPosition::new(1.0, 1.0, z).unwrap(),
176            DirectPosition::new(0.0, 1.0, z).unwrap(),
177        ])
178        .unwrap();
179        let polygon = crate::model::geometry::primitives::Polygon::new(
180            Some(RingProperty::new(RingKind::LinearRing(ring))),
181            vec![],
182        )
183        .unwrap();
184        SurfaceKind::Polygon(polygon)
185    }
186
187    #[test]
188    fn area_3d_two_unit_squares() {
189        let multi_surface = MultiSurface::new([
190            SurfaceProperty::new(unit_square_polygon(0.0)),
191            SurfaceProperty::new(unit_square_polygon(1.0)),
192        ])
193        .unwrap();
194        assert!((multi_surface.area_3d().unwrap() - 2.0).abs() < 1e-10);
195    }
196
197    #[test]
198    fn area_3d_unresolved_surface_reference() {
199        let multi_surface = MultiSurface::new([SurfaceProperty::new_href(
200            "urn:example:surface-1".to_string(),
201        )])
202        .unwrap();
203        assert_eq!(
204            multi_surface.area_3d(),
205            Err(Error::UnresolvedSurfaceReference {
206                href: Some("urn:example:surface-1".to_string())
207            })
208        );
209    }
210}