geometry_algorithm/dyn_area.rs
1//! `area_dyn` — runtime-dispatched area for [`DynGeometry`].
2//!
3//! Same shape as `length_dyn`. Per Boost (`algorithms/area.hpp`) the
4//! area of a non-areal *leaf* kind (point, linestring, multi-point,
5//! multi-linestring, …) is `0`, so every leaf arm has a value and the
6//! wrapper returns a plain scalar, never an error (KC4.T1). A
7//! `GeometryCollection` is **not** a leaf: Boost's
8//! `area<geometry_collection_tag>` (`area.hpp:273-285`) recursively sums
9//! the area of every member, so this wrapper does too.
10
11use geometry_coords::CoordinateScalar;
12use geometry_cs::{CartesianFamily, CoordinateSystem};
13use geometry_model::{DynGeometry, Point, Polygon};
14use geometry_strategy::{AreaStrategy, DefaultArea, DefaultAreaStrategy, ShoelaceMultiPolygonArea};
15use geometry_tag::SameAs;
16
17use crate::area::{area, multi_polygon_area};
18
19/// Runtime-dispatched signed area.
20///
21/// Returns the signed area for areal kinds (`Polygon`, `MultiPolygon`),
22/// the recursive sum of member areas for a `GeometryCollection`, and `0`
23/// for every non-areal leaf kind — matching Boost's contract
24/// (`area.hpp`, incl. the `geometry_collection_tag` specialisation at
25/// `:273-285`).
26#[must_use]
27pub fn area_dyn<S, Cs>(g: &DynGeometry<S, Cs>) -> S
28where
29 S: CoordinateScalar,
30 Cs: CoordinateSystem,
31 Cs::Family: SameAs<CartesianFamily> + DefaultArea<Cs::Family>,
32 DefaultAreaStrategy<Polygon<Point<S, 2, Cs>>>:
33 AreaStrategy<Polygon<Point<S, 2, Cs>>, Out = S> + Default,
34 ShoelaceMultiPolygonArea:
35 AreaStrategy<geometry_model::MultiPolygon<Polygon<Point<S, 2, Cs>>>, Out = S>,
36{
37 use DynGeometry::{
38 GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon,
39 Point as PointArm, Polygon as PolygonArm,
40 };
41 // A collection's area is the sum of its members' areas
42 // (`area.hpp:273-285` recurses via `visit_breadth_first`). The walk is
43 // an explicit work-list rather than recursion so an adversarially deep
44 // `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 PolygonArm(pg) => total = total + area(pg),
51 MultiPolygon(mpg) => total = total + multi_polygon_area(mpg),
52 GeometryCollection(items) => stack.extend(items.iter()),
53 // Non-areal leaf kinds have no area — Boost returns 0.
54 PointArm(_) | LineString(_) | MultiPoint(_) | MultiLineString(_) => {}
55 }
56 }
57 total
58}