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}