Skip to main content

geometry_algorithm/
num_interior_rings.rs

1//! `num_interior_rings(&g)` — Σ interior-ring counts.
2//!
3//! Mirrors `boost::geometry::num_interior_rings` from
4//! `boost/geometry/algorithms/num_interior_rings.hpp`. Every kind that
5//! is not a polygon (or multi-polygon) returns `0` by convention; for
6//! polygons the count is the number of holes (`interior_rings`
7//! accessor); a multi-polygon sums over its members.
8
9use geometry_model::{
10    Box, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point, Polygon, Ring, Segment,
11};
12use geometry_trait::Polygon as _;
13
14/// Number of interior rings (holes) in `g`.
15///
16/// * `Polygon` → `interiors().count()`
17/// * `MultiPolygon` → sum over members
18/// * everything else → `0`
19///
20/// Mirrors `boost::geometry::num_interior_rings(g)` from
21/// `boost/geometry/algorithms/num_interior_rings.hpp`.
22#[inline]
23#[must_use]
24pub fn num_interior_rings<G: NumInteriorRings>(g: &G) -> usize {
25    g.num_interior_rings()
26}
27
28/// Public-but-implementation-detail trait dispatching by concrete
29/// model type (per the LA0.T2 coherence note — the zero default is
30/// spelled out per concrete type rather than a blanket, so it does not
31/// collide with the polygon impl). Users call [`num_interior_rings`].
32#[doc(hidden)]
33pub trait NumInteriorRings {
34    /// Number of interior rings (holes) in `self`.
35    fn num_interior_rings(&self) -> usize;
36}
37
38// Zero for every kind that is not polygon-shaped.
39impl<T, const D: usize, Cs> NumInteriorRings for Point<T, D, Cs>
40where
41    T: geometry_coords::CoordinateScalar,
42    Cs: geometry_cs::CoordinateSystem,
43{
44    fn num_interior_rings(&self) -> usize {
45        0
46    }
47}
48
49impl<P: geometry_trait::Point> NumInteriorRings for Linestring<P> {
50    fn num_interior_rings(&self) -> usize {
51        0
52    }
53}
54
55impl<P: geometry_trait::Point, const CW: bool, const CL: bool> NumInteriorRings
56    for Ring<P, CW, CL>
57{
58    fn num_interior_rings(&self) -> usize {
59        0
60    }
61}
62
63impl<P: geometry_trait::Point> NumInteriorRings for Segment<P> {
64    fn num_interior_rings(&self) -> usize {
65        0
66    }
67}
68
69impl<P: geometry_trait::Point> NumInteriorRings for Box<P> {
70    fn num_interior_rings(&self) -> usize {
71        0
72    }
73}
74
75impl<P: geometry_trait::Point> NumInteriorRings for MultiPoint<P> {
76    fn num_interior_rings(&self) -> usize {
77        0
78    }
79}
80
81impl<L: geometry_trait::Linestring> NumInteriorRings for MultiLinestring<L> {
82    fn num_interior_rings(&self) -> usize {
83        0
84    }
85}
86
87// Polygon-shaped kinds count holes.
88impl<P: geometry_trait::Point, const CW: bool, const CL: bool> NumInteriorRings
89    for Polygon<P, CW, CL>
90{
91    fn num_interior_rings(&self) -> usize {
92        self.interiors().count()
93    }
94}
95
96impl<Pg: geometry_trait::Polygon + NumInteriorRings> NumInteriorRings for MultiPolygon<Pg> {
97    fn num_interior_rings(&self) -> usize {
98        self.0
99            .iter()
100            .map(NumInteriorRings::num_interior_rings)
101            .sum()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    //! Reference values from
108    //! `geometry/test/algorithms/num_interior_rings.cpp`.
109
110    use super::num_interior_rings;
111    use geometry_cs::Cartesian;
112    use geometry_model::{Linestring, MultiPolygon, Point2D, Polygon, linestring, polygon};
113
114    type Pt = Point2D<f64, Cartesian>;
115    type Ls = Linestring<Pt>;
116    type Poly = Polygon<Pt>;
117
118    /// `num_interior_rings.cpp:66` — `POINT(0 0)` → 0.
119    #[test]
120    fn point_has_zero() {
121        assert_eq!(num_interior_rings(&Pt::new(0.0, 0.0)), 0);
122    }
123
124    /// `num_interior_rings.cpp:81` — `LINESTRING(0 0,1 1)` → 0.
125    #[test]
126    fn linestring_has_zero() {
127        let ls: Ls = linestring![(0.0, 0.0), (1.0, 1.0)];
128        assert_eq!(num_interior_rings(&ls), 0);
129    }
130
131    /// `num_interior_rings.cpp:109` — an outer-only polygon → 0.
132    #[test]
133    fn polygon_outer_only_has_zero() {
134        let pg: Poly = polygon![[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]];
135        assert_eq!(num_interior_rings(&pg), 0);
136    }
137
138    /// `num_interior_rings.cpp:111` — a polygon with two holes → 2.
139    #[test]
140    fn polygon_with_two_holes() {
141        let pg: Poly = polygon![
142            [
143                (0.0, 0.0),
144                (10.0, 0.0),
145                (10.0, 10.0),
146                (0.0, 10.0),
147                (0.0, 0.0)
148            ],
149            [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 1.0)],
150            [(5.0, 5.0), (6.0, 5.0), (6.0, 6.0), (5.0, 5.0)],
151        ];
152        assert_eq!(num_interior_rings(&pg), 2);
153    }
154
155    /// Every non-polygon kind reports zero interior rings by convention.
156    #[test]
157    fn non_polygon_kinds_have_zero() {
158        use geometry_model::{Box, MultiLinestring, MultiPoint, Ring, Segment};
159        let ring: Ring<Pt> = Ring::from_vec(vec![
160            Pt::new(0.0, 0.0),
161            Pt::new(1.0, 0.0),
162            Pt::new(0.0, 0.0),
163        ]);
164        assert_eq!(num_interior_rings(&ring), 0);
165        assert_eq!(
166            num_interior_rings(&Segment::new(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0))),
167            0
168        );
169        assert_eq!(
170            num_interior_rings(&Box::from_corners(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0))),
171            0
172        );
173        assert_eq!(num_interior_rings(&MultiPoint(vec![Pt::new(0.0, 0.0)])), 0);
174        let mls: MultiLinestring<Ls> = MultiLinestring(vec![linestring![(0.0, 0.0), (1.0, 1.0)]]);
175        assert_eq!(num_interior_rings(&mls), 0);
176    }
177
178    /// `num_interior_rings.cpp:121` —
179    /// `MULTIPOLYGON(<1 hole>,<0 holes>)` → 1 (sum over members).
180    #[test]
181    fn multi_polygon_sums_holes() {
182        let mpg: MultiPolygon<Poly> = MultiPolygon(vec![
183            polygon![
184                [
185                    (0.0, 0.0),
186                    (10.0, 0.0),
187                    (10.0, 10.0),
188                    (0.0, 10.0),
189                    (0.0, 0.0)
190                ],
191                [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 1.0)],
192            ],
193            polygon![[(20.0, 0.0), (30.0, 0.0), (30.0, 10.0), (20.0, 0.0)]],
194        ]);
195        assert_eq!(num_interior_rings(&mpg), 1);
196    }
197}