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