Skip to main content

geometry_algorithm/
num_geometries.rs

1//! `num_geometries(&g)` — number of distinct geometries in `g`.
2//!
3//! Mirrors `boost::geometry::num_geometries` from
4//! `boost/geometry/algorithms/num_geometries.hpp`. Single geometries
5//! return `1`; multi-geometries return the member count (Boost's same
6//! convention — no recursive flatten).
7
8use geometry_model::{
9    Box, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point, Polygon, Ring, Segment,
10};
11
12/// Number of distinct geometries in `g`.
13///
14/// * `Point` / `Linestring` / `Ring` / `Polygon` / `Segment` / `Box`
15///   → `1`
16/// * `Multi*` → member count
17///
18/// Mirrors `boost::geometry::num_geometries(g)` from
19/// `boost/geometry/algorithms/num_geometries.hpp`.
20#[inline]
21#[must_use]
22pub fn num_geometries<G: NumGeometries>(g: &G) -> usize {
23    g.num_geometries()
24}
25
26/// Public-but-implementation-detail trait dispatching by concrete
27/// model type (per the LA0.T2 coherence note). One impl per
28/// `geometry-model` struct; users call [`num_geometries`].
29#[doc(hidden)]
30pub trait NumGeometries {
31    /// Number of distinct geometries in `self`.
32    fn num_geometries(&self) -> usize;
33}
34
35// Singles return 1.
36impl<T, const D: usize, Cs> NumGeometries for Point<T, D, Cs>
37where
38    T: geometry_coords::CoordinateScalar,
39    Cs: geometry_cs::CoordinateSystem,
40{
41    fn num_geometries(&self) -> usize {
42        1
43    }
44}
45
46impl<P: geometry_trait::Point> NumGeometries for Linestring<P> {
47    fn num_geometries(&self) -> usize {
48        1
49    }
50}
51
52impl<P: geometry_trait::Point, const CW: bool, const CL: bool> NumGeometries for Ring<P, CW, CL> {
53    fn num_geometries(&self) -> usize {
54        1
55    }
56}
57
58impl<P: geometry_trait::Point, const CW: bool, const CL: bool> NumGeometries
59    for Polygon<P, CW, CL>
60{
61    fn num_geometries(&self) -> usize {
62        1
63    }
64}
65
66impl<P: geometry_trait::Point> NumGeometries for Segment<P> {
67    fn num_geometries(&self) -> usize {
68        1
69    }
70}
71
72impl<P: geometry_trait::Point> NumGeometries for Box<P> {
73    fn num_geometries(&self) -> usize {
74        1
75    }
76}
77
78// Multis return member counts.
79impl<P: geometry_trait::Point> NumGeometries for MultiPoint<P> {
80    fn num_geometries(&self) -> usize {
81        self.0.len()
82    }
83}
84
85impl<L: geometry_trait::Linestring> NumGeometries for MultiLinestring<L> {
86    fn num_geometries(&self) -> usize {
87        self.0.len()
88    }
89}
90
91impl<Pg: geometry_trait::Polygon> NumGeometries for MultiPolygon<Pg> {
92    fn num_geometries(&self) -> usize {
93        self.0.len()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    //! Reference values from
100    //! `geometry/test/algorithms/num_geometries.cpp`.
101
102    use super::num_geometries;
103    use geometry_cs::Cartesian;
104    use geometry_model::{
105        Linestring, MultiPoint, MultiPolygon, Point2D, Polygon, linestring, polygon,
106    };
107
108    type Pt = Point2D<f64, Cartesian>;
109    type Ls = Linestring<Pt>;
110    type Poly = Polygon<Pt>;
111
112    /// `num_geometries.cpp:66` — `POINT(0 0)` → 1.
113    #[test]
114    fn point_is_one() {
115        assert_eq!(num_geometries(&Pt::new(0.0, 0.0)), 1);
116    }
117
118    /// `num_geometries.cpp:81` — `LINESTRING(0 0,1 1,2 2)` → 1.
119    #[test]
120    fn linestring_is_one() {
121        let ls: Ls = linestring![(0.0, 0.0), (1.0, 1.0)];
122        assert_eq!(num_geometries(&ls), 1);
123    }
124
125    /// `num_geometries.cpp:114` — a polygon with a hole is still 1.
126    #[test]
127    fn polygon_with_holes_is_one() {
128        let pg: Poly = polygon![
129            [(0.0, 0.0), (5.0, 0.0), (5.0, 5.0), (0.0, 5.0), (0.0, 0.0)],
130            [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)],
131        ];
132        assert_eq!(num_geometries(&pg), 1);
133    }
134
135    /// `num_geometries.cpp:91` — `MULTIPOINT(0 0,0 0,1 1)` → 3.
136    #[test]
137    fn multi_point_returns_member_count() {
138        let mp = MultiPoint(vec![
139            Pt::new(0.0, 0.0),
140            Pt::new(1.0, 1.0),
141            Pt::new(2.0, 2.0),
142        ]);
143        assert_eq!(num_geometries(&mp), 3);
144    }
145
146    /// The remaining single kinds — `Ring`, `Segment`, `Box` — each
147    /// report exactly one geometry.
148    #[test]
149    fn other_single_kinds_are_one() {
150        use geometry_model::{Box, Ring, Segment};
151        let ring: Ring<Pt> = Ring::from_vec(vec![
152            Pt::new(0.0, 0.0),
153            Pt::new(1.0, 0.0),
154            Pt::new(0.0, 0.0),
155        ]);
156        assert_eq!(num_geometries(&ring), 1);
157        assert_eq!(
158            num_geometries(&Segment::new(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0))),
159            1
160        );
161        assert_eq!(
162            num_geometries(&Box::from_corners(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0))),
163            1
164        );
165    }
166
167    /// A `MultiLineString` reports its member count.
168    #[test]
169    fn multi_linestring_returns_member_count() {
170        use geometry_model::MultiLinestring;
171        let mls: MultiLinestring<Ls> = MultiLinestring(vec![
172            linestring![(0.0, 0.0), (1.0, 1.0)],
173            linestring![(2.0, 2.0), (3.0, 3.0)],
174            linestring![(4.0, 4.0), (5.0, 5.0)],
175        ]);
176        assert_eq!(num_geometries(&mls), 3);
177    }
178
179    /// `num_geometries.cpp:124` — a 2-member multipolygon → 2.
180    #[test]
181    fn multi_polygon_returns_member_count() {
182        let mpg: MultiPolygon<Poly> = MultiPolygon(vec![
183            polygon![[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]],
184            polygon![[(2.0, 2.0), (3.0, 2.0), (3.0, 3.0), (2.0, 2.0)]],
185        ]);
186        assert_eq!(num_geometries(&mpg), 2);
187    }
188}