Skip to main content

geometry_model/
dyn_geometry.rs

1//! A runtime-tagged variant geometry — one variant per OGC kind.
2//!
3//! Mirrors `boost::geometry::dynamic_geometry_tag` and the
4//! variant adapters in
5//! `boost/geometry/geometries/adapted/boost_variant.hpp` (and the
6//! `boost_variant2` / `std_variant` siblings). Where the C++ side
7//! uses `boost::variant<...>` and a `dynamic_geometry_tag` to dispatch,
8//! the Rust port uses a plain enum and lets `match` do the dispatch —
9//! one arm per OGC kind, every arm parameterised by the same
10//! `(Scalar, Cs)` so the static algorithms inside each arm can still
11//! be monomorphised.
12//!
13//! Phase positioning:
14//! * KC2.T1 (this file) — the enum + `kind()` accessor.
15//! * KC2.T2 — `length_dyn`, `area_dyn`, … in `geometry-algorithm`.
16//! * KC2.T3 — `impl GeometryCollection for Vec<DynGeometry<...>>` so
17//!   a heterogeneous vector is itself a collection.
18//! * KC4   — `length`/`area` of "wrong" kinds returns 0 — depends on
19//!   the variant existing so the dispatch arm has somewhere to land.
20
21use alloc::vec::Vec;
22
23use geometry_coords::CoordinateScalar;
24use geometry_cs::{Cartesian, CoordinateSystem};
25use geometry_tag::DynamicGeometryTag;
26use geometry_trait::Geometry;
27
28use crate::{Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point, Polygon};
29
30/// A geometry whose OGC kind is decided at runtime.
31///
32/// Mirrors Boost's `boost::geometry::dynamic_geometry_tag` family
33/// (`core/tags.hpp:124-125`,
34/// `geometries/adapted/boost_variant.hpp`). One variant per OGC kind;
35/// every variant carries the matching `model::*` struct parameterised
36/// by the supplied `Scalar` and `Cs`.
37///
38/// The parameterisation deliberately fixes a single
39/// `(Scalar, Cs)` across all variants — heterogeneous *scalar* or
40/// *coordinate-system* mixing inside one collection is out of scope
41/// for v1 (it would require runtime CS conversion, which is a
42/// `phase_07` projections concern).
43///
44/// # Layout
45///
46/// `GeometryCollection` wraps a `Vec<DynGeometry<Scalar, Cs>>` —
47/// nested collections are allowed. The recursive variant is required
48/// because OGC `GeometryCollection` itself is heterogeneous.
49///
50/// # Example
51///
52/// ```
53/// use geometry_cs::Cartesian;
54/// use geometry_model::{DynGeometry, DynKind, Point2D};
55///
56/// let p = DynGeometry::<f64, Cartesian>::Point(Point2D::new(1.0, 2.0));
57/// assert_eq!(p.kind(), DynKind::Point);
58/// ```
59#[derive(Debug, Clone, PartialEq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61#[cfg_attr(
62    feature = "serde",
63    serde(bound(
64        serialize = "Scalar: serde::Serialize",
65        deserialize = "Scalar: serde::Deserialize<'de>"
66    ))
67)]
68pub enum DynGeometry<Scalar: CoordinateScalar, Cs: CoordinateSystem = Cartesian> {
69    /// OGC `Point`. The dimension is fixed at 2D; 3D points go through
70    /// `PolyhedralSurface` (or wait for the v1.1 `DynPoint3` variant —
71    /// not in this task).
72    Point(Point<Scalar, 2, Cs>),
73
74    /// OGC `LineString`. Wraps [`Linestring<Point<Scalar, 2, Cs>>`].
75    LineString(Linestring<Point<Scalar, 2, Cs>>),
76
77    /// OGC `Polygon`. The two const-generic bools default to Boost's
78    /// `(ClockWise = true, Closed = true)`.
79    Polygon(Polygon<Point<Scalar, 2, Cs>>),
80
81    /// OGC `MultiPoint`.
82    MultiPoint(MultiPoint<Point<Scalar, 2, Cs>>),
83
84    /// OGC `MultiLineString`.
85    MultiLineString(MultiLinestring<Linestring<Point<Scalar, 2, Cs>>>),
86
87    /// OGC `MultiPolygon`.
88    MultiPolygon(MultiPolygon<Polygon<Point<Scalar, 2, Cs>>>),
89
90    /// OGC `GeometryCollection` — a `Vec` of dyn-geometries. Nested
91    /// collections are permitted.
92    GeometryCollection(Vec<DynGeometry<Scalar, Cs>>),
93    // OGC `PolyhedralSurface`. Reuses `crate::PolyhedralSurface` once a
94    // concrete model type exists (it is currently trait-only per v1
95    // T14); until then there is no variant to land here.
96    // PolyhedralSurface(PolyhedralSurface<...>)
97}
98
99/// Discriminant of [`DynGeometry`]. Stable across releases.
100///
101/// Mirrors the result of the `geometry::detail::single_tag_of` family
102/// plus Boost's `dynamic_geometry_concept`'s tag inspection
103/// (`geometries/concepts/dynamic_geometry_concept.hpp`).
104#[non_exhaustive]
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106pub enum DynKind {
107    Point,
108    LineString,
109    Polygon,
110    MultiPoint,
111    MultiLineString,
112    MultiPolygon,
113    GeometryCollection,
114    /// Reserved for the `PolyhedralSurface` variant once the
115    /// matching concrete model type lands. v1's `PolyhedralSurface`
116    /// is trait-only.
117    PolyhedralSurface,
118}
119
120impl<Scalar: CoordinateScalar, Cs: CoordinateSystem> Geometry for DynGeometry<Scalar, Cs> {
121    type Kind = DynamicGeometryTag;
122    // A runtime-tagged geometry has no single static point type; the
123    // representative choice is the 2D point every variant is built on.
124    // Mirrors Boost treating a `dynamic_geometry` through its
125    // `point_type` of the underlying variant elements.
126    type Point = Point<Scalar, 2, Cs>;
127}
128
129impl<Scalar: CoordinateScalar, Cs: CoordinateSystem> DynGeometry<Scalar, Cs> {
130    /// Discriminant of this value.
131    ///
132    /// Mirrors `tag<G>::type` on `dynamic_geometry_tag` Boost types
133    /// (`core/tag.hpp` + the variant adapters). Pure run-time
134    /// inspection — algorithms should not branch on `kind()` directly;
135    /// the `_dyn` algorithm wrappers in KC2.T2 match each arm and
136    /// forward to the static algorithm, keeping dispatch monomorphic
137    /// per arm.
138    #[must_use]
139    pub fn kind(&self) -> DynKind {
140        match self {
141            DynGeometry::Point(_) => DynKind::Point,
142            DynGeometry::LineString(_) => DynKind::LineString,
143            DynGeometry::Polygon(_) => DynKind::Polygon,
144            DynGeometry::MultiPoint(_) => DynKind::MultiPoint,
145            DynGeometry::MultiLineString(_) => DynKind::MultiLineString,
146            DynGeometry::MultiPolygon(_) => DynKind::MultiPolygon,
147            DynGeometry::GeometryCollection(_) => DynKind::GeometryCollection,
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    //! Round-trip each variant through `kind()`. Mirrors the per-tag
155    //! check pattern in `boost/geometry/test/core/tag.cpp`, but on the
156    //! runtime discriminant instead of the compile-time tag type.
157
158    use super::*;
159    use crate::{Point2D, linestring, polygon};
160    use alloc::vec;
161    use geometry_cs::Cartesian;
162
163    type S = f64;
164    type Pt = Point2D<S, Cartesian>;
165
166    #[test]
167    fn kind_round_trip_every_variant() {
168        let cases: Vec<(DynGeometry<S, Cartesian>, DynKind)> = vec![
169            (DynGeometry::Point(Pt::new(1.0, 2.0)), DynKind::Point),
170            (
171                DynGeometry::LineString(linestring![(0.0, 0.0), (1.0, 1.0)]),
172                DynKind::LineString,
173            ),
174            (
175                DynGeometry::Polygon(polygon![[
176                    (0.0, 0.0),
177                    (1.0, 0.0),
178                    (1.0, 1.0),
179                    (0.0, 1.0),
180                    (0.0, 0.0)
181                ]]),
182                DynKind::Polygon,
183            ),
184            (
185                DynGeometry::MultiPoint(crate::MultiPoint(vec![Pt::new(0.0, 0.0)])),
186                DynKind::MultiPoint,
187            ),
188            (
189                DynGeometry::GeometryCollection(vec![]),
190                DynKind::GeometryCollection,
191            ),
192        ];
193        for (g, expected) in cases {
194            assert_eq!(g.kind(), expected);
195        }
196    }
197
198    #[test]
199    fn geometry_collection_can_nest() {
200        let nested = DynGeometry::<S, Cartesian>::GeometryCollection(vec![
201            DynGeometry::Point(Pt::new(1.0, 2.0)),
202            DynGeometry::GeometryCollection(vec![DynGeometry::Point(Pt::new(3.0, 4.0))]),
203        ]);
204        assert_eq!(
205            nested,
206            DynGeometry::GeometryCollection(vec![
207                DynGeometry::Point(Pt::new(1.0, 2.0)),
208                DynGeometry::GeometryCollection(vec![DynGeometry::Point(Pt::new(3.0, 4.0))]),
209            ])
210        );
211    }
212}