Skip to main content

geometry_model/
dyn_collection.rs

1//! [`DynGeometryCollection`] — a heterogeneous OGC `GeometryCollection`.
2//!
3//! Mirrors Boost's `geometry_collection<Geometry>` adapter pattern
4//! (`boost/geometry/geometries/geometry_collection.hpp`) over a
5//! `boost::variant`-typed element. The Rust port encodes the same
6//! relationship via the runtime-tagged
7//! [`crate::DynGeometry`] enum from KC2.T1.
8//!
9//! A `DynGeometryCollection<f64, Cartesian>` satisfies the
10//! [`GeometryCollection`] trait — the *same* trait v1's homogeneous
11//! `Vec<Polygon<P>>` already satisfies — and can flow through every
12//! algorithm written against `G: GeometryCollection`.
13//!
14//! ## Why a newtype, not a bare `Vec`
15//!
16//! The orphan rule forbids `impl GeometryCollection for
17//! Vec<DynGeometry<…>>`: both the trait (from `geometry-trait`) and
18//! `Vec` (from `alloc`) are foreign, and `Vec` is not a `#[fundamental]`
19//! type, so a local type parameter inside it is not enough. Wrapping
20//! the vector in this crate-local newtype restores coherence — the
21//! self type's head (`DynGeometryCollection`) is local.
22
23use alloc::vec::Vec;
24
25use geometry_coords::CoordinateScalar;
26use geometry_cs::CoordinateSystem;
27use geometry_tag::GeometryCollectionTag;
28use geometry_trait::{Geometry, GeometryCollection};
29
30use crate::DynGeometry;
31
32/// A homogeneous-`(Scalar, Cs)`, heterogeneous-kind collection of
33/// [`DynGeometry`] values — the v1.x OGC `GeometryCollection`.
34///
35/// # Example
36///
37/// ```
38/// use geometry_cs::Cartesian;
39/// use geometry_model::{DynGeometry, DynGeometryCollection, Point2D};
40/// use geometry_trait::GeometryCollection as _;
41///
42/// let gc = DynGeometryCollection::<f64, Cartesian>(vec![
43///     DynGeometry::Point(Point2D::new(0.0, 0.0)),
44/// ]);
45/// assert_eq!(gc.items().count(), 1);
46/// ```
47#[derive(Debug, Clone, PartialEq, Default)]
48pub struct DynGeometryCollection<S: CoordinateScalar, Cs: CoordinateSystem>(
49    pub Vec<DynGeometry<S, Cs>>,
50);
51
52impl<S, Cs> Geometry for DynGeometryCollection<S, Cs>
53where
54    S: CoordinateScalar,
55    Cs: CoordinateSystem,
56{
57    type Kind = GeometryCollectionTag;
58    // The `Point` projection on a heterogeneous collection is the
59    // "representative point" of its inner element. Every variant
60    // carries a `Point<S, 2, Cs>`-based geometry, so the natural
61    // choice is `model::Point<S, 2, Cs>` itself.
62    type Point = crate::Point<S, 2, Cs>;
63}
64
65impl<S, Cs> GeometryCollection for DynGeometryCollection<S, Cs>
66where
67    S: CoordinateScalar,
68    Cs: CoordinateSystem,
69{
70    type Item = DynGeometry<S, Cs>;
71
72    fn items(&self) -> impl ExactSizeIterator<Item = &Self::Item> {
73        self.0.iter()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    //! Construct a heterogeneous collection, iterate it through the
80    //! [`GeometryCollection`] interface, and confirm the `.kind()`
81    //! sequence matches construction order.
82
83    use super::*;
84    use crate::{DynKind, Point2D, linestring, polygon};
85    use alloc::vec;
86    use geometry_cs::Cartesian;
87
88    type S = f64;
89    type Pt = Point2D<S, Cartesian>;
90
91    #[test]
92    fn dyn_collection_iterates_in_order() {
93        let xs = DynGeometryCollection::<S, Cartesian>(vec![
94            DynGeometry::Point(Pt::new(0.0, 0.0)),
95            DynGeometry::LineString(linestring![(0.0, 0.0), (1.0, 1.0)]),
96            DynGeometry::Polygon(polygon![[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]]),
97        ]);
98
99        let kinds: Vec<DynKind> = xs.items().map(DynGeometry::kind).collect();
100        assert_eq!(
101            kinds,
102            vec![DynKind::Point, DynKind::LineString, DynKind::Polygon]
103        );
104    }
105
106    #[test]
107    fn empty_collection_is_a_geometry_collection() {
108        let xs = DynGeometryCollection::<S, Cartesian>(vec![]);
109        assert_eq!(xs.items().count(), 0);
110    }
111
112    /// Compile-time proof that the newtype satisfies the trait.
113    fn accepts_collection<C: GeometryCollection>() {}
114    #[test]
115    fn dyn_collection_is_a_geometry_collection() {
116        accepts_collection::<DynGeometryCollection<S, Cartesian>>();
117    }
118}