Skip to main content

geometry_algorithm/
dyn_length.rs

1//! `length_dyn` — runtime-dispatched length for [`DynGeometry`].
2//!
3//! Mirrors `boost::geometry::length(g)` reached through the variant
4//! adapter. Per Boost's convention (`algorithms/length.hpp:75-80`) the
5//! length of a non-linear *leaf* kind (point, polygon, multi-point, …)
6//! is `0` — so every leaf arm has a value and the wrapper returns a
7//! plain scalar, never an error (KC4.T1). A `GeometryCollection` is
8//! **not** a leaf: Boost's `length<geometry_collection_tag>`
9//! (`length.hpp:251-265`) recursively sums the length of every member,
10//! so this wrapper does too.
11
12use geometry_coords::CoordinateScalar;
13use geometry_cs::{CartesianFamily, CoordinateSystem};
14use geometry_model::{DynGeometry, Linestring, Point};
15use geometry_strategy::{CartesianLength, DefaultLength, DefaultLengthStrategy, LengthStrategy};
16use geometry_tag::SameAs;
17
18use crate::length::length;
19
20/// Runtime-dispatched length.
21///
22/// Returns the polyline length for linear kinds (`LineString`,
23/// `MultiLineString`), the recursive sum of member lengths for a
24/// `GeometryCollection`, and `0` for every non-linear leaf kind —
25/// matching Boost's contract (`length.hpp:75-80`, plus the
26/// `geometry_collection_tag` specialisation at `:251-265`).
27#[must_use]
28pub fn length_dyn<S, Cs>(g: &DynGeometry<S, Cs>) -> S
29where
30    S: CoordinateScalar,
31    Cs: CoordinateSystem,
32    Cs::Family: SameAs<CartesianFamily> + DefaultLength<Cs::Family>,
33    CartesianLength: LengthStrategy<Linestring<Point<S, 2, Cs>>, Out = S>,
34    DefaultLengthStrategy<Linestring<Point<S, 2, Cs>>>:
35        LengthStrategy<Linestring<Point<S, 2, Cs>>, Out = S> + Default,
36{
37    use DynGeometry::{
38        GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon,
39        Point as PointArm, Polygon,
40    };
41    // A collection's length is the sum of its members' lengths
42    // (`length.hpp:251-265` recurses via `visit_breadth_first`). The walk
43    // is an explicit work-list rather than recursion so an adversarially
44    // deep `GeometryCollection` chain cannot overflow the native stack (an
45    // uncatchable process abort).
46    let mut total = S::ZERO;
47    let mut stack = alloc::vec![g];
48    while let Some(node) = stack.pop() {
49        match node {
50            LineString(ls) => total = total + length(ls),
51            MultiLineString(ml) => {
52                total = ml.0.iter().fold(total, |acc, ls| acc + length(ls));
53            }
54            GeometryCollection(items) => stack.extend(items.iter()),
55            // Non-linear leaf kinds have no length — Boost returns 0.
56            PointArm(_) | Polygon(_) | MultiPoint(_) | MultiPolygon(_) => {}
57        }
58    }
59    total
60}