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}