Skip to main content

egml_core/model/geometry/complexes/
composite_surface.rs

1use crate::model::base::HasAssociationAttributes;
2use crate::model::common::{
3    ApplyTransform, ComputeEnvelope, IterGeometries, Triangulate, Triangulation,
4};
5use crate::model::geometry::aggregates::AggregationType;
6use crate::model::geometry::primitives::{
7    AbstractSurface, AbstractSurfaceProperty, AsAbstractSurface, AsAbstractSurfaceMut,
8    TriangulatedSurface,
9};
10use crate::model::geometry::refs::AbstractGeometryKindRef;
11use crate::model::geometry::{DirectPosition, Envelope};
12use crate::{
13    Error, impl_abstract_surface_mut_traits, impl_abstract_surface_traits, impl_has_geometry_type,
14};
15use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
16use rayon::iter::IntoParallelRefMutIterator;
17use rayon::iter::ParallelIterator;
18
19/// A topology-aware surface composed of connected [`AbstractSurfaceProperty`] members.
20///
21/// Corresponds to `gml:CompositeSurface` in [OGC 07-036 §11.2.2.3](https://docs.ogc.org/is/07-036/07-036.pdf).  Unlike
22/// [`MultiSurface`](crate::model::geometry::aggregates::MultiSurface), a
23/// `CompositeSurface` requires that its constituent surfaces share boundaries
24/// coherently, forming a single connected manifold.
25#[derive(Debug, Clone, PartialEq)]
26pub struct CompositeSurface {
27    pub abstract_surface: AbstractSurface,
28    surface_member: Vec<AbstractSurfaceProperty>,
29    aggregation_type: AggregationType,
30}
31
32impl CompositeSurface {
33    /// Creates a new `CompositeSurface` from surface members and an aggregation type.
34    ///
35    /// # Errors
36    ///
37    /// Returns [`Error::TooFewElements`] if `surface_members` is empty.
38    pub fn new(
39        surface_member: impl IntoIterator<Item = AbstractSurfaceProperty>,
40        aggregation_type: AggregationType,
41    ) -> Result<Self, Error> {
42        let surface_member: Vec<AbstractSurfaceProperty> = surface_member.into_iter().collect();
43        Self::validate(&surface_member)?;
44
45        Ok(CompositeSurface {
46            abstract_surface: AbstractSurface::default(),
47            surface_member,
48            aggregation_type,
49        })
50    }
51
52    pub fn from_abstract_surface(
53        abstract_surface: AbstractSurface,
54        surface_member: impl IntoIterator<Item = AbstractSurfaceProperty>,
55        aggregation_type: AggregationType,
56    ) -> Result<Self, Error> {
57        let surface_member: Vec<AbstractSurfaceProperty> = surface_member.into_iter().collect();
58        Self::validate(&surface_member)?;
59
60        Ok(Self {
61            abstract_surface,
62            surface_member,
63            aggregation_type,
64        })
65    }
66
67    fn validate(members: &[AbstractSurfaceProperty]) -> Result<(), Error> {
68        if members.is_empty() {
69            return Err(Error::TooFewElements {
70                geometry: "gml:CompositeSurface",
71                minimum: 1,
72                spec: Some("OGC 07-036 §10.5.11.4"),
73                id: None,
74                detail: None,
75            });
76        }
77        Ok(())
78    }
79
80    /// Returns the surface members as a slice.
81    pub fn surface_member(&self) -> &[AbstractSurfaceProperty] {
82        &self.surface_member
83    }
84
85    /// Replaces the surface members.
86    ///
87    /// # Errors
88    ///
89    /// Returns [`Error::TooFewElements`] if `surface_members` is empty.
90    pub fn set_surface_member(
91        &mut self,
92        surface_members: Vec<AbstractSurfaceProperty>,
93    ) -> Result<(), Error> {
94        Self::validate(&surface_members)?;
95        self.surface_member = surface_members;
96        Ok(())
97    }
98
99    pub fn push_surface_member(&mut self, member: AbstractSurfaceProperty) {
100        self.surface_member.push(member);
101    }
102
103    pub fn extend_surface_members(
104        &mut self,
105        members: impl IntoIterator<Item = AbstractSurfaceProperty>,
106    ) {
107        self.surface_member.extend(members);
108    }
109
110    /// Returns the aggregation type that qualifies how members relate.
111    pub fn aggregation_type(&self) -> AggregationType {
112        self.aggregation_type
113    }
114
115    pub fn set_aggregation_type(&mut self, aggregation_type: AggregationType) {
116        self.aggregation_type = aggregation_type;
117    }
118}
119
120impl AsAbstractSurface for CompositeSurface {
121    fn abstract_surface(&self) -> &AbstractSurface {
122        &self.abstract_surface
123    }
124}
125
126impl AsAbstractSurfaceMut for CompositeSurface {
127    fn abstract_surface_mut(&mut self) -> &mut AbstractSurface {
128        &mut self.abstract_surface
129    }
130}
131
132impl_abstract_surface_traits!(CompositeSurface);
133impl_abstract_surface_mut_traits!(CompositeSurface);
134impl_has_geometry_type!(CompositeSurface, CompositeSurface);
135
136impl CompositeSurface {
137    /// Returns the number of surface members.
138    pub fn surface_member_count(&self) -> usize {
139        self.surface_member.len()
140    }
141
142    pub fn area_3d(&self) -> Result<f64, Error> {
143        self.surface_member
144            .iter()
145            .map(|s| {
146                s.object()
147                    .ok_or_else(|| Error::UnresolvedSurfaceReference {
148                        href: s.href().map(|h| h.to_string()),
149                    })
150                    .and_then(|kind| kind.area_3d())
151            })
152            .collect::<Result<Vec<f64>, Error>>()
153            .map(|area_3ds| area_3ds.into_iter().sum())
154    }
155
156    /// Returns the positions of all resolved surface members, in member order.
157    ///
158    /// Members that carry only an unresolved xlink:href are skipped.
159    pub fn points(&self) -> Vec<&DirectPosition> {
160        self.surface_member
161            .iter()
162            .flat_map(|s| s.object())
163            .flat_map(|kind| kind.points())
164            .collect()
165    }
166}
167
168impl ApplyTransform for CompositeSurface {
169    fn apply_transform(&mut self, transform: Transform3<f64>) {
170        self.surface_member
171            .par_iter_mut()
172            .flat_map(|x| x.object_mut())
173            .for_each(|x| x.apply_transform(transform));
174    }
175
176    fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
177        self.surface_member
178            .par_iter_mut()
179            .flat_map(|x| x.object_mut())
180            .for_each(|x| x.apply_isometry(isometry));
181    }
182
183    fn apply_translation(&mut self, vector: Vector3<f64>) {
184        self.surface_member
185            .par_iter_mut()
186            .flat_map(|x| x.object_mut())
187            .for_each(|x| x.apply_translation(vector));
188    }
189
190    fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
191        self.surface_member
192            .par_iter_mut()
193            .flat_map(|x| x.object_mut())
194            .for_each(|x| x.apply_rotation(rotation));
195    }
196
197    fn apply_scale(&mut self, scale: Scale3<f64>) {
198        self.surface_member
199            .par_iter_mut()
200            .flat_map(|x| x.object_mut())
201            .for_each(|x| x.apply_scale(scale));
202    }
203}
204
205impl ComputeEnvelope for CompositeSurface {
206    /// Returns the union of the bounding boxes of all surface members.
207    fn compute_envelope(&self) -> Option<Envelope> {
208        let envelopes: Vec<Envelope> = self
209            .surface_member
210            .iter()
211            .flat_map(|x| x.object())
212            .flat_map(|x| x.compute_envelope())
213            .collect::<Vec<_>>();
214
215        Envelope::from_envelopes(&envelopes)
216    }
217}
218
219impl IterGeometries for CompositeSurface {
220    fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
221        Box::new(
222            std::iter::once(self.into()).chain(
223                self.surface_member
224                    .iter()
225                    .filter_map(|x| x.object())
226                    .flat_map(|x| x.iter_geometries()),
227            ),
228        )
229    }
230}
231
232impl Triangulate for CompositeSurface {
233    /// Members that fail to triangulate individually (e.g. a degenerate ring) are
234    /// skipped rather than failing the whole aggregate; see their errors via
235    /// [`Triangulation::skipped`].
236    ///
237    /// # Errors
238    ///
239    /// Returns [`Error::TooFewElements`] if no member could be triangulated.
240    fn triangulate(&self) -> Result<Triangulation, Error> {
241        let mut surfaces = Vec::new();
242        let mut skipped = Vec::new();
243
244        for member in self.surface_member.iter().flat_map(|x| x.object()) {
245            match member.triangulate() {
246                Ok(triangulation) => {
247                    let (surface, nested_skipped) = triangulation.into_parts();
248                    surfaces.push(surface);
249                    skipped.extend(nested_skipped);
250                }
251                Err(error) => {
252                    skipped.push(error);
253                }
254            }
255        }
256
257        let combined = TriangulatedSurface::from_triangulated_surfaces(surfaces)?;
258        Ok(Triangulation::new(combined, skipped))
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use crate::model::geometry::primitives::{
266        AbstractRingKind, AbstractSurfaceKind, LinearRing, Polygon,
267    };
268
269    fn unit_square(z: f64) -> AbstractSurfaceProperty {
270        let ring = LinearRing::new([
271            DirectPosition::new(0.0, 0.0, z).unwrap(),
272            DirectPosition::new(1.0, 0.0, z).unwrap(),
273            DirectPosition::new(1.0, 1.0, z).unwrap(),
274            DirectPosition::new(0.0, 1.0, z).unwrap(),
275        ])
276        .unwrap();
277        let polygon = Polygon::new(Some(AbstractRingKind::LinearRing(ring)), []).unwrap();
278        AbstractSurfaceProperty::from_object(AbstractSurfaceKind::Polygon(polygon))
279    }
280
281    #[test]
282    fn points_collects_all_members() {
283        let composite =
284            CompositeSurface::new([unit_square(0.0), unit_square(1.0)], AggregationType::Set)
285                .unwrap();
286        let points = composite.points();
287        assert_eq!(points.len(), 8);
288        assert_eq!(points[4], &DirectPosition::new(0.0, 0.0, 1.0).unwrap());
289    }
290
291    #[test]
292    fn points_skips_unresolved_members() {
293        let composite = CompositeSurface::new(
294            [
295                unit_square(0.0),
296                AbstractSurfaceProperty::from_href("urn:example:surface-1".into()),
297            ],
298            AggregationType::Set,
299        )
300        .unwrap();
301        assert_eq!(composite.points().len(), 4);
302    }
303}