Skip to main content

egml_core/model/geometry/aggregates/
multi_curve.rs

1use crate::model::geometry::Envelope;
2use crate::model::geometry::aggregates::{
3    AbstractGeometricAggregate, AsAbstractGeometricAggregate, AsAbstractGeometricAggregateMut,
4};
5use crate::model::geometry::primitives::CurveProperty;
6use crate::{Error, impl_abstract_geometric_aggregate_traits};
7use nalgebra::Isometry3;
8use rayon::iter::IntoParallelRefMutIterator;
9use rayon::iter::ParallelIterator;
10
11/// An unordered collection of [`CurveKind`] members.
12///
13/// Corresponds to `gml:MultiCurve` in [OGC 07-036 §11.3.3.1](https://docs.ogc.org/is/07-036/07-036.pdf).
14#[derive(Debug, Clone, PartialEq)]
15pub struct MultiCurve {
16    pub(crate) abstract_geometric_aggregate: AbstractGeometricAggregate,
17    curve_member: Vec<CurveProperty>,
18}
19
20impl MultiCurve {
21    /// Creates a new `MultiCurve` from an ordered list of curve members.
22    ///
23    /// # Errors
24    ///
25    /// Returns [`Error::TooFewElements`] if `members` is empty.
26    pub fn new(members: impl IntoIterator<Item = CurveProperty>) -> Result<Self, Error> {
27        let members: Vec<CurveProperty> = members.into_iter().collect();
28        if members.is_empty() {
29            return Err(Error::TooFewElements {
30                geometry: "gml:MultiCurve",
31                minimum: 1,
32                spec: Some("OGC 07-036 §11.3.3.1"),
33                id: None,
34                detail: None,
35            });
36        }
37
38        Ok(Self {
39            abstract_geometric_aggregate: AbstractGeometricAggregate::default(),
40            curve_member: members,
41        })
42    }
43
44    /// Returns the curve members as a slice.
45    pub fn curve_member(&self) -> &[CurveProperty] {
46        &self.curve_member
47    }
48
49    /// Replaces the curve members.
50    ///
51    /// # Errors
52    ///
53    /// Returns [`Error::TooFewElements`] if `val` is empty.
54    pub fn set_curve_member(&mut self, val: Vec<CurveProperty>) -> Result<(), Error> {
55        if val.is_empty() {
56            return Err(Error::TooFewElements {
57                geometry: "gml:MultiCurve",
58                minimum: 1,
59                spec: Some("OGC 07-036 §11.3.3.1"),
60                id: None,
61                detail: None,
62            });
63        }
64        self.curve_member = val;
65        Ok(())
66    }
67
68    pub fn push_curve_member(&mut self, member: CurveProperty) {
69        self.curve_member.push(member);
70    }
71
72    pub fn extend_curve_members(&mut self, members: impl IntoIterator<Item = CurveProperty>) {
73        self.curve_member.extend(members);
74    }
75}
76
77impl MultiCurve {
78    /// Returns the total 3D length of all curve members.
79    ///
80    /// # Errors
81    ///
82    /// Returns [`Error::UnresolvedCurveReference`] if any member carries only an
83    /// xlink:href that has not been resolved into an inline object.
84    pub fn length_3d(&self) -> Result<f64, Error> {
85        self.curve_member
86            .iter()
87            .map(|c| {
88                c.object
89                    .as_ref()
90                    .ok_or_else(|| Error::UnresolvedCurveReference {
91                        href: c.href.clone(),
92                    })
93                    .map(|curve| curve.length_3d())
94            })
95            .collect::<Result<Vec<f64>, Error>>()
96            .map(|lengths| lengths.into_iter().sum())
97    }
98
99    pub fn apply_transform(&mut self, m: &Isometry3<f64>) {
100        self.curve_member.par_iter_mut().for_each(|p| {
101            if let Some(object) = &mut p.object {
102                object.apply_transform(m);
103            }
104        });
105    }
106
107    /// Returns the union of the bounding boxes of all curve members.
108    pub fn compute_envelope(&self) -> Option<Envelope> {
109        let envelopes: Vec<Envelope> = self
110            .curve_member
111            .iter()
112            .flat_map(|x| x.object.as_ref())
113            .map(|x| x.compute_envelope())
114            .collect();
115
116        Envelope::from_envelopes(&envelopes)
117    }
118}
119
120impl AsAbstractGeometricAggregate for MultiCurve {
121    fn abstract_geometric_aggregate(&self) -> &AbstractGeometricAggregate {
122        &self.abstract_geometric_aggregate
123    }
124}
125
126impl AsAbstractGeometricAggregateMut for MultiCurve {
127    fn abstract_geometric_aggregate_mut(&mut self) -> &mut AbstractGeometricAggregate {
128        &mut self.abstract_geometric_aggregate
129    }
130}
131
132impl_abstract_geometric_aggregate_traits!(MultiCurve);
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::Error;
138    use crate::model::geometry::DirectPosition;
139    use crate::model::geometry::primitives::{CurveKind, LineString};
140
141    fn line_string(points: Vec<DirectPosition>) -> CurveProperty {
142        CurveProperty::new(CurveKind::LineString(LineString::new(points).unwrap()))
143    }
144
145    #[test]
146    fn length_3d_two_segments() {
147        // Two unit segments along X — total length 2.
148        let multi_curve = MultiCurve::new([
149            line_string(vec![
150                DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
151                DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
152            ]),
153            line_string(vec![
154                DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
155                DirectPosition::new(2.0, 0.0, 0.0).unwrap(),
156            ]),
157        ])
158        .unwrap();
159        assert!((multi_curve.length_3d().unwrap() - 2.0).abs() < 1e-10);
160    }
161
162    #[test]
163    fn length_3d_unresolved_curve_reference() {
164        let multi_curve =
165            MultiCurve::new([CurveProperty::new_href("urn:example:curve-1")]).unwrap();
166        assert_eq!(
167            multi_curve.length_3d(),
168            Err(Error::UnresolvedCurveReference {
169                href: Some("urn:example:curve-1".to_string())
170            })
171        );
172    }
173}