Skip to main content

egml_core/model/geometry/aggregates/
multi_curve.rs

1use crate::model::base::HasAssociationAttributes;
2use crate::model::common::{ApplyTransform, ComputeEnvelope, IterGeometries};
3use crate::model::geometry::Envelope;
4use crate::model::geometry::aggregates::{
5    AbstractGeometricAggregate, AsAbstractGeometricAggregate, AsAbstractGeometricAggregateMut,
6};
7use crate::model::geometry::primitives::AbstractCurveProperty;
8use crate::model::geometry::refs::AbstractGeometryKindRef;
9use crate::{
10    Error, impl_abstract_geometric_aggregate_mut_traits, impl_abstract_geometric_aggregate_traits,
11    impl_has_geometry_type,
12};
13use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector3};
14use rayon::iter::IntoParallelRefMutIterator;
15use rayon::iter::ParallelIterator;
16
17/// An unordered collection of [`CurveKind`] members.
18///
19/// Corresponds to `gml:MultiCurve` in [OGC 07-036 §11.3.3.1](https://docs.ogc.org/is/07-036/07-036.pdf).
20#[derive(Debug, Clone, PartialEq)]
21pub struct MultiCurve {
22    pub abstract_geometric_aggregate: AbstractGeometricAggregate,
23    curve_member: Vec<AbstractCurveProperty>,
24}
25
26impl MultiCurve {
27    /// Creates a new `MultiCurve` from an ordered list of curve members.
28    ///
29    /// # Errors
30    ///
31    /// Returns [`Error::TooFewElements`] if `members` is empty.
32    pub fn new(members: impl IntoIterator<Item = AbstractCurveProperty>) -> Result<Self, Error> {
33        let members: Vec<AbstractCurveProperty> = members.into_iter().collect();
34        Self::validate(&members)?;
35
36        Ok(Self {
37            abstract_geometric_aggregate: AbstractGeometricAggregate::default(),
38            curve_member: members,
39        })
40    }
41
42    pub fn from_abstract_geometric_aggregate(
43        abstract_geometric_aggregate: AbstractGeometricAggregate,
44        members: impl IntoIterator<Item = AbstractCurveProperty>,
45    ) -> Result<Self, Error> {
46        let members: Vec<AbstractCurveProperty> = members.into_iter().collect();
47        Self::validate(&members)?;
48
49        Ok(Self {
50            abstract_geometric_aggregate,
51            curve_member: members,
52        })
53    }
54
55    fn validate(members: &[AbstractCurveProperty]) -> Result<(), Error> {
56        if members.is_empty() {
57            return Err(Error::TooFewElements {
58                geometry: "gml:MultiCurve",
59                minimum: 1,
60                spec: Some("OGC 07-036 §11.3.3.1"),
61                id: None,
62                detail: None,
63            });
64        }
65        Ok(())
66    }
67
68    /// Returns the curve members as a slice.
69    pub fn curve_member(&self) -> &[AbstractCurveProperty] {
70        &self.curve_member
71    }
72
73    /// Replaces the curve members.
74    ///
75    /// # Errors
76    ///
77    /// Returns [`Error::TooFewElements`] if `val` is empty.
78    pub fn set_curve_member(&mut self, val: Vec<AbstractCurveProperty>) -> Result<(), Error> {
79        Self::validate(&val)?;
80        self.curve_member = val;
81        Ok(())
82    }
83
84    pub fn push_curve_member(&mut self, member: AbstractCurveProperty) {
85        self.curve_member.push(member);
86    }
87
88    pub fn extend_curve_members(
89        &mut self,
90        members: impl IntoIterator<Item = AbstractCurveProperty>,
91    ) {
92        self.curve_member.extend(members);
93    }
94}
95
96impl MultiCurve {
97    /// Returns the total 3D length of all curve members.
98    ///
99    /// # Errors
100    ///
101    /// Returns [`Error::UnresolvedCurveReference`] if any member carries only an
102    /// xlink:href that has not been resolved into an inline object.
103    pub fn length_3d(&self) -> Result<f64, Error> {
104        self.curve_member
105            .iter()
106            .map(|c| {
107                c.object()
108                    .ok_or_else(|| Error::UnresolvedCurveReference {
109                        href: c.href().as_ref().map(|h| h.to_string()),
110                    })
111                    .map(|curve| curve.length_3d())
112            })
113            .collect::<Result<Vec<f64>, Error>>()
114            .map(|lengths| lengths.into_iter().sum())
115    }
116}
117
118impl ApplyTransform for MultiCurve {
119    fn apply_transform(&mut self, transform: Transform3<f64>) {
120        self.curve_member.par_iter_mut().for_each(|p| {
121            if let Some(object) = p.object_mut() {
122                object.apply_transform(transform);
123            }
124        });
125    }
126
127    fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
128        self.curve_member.par_iter_mut().for_each(|p| {
129            if let Some(object) = p.object_mut() {
130                object.apply_isometry(isometry);
131            }
132        });
133    }
134
135    fn apply_translation(&mut self, vector: Vector3<f64>) {
136        self.curve_member.par_iter_mut().for_each(|p| {
137            if let Some(object) = p.object_mut() {
138                object.apply_translation(vector);
139            }
140        });
141    }
142
143    fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
144        self.curve_member.par_iter_mut().for_each(|p| {
145            if let Some(object) = p.object_mut() {
146                object.apply_rotation(rotation);
147            }
148        });
149    }
150
151    fn apply_scale(&mut self, scale: Scale3<f64>) {
152        self.curve_member.par_iter_mut().for_each(|p| {
153            if let Some(object) = p.object_mut() {
154                object.apply_scale(scale);
155            }
156        });
157    }
158}
159
160impl ComputeEnvelope for MultiCurve {
161    /// Returns the union of the bounding boxes of all curve members.
162    fn compute_envelope(&self) -> Option<Envelope> {
163        let envelopes: Vec<Envelope> = self
164            .curve_member
165            .iter()
166            .flat_map(|x| x.object())
167            .filter_map(|x| x.compute_envelope())
168            .collect();
169
170        Envelope::from_envelopes(&envelopes)
171    }
172}
173
174impl AsAbstractGeometricAggregate for MultiCurve {
175    fn abstract_geometric_aggregate(&self) -> &AbstractGeometricAggregate {
176        &self.abstract_geometric_aggregate
177    }
178}
179
180impl AsAbstractGeometricAggregateMut for MultiCurve {
181    fn abstract_geometric_aggregate_mut(&mut self) -> &mut AbstractGeometricAggregate {
182        &mut self.abstract_geometric_aggregate
183    }
184}
185
186impl_abstract_geometric_aggregate_traits!(MultiCurve);
187impl_abstract_geometric_aggregate_mut_traits!(MultiCurve);
188impl_has_geometry_type!(MultiCurve, MultiCurve);
189
190impl IterGeometries for MultiCurve {
191    fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
192        Box::new(
193            std::iter::once(self.into()).chain(
194                self.curve_member
195                    .iter()
196                    .filter_map(|x| x.object())
197                    .flat_map(|x| x.iter_geometries()),
198            ),
199        )
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use crate::Error;
207    use crate::model::geometry::DirectPosition;
208    use crate::model::geometry::primitives::{AbstractCurveKind, LineString};
209
210    fn line_string(points: Vec<DirectPosition>) -> AbstractCurveProperty {
211        AbstractCurveProperty::from_object(AbstractCurveKind::LineString(
212            LineString::new(points).unwrap(),
213        ))
214    }
215
216    #[test]
217    fn length_3d_two_segments() {
218        // Two unit segments along X — total length 2.
219        let multi_curve = MultiCurve::new([
220            line_string(vec![
221                DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
222                DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
223            ]),
224            line_string(vec![
225                DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
226                DirectPosition::new(2.0, 0.0, 0.0).unwrap(),
227            ]),
228        ])
229        .unwrap();
230        assert!((multi_curve.length_3d().unwrap() - 2.0).abs() < 1e-10);
231    }
232
233    #[test]
234    fn length_3d_unresolved_curve_reference() {
235        let multi_curve = MultiCurve::new([AbstractCurveProperty::from_href(
236            "urn:example:curve-1".into(),
237        )])
238        .unwrap();
239        assert_eq!(
240            multi_curve.length_3d(),
241            Err(Error::UnresolvedCurveReference {
242                href: Some("urn:example:curve-1".to_string())
243            })
244        );
245    }
246}