Skip to main content

geometry_trait/
collection.rs

1//! The [`GeometryCollection`] concept — a sequence of geometries.
2//!
3//! Mirrors `boost::geometry::model::geometry_collection` in
4//! `boost/geometry/geometries/geometry_collection.hpp` and the
5//! `geometry_collection_tag` marker in
6//! `boost/geometry/core/tags.hpp` (`struct geometry_collection_tag :
7//! multi_tag {};`). The C++ model derives from
8//! `Container<DynamicGeometry, Allocator<DynamicGeometry>>` and is
9//! consumed through `boost::range`; this Rust port surfaces the
10//! element sequence via an RPITIT `items()` iterator, matching the
11//! pattern already used by the multi-* traits.
12//!
13//! **Scope:** v1 only supplies the trait surface. Heterogeneous
14//! dynamic dispatch (a `dyn`/`enum`-based story that would let one
15//! collection mix Points, Linestrings, Polygons, …) is intentionally
16//! deferred to a future iteration (a `DynGeometry` variant and a
17//! heterogeneous `GeometryCollection`). The
18//! [`GeometryCollection::Item`] associated type is therefore bound on
19//! [`Geometry`] alone, so today's impls are expected to be
20//! homogeneous in practice (e.g. `Vec<Polygon<P>>`).
21
22use crate::geometry::Geometry;
23use geometry_tag::GeometryCollectionTag;
24
25/// A collection of geometries belonging to each other.
26///
27/// Mirrors the `GeometryCollection` concept; the canonical C++ model
28/// is `boost::geometry::model::geometry_collection` in
29/// `boost/geometry/geometries/geometry_collection.hpp`, tagged with
30/// `geometry_collection_tag` (`boost/geometry/core/tags.hpp`).
31///
32/// In v1 the [`Item`](Self::Item) associated type is bound only on
33/// [`Geometry`] — every concrete impl is homogeneous (a single
34/// element type for the whole collection). Heterogeneous dispatch is
35/// intentionally deferred to a later iteration (a `DynGeometry`
36/// variant and a heterogeneous `GeometryCollection`).
37///
38/// # Examples
39///
40/// ```
41/// use geometry_trait::GeometryCollection;
42/// fn count<C: GeometryCollection>(c: &C) -> usize { c.items().len() }
43/// ```
44pub trait GeometryCollection: Geometry<Kind = GeometryCollectionTag> {
45    /// The element geometry type.
46    ///
47    /// Mirrors the `DynamicGeometry` template parameter on
48    /// `boost::geometry::model::geometry_collection<DynamicGeometry, …>`
49    /// (`boost/geometry/geometries/geometry_collection.hpp`). In v1
50    /// the bound is just [`Geometry`]; once §1.4 lands this will
51    /// tighten to a dynamic-geometry concept.
52    type Item: Geometry;
53
54    /// The items of this collection, in declared order.
55    ///
56    /// Plays the role of `boost::begin(gc)` / `boost::end(gc)` from
57    /// `boost/geometry/geometries/geometry_collection.hpp` when read
58    /// together.
59    fn items(&self) -> impl ExactSizeIterator<Item = &Self::Item>;
60}
61
62#[cfg(test)]
63mod tests {
64    extern crate alloc;
65
66    use super::*;
67    use crate::point::Point;
68    use alloc::vec;
69    use alloc::vec::Vec;
70    use geometry_cs::Cartesian;
71    use geometry_tag::PointTag;
72
73    // Witness: any T satisfying the trait is accepted. Static,
74    // compile-time check; never runs.
75    fn accepts_gc<G: GeometryCollection>() {}
76
77    // Shared 2D Cartesian point used by the test impl below.
78    struct Xy(f64, f64);
79
80    impl Geometry for Xy {
81        type Kind = PointTag;
82        type Point = Self;
83    }
84
85    impl Point for Xy {
86        type Scalar = f64;
87        type Cs = Cartesian;
88        const DIM: usize = 2;
89
90        fn get<const D: usize>(&self) -> f64 {
91            // Indexed, not `if D == 0 { .. } else { .. }`: the branch
92            // form silently aliased every out-of-range `D` onto y.
93            [self.0, self.1][D]
94        }
95    }
96
97    // Homogeneous Vec-backed collection of points.
98    struct ManyPoints(Vec<Xy>);
99
100    impl Geometry for ManyPoints {
101        type Kind = GeometryCollectionTag;
102        type Point = Xy;
103    }
104
105    impl GeometryCollection for ManyPoints {
106        type Item = Xy;
107
108        fn items(&self) -> impl ExactSizeIterator<Item = &Xy> {
109            self.0.iter()
110        }
111    }
112
113    #[test]
114    fn collection_iterates_all_items() {
115        // Distinct x and y per item: reading only one ordinate, or
116        // transposing the two, would still have matched a fixture whose
117        // members were `Xy(n, n)`.
118        let m = ManyPoints(vec![Xy(0.0, 10.0), Xy(1.0, 11.0), Xy(2.0, 12.0)]);
119        accepts_gc::<ManyPoints>();
120        assert_eq!(m.items().len(), 3);
121        let coords: Vec<(f64, f64)> = m.items().map(|p| (p.get::<0>(), p.get::<1>())).collect();
122        assert_eq!(coords, vec![(0.0, 10.0), (1.0, 11.0), (2.0, 12.0)]);
123    }
124}