Skip to main content

egml_core/resolver/
geometry_resolver.rs

1use crate::model::base::{AsAbstractGml, Id};
2use crate::model::common::IterGeometries;
3use crate::model::geometry::refs::AbstractGeometryKindRef;
4use std::collections::HashMap;
5
6/// Resolves `gml:id`/`xlink:href` references to the geometry they point at.
7///
8/// Indexes borrowed [`AbstractGeometryKindRef`]s by [`Id`], so lookups by
9/// reference are O(1) instead of re-walking the geometry tree. Geometries
10/// without an `id` are not indexed — they cannot be referenced by
11/// `xlink:href` in the first place.
12#[derive(Debug, Clone, Default)]
13pub struct GeometryResolver<'a> {
14    by_id: HashMap<Id, AbstractGeometryKindRef<'a>>,
15}
16
17impl<'a> GeometryResolver<'a> {
18    /// Creates an empty resolver.
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Builds a resolver by recursively walking `root` and indexing every
24    /// descendant geometry (including `root` itself) that carries an `id`.
25    ///
26    /// # Examples
27    ///
28    /// ```rust
29    /// use egml_core::model::geometry::DirectPosition;
30    /// use egml_core::model::geometry::primitives::Point;
31    /// use egml_core::model::base::{AsAbstractGmlMut, Id};
32    /// use egml_core::resolver::GeometryResolver;
33    ///
34    /// let mut point = Point::new(DirectPosition::new(1.0, 2.0, 3.0).unwrap());
35    /// point.set_id(Id::try_from("point-1").expect("valid id"));
36    ///
37    /// let resolver = GeometryResolver::build(&point);
38    /// assert!(resolver.resolve(&Id::try_from("point-1").expect("valid id")).is_some());
39    /// ```
40    pub fn build<T: IterGeometries>(root: &'a T) -> Self {
41        let mut resolver = Self::new();
42        resolver.insert_root(root);
43        resolver
44    }
45
46    /// Recursively walks `root` and indexes every descendant geometry
47    /// (including `root` itself) that carries an `id`.
48    ///
49    /// Unlike [`build`](Self::build), this adds to an existing resolver
50    /// instead of creating a new one — call it once per top-level geometry
51    /// when a document has several independent roots (e.g. one per feature
52    /// in a city model) that should all resolve through the same table.
53    ///
54    /// # Examples
55    ///
56    /// ```rust
57    /// use egml_core::model::base::{AsAbstractGmlMut, Id};
58    /// use egml_core::model::geometry::DirectPosition;
59    /// use egml_core::model::geometry::primitives::Point;
60    /// use egml_core::resolver::GeometryResolver;
61    ///
62    /// let mut first = Point::new(DirectPosition::new(1.0, 2.0, 3.0).unwrap());
63    /// first.set_id(Id::try_from("point-1").expect("valid id"));
64    /// let mut second = Point::new(DirectPosition::new(4.0, 5.0, 6.0).unwrap());
65    /// second.set_id(Id::try_from("point-2").expect("valid id"));
66    ///
67    /// let mut resolver = GeometryResolver::new();
68    /// resolver.insert_root(&first);
69    /// resolver.insert_root(&second);
70    ///
71    /// assert_eq!(resolver.len(), 2);
72    /// ```
73    pub fn insert_root<T: IterGeometries>(&mut self, root: &'a T) {
74        self.extend(root.iter_geometries());
75    }
76
77    /// Indexes a single geometry by its `id`, if it has one.
78    ///
79    /// Returns `true` if the geometry had an `id` and was stored. If another
80    /// geometry was already indexed under the same `id`, it is silently
81    /// replaced — `gml:id` is supposed to be unique within a document, so a
82    /// collision indicates malformed input rather than a case this resolver
83    /// needs to arbitrate.
84    pub fn insert(&mut self, geometry: AbstractGeometryKindRef<'a>) -> bool {
85        match geometry.id() {
86            Some(id) => {
87                self.by_id.insert(id.clone(), geometry);
88                true
89            }
90            None => false,
91        }
92    }
93
94    /// Indexes every geometry in `geometries` that carries an `id`.
95    pub fn extend(&mut self, geometries: impl IntoIterator<Item = AbstractGeometryKindRef<'a>>) {
96        for geometry in geometries {
97            self.insert(geometry);
98        }
99    }
100
101    /// Looks up the geometry stored under `id`, if any.
102    pub fn resolve(&self, id: &Id) -> Option<AbstractGeometryKindRef<'a>> {
103        self.by_id.get(id).copied()
104    }
105
106    /// Looks up the geometry stored under `id` and downcasts it to a concrete
107    /// leaf type or intermediate `*Ref` enum via the [`TryFrom`] conversions
108    /// generated across the geometry ref hierarchy.
109    ///
110    /// Returns `None` if `id` is unresolved, or if it resolves to a geometry
111    /// of a different concrete type than `T`.
112    ///
113    /// # Examples
114    ///
115    /// ```rust
116    /// use egml_core::model::base::{AsAbstractGmlMut, Id};
117    /// use egml_core::model::geometry::DirectPosition;
118    /// use egml_core::model::geometry::primitives::LinearRing;
119    /// use egml_core::resolver::GeometryResolver;
120    ///
121    /// let mut ring = LinearRing::new([
122    ///     DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
123    ///     DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
124    ///     DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
125    /// ])
126    /// .unwrap();
127    /// ring.set_id(Id::try_from("ring-1").expect("valid id"));
128    ///
129    /// let resolver = GeometryResolver::build(&ring);
130    /// let resolved: Option<&LinearRing> = resolver.resolve_as(&Id::try_from("ring-1").expect("valid id"));
131    /// assert!(resolved.is_some());
132    /// ```
133    pub fn resolve_as<T>(&self, id: &Id) -> Option<T>
134    where
135        T: TryFrom<AbstractGeometryKindRef<'a>>,
136    {
137        self.resolve(id)
138            .and_then(|geometry| T::try_from(geometry).ok())
139    }
140
141    /// Returns `true` if `id` is indexed.
142    pub fn contains(&self, id: &Id) -> bool {
143        self.by_id.contains_key(id)
144    }
145
146    /// Returns an iterator over all indexed `id`s, in arbitrary order.
147    pub fn ids(&self) -> impl Iterator<Item = &Id> {
148        self.by_id.keys()
149    }
150
151    /// Returns an iterator over all indexed `(id, geometry)` pairs, in
152    /// arbitrary order.
153    pub fn iter(&self) -> impl Iterator<Item = (&Id, &AbstractGeometryKindRef<'a>)> {
154        self.by_id.iter()
155    }
156
157    pub fn len(&self) -> usize {
158        self.by_id.len()
159    }
160
161    pub fn is_empty(&self) -> bool {
162        self.by_id.is_empty()
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::model::base::AsAbstractGmlMut;
170    use crate::model::geometry::DirectPosition;
171    use crate::model::geometry::primitives::{
172        AbstractRingKind, AbstractRingProperty, LinearRing, Point, Polygon,
173    };
174
175    fn ring_with_id(id: &str) -> LinearRing {
176        let mut ring = LinearRing::new([
177            DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
178            DirectPosition::new(1.0, 0.0, 0.0).unwrap(),
179            DirectPosition::new(0.0, 1.0, 0.0).unwrap(),
180        ])
181        .unwrap();
182        ring.set_id(Id::try_from(id).expect("valid id"));
183        ring
184    }
185
186    #[test]
187    fn build_indexes_self_and_descendants() {
188        let mut polygon = Polygon::new(
189            Some(AbstractRingProperty::from_object(
190                AbstractRingKind::LinearRing(ring_with_id("ring-1")),
191            )),
192            [],
193        )
194        .unwrap();
195        polygon.set_id(Id::try_from("polygon-1").expect("valid id"));
196
197        let resolver = GeometryResolver::build(&polygon);
198
199        assert_eq!(resolver.len(), 2);
200        assert!(resolver.contains(&Id::try_from("polygon-1").expect("valid id")));
201        assert!(resolver.contains(&Id::try_from("ring-1").expect("valid id")));
202    }
203
204    #[test]
205    fn resolve_unknown_id_yields_none() {
206        let polygon = Polygon::new(None, []).unwrap();
207        let resolver = GeometryResolver::build(&polygon);
208
209        assert!(
210            resolver
211                .resolve(&Id::try_from("missing").expect("valid id"))
212                .is_none()
213        );
214    }
215
216    #[test]
217    fn resolve_as_downcasts_to_concrete_type() {
218        let mut polygon = Polygon::new(
219            Some(AbstractRingProperty::from_object(
220                AbstractRingKind::LinearRing(ring_with_id("ring-1")),
221            )),
222            [],
223        )
224        .unwrap();
225        polygon.set_id(Id::try_from("polygon-1").expect("valid id"));
226
227        let resolver = GeometryResolver::build(&polygon);
228
229        let ring: Option<&LinearRing> =
230            resolver.resolve_as(&Id::try_from("ring-1").expect("valid id"));
231        assert!(ring.is_some());
232
233        // Downcasting to the wrong concrete type fails.
234        let wrong: Option<&Point> = resolver.resolve_as(&Id::try_from("ring-1").expect("valid id"));
235        assert!(wrong.is_none());
236    }
237
238    #[test]
239    fn insert_root_adds_a_second_independent_tree_to_the_same_resolver() {
240        let mut first_polygon = Polygon::new(
241            Some(AbstractRingProperty::from_object(
242                AbstractRingKind::LinearRing(ring_with_id("ring-1")),
243            )),
244            [],
245        )
246        .unwrap();
247        first_polygon.set_id(Id::try_from("polygon-1").expect("valid id"));
248
249        let mut second_polygon = Polygon::new(
250            Some(AbstractRingProperty::from_object(
251                AbstractRingKind::LinearRing(ring_with_id("ring-2")),
252            )),
253            [],
254        )
255        .unwrap();
256        second_polygon.set_id(Id::try_from("polygon-2").expect("valid id"));
257
258        let mut resolver = GeometryResolver::new();
259        resolver.insert_root(&first_polygon);
260        resolver.insert_root(&second_polygon);
261
262        // self (2) + one ring each (2) = 4, across two unrelated root trees.
263        assert_eq!(resolver.len(), 4);
264        assert!(resolver.contains(&Id::try_from("polygon-1").expect("valid id")));
265        assert!(resolver.contains(&Id::try_from("polygon-2").expect("valid id")));
266        assert!(resolver.contains(&Id::try_from("ring-1").expect("valid id")));
267        assert!(resolver.contains(&Id::try_from("ring-2").expect("valid id")));
268    }
269
270    #[test]
271    fn insert_skips_geometry_without_id() {
272        let point = Point::new(DirectPosition::new(1.0, 2.0, 3.0).unwrap());
273        let mut resolver = GeometryResolver::new();
274
275        let inserted = resolver.insert((&point).into());
276
277        assert!(!inserted);
278        assert!(resolver.is_empty());
279    }
280
281    #[test]
282    fn insert_duplicate_id_overwrites_last_wins() {
283        let mut first = Point::new(DirectPosition::new(1.0, 2.0, 3.0).unwrap());
284        first.set_id(Id::try_from("dup").expect("valid id"));
285        let mut second = Point::new(DirectPosition::new(4.0, 5.0, 6.0).unwrap());
286        second.set_id(Id::try_from("dup").expect("valid id"));
287
288        let mut resolver = GeometryResolver::new();
289        resolver.insert((&first).into());
290        resolver.insert((&second).into());
291
292        assert_eq!(resolver.len(), 1);
293        let resolved: &Point = resolver
294            .resolve_as(&Id::try_from("dup").expect("valid id"))
295            .unwrap();
296        assert_eq!(resolved.pos().x(), 4.0);
297    }
298
299    #[test]
300    fn ids_and_iter_expose_all_entries() {
301        let mut polygon = Polygon::new(
302            Some(AbstractRingProperty::from_object(
303                AbstractRingKind::LinearRing(ring_with_id("ring-1")),
304            )),
305            [],
306        )
307        .unwrap();
308        polygon.set_id(Id::try_from("polygon-1").expect("valid id"));
309
310        let resolver = GeometryResolver::build(&polygon);
311
312        let ids: Vec<&Id> = resolver.ids().collect();
313        assert_eq!(ids.len(), 2);
314        assert!(ids.contains(&&Id::try_from("polygon-1").expect("valid id")));
315        assert!(ids.contains(&&Id::try_from("ring-1").expect("valid id")));
316
317        assert_eq!(resolver.iter().count(), 2);
318    }
319}