Skip to main content

geometry_algorithm/
num_points.rs

1//! `num_points(&g)` — total point count, recursive on multi / polygon
2//! kinds.
3//!
4//! Mirrors `boost::geometry::num_points` from
5//! `boost/geometry/algorithms/num_points.hpp`. Boost's free function
6//! also takes an `add_for_open` flag (when `true`, an *open* ring
7//! counts an extra point for the implicit closing edge); v1 ships the
8//! plain count only — the flag has no v1 fixture that a value depends
9//! on, so it is omitted until an open-ring caller needs it.
10
11use geometry_tag::{
12    BoxTag, LinestringTag, MultiLinestringTag, MultiPointTag, MultiPolygonTag, PointTag,
13    PolygonTag, RingTag, SegmentTag,
14};
15use geometry_trait::{
16    Box as BoxTrait, Geometry, Linestring as LinestringTrait,
17    MultiLinestring as MultiLinestringTrait, MultiPoint as MultiPointTrait,
18    MultiPolygon as MultiPolygonTrait, Point as PointTrait, Polygon as PolygonTrait,
19    Ring as RingTrait, Segment as SegmentTrait,
20};
21
22/// Total number of points in `g`.
23///
24/// * `Point` → `1`
25/// * `Segment` → `2` (the two endpoints)
26/// * `Box` → `2^D` corner points (`num_points.hpp:100-101`) — 4 in 2D
27/// * `Linestring` / `Ring` → length of the stored point sequence
28/// * `Polygon` → exterior + Σ interior ring point counts
29/// * `Multi*` → sum over members
30///
31/// Mirrors `boost::geometry::num_points(g)` from
32/// `boost/geometry/algorithms/num_points.hpp`. The per-kind body is
33/// selected by the tag-keyed [`NumPointsStrategyForKind`] picker, so any
34/// concept-adapted foreign type resolves through the same path as the
35/// equivalent `geometry-model` value.
36#[inline]
37#[must_use]
38pub fn num_points<G>(g: &G) -> usize
39where
40    G: Geometry,
41    G::Kind: NumPointsStrategyForKind,
42    <G::Kind as NumPointsStrategyForKind>::S: NumPointsStrategy<G>,
43{
44    <<G::Kind as NumPointsStrategyForKind>::S as Default>::default().num_points(g)
45}
46
47/// A strategy for counting the points of `G`. Each per-kind struct below
48/// carries a single concept-bounded impl, so distinct structs never
49/// overlap (the distinct-struct-per-kind pattern); users call [`num_points`], not this
50/// trait.
51#[doc(hidden)]
52pub trait NumPointsStrategy<G> {
53    /// Total number of points in `g`.
54    fn num_points(&self, g: &G) -> usize;
55}
56
57#[derive(Default)]
58#[doc(hidden)]
59pub struct PointNumPoints;
60#[derive(Default)]
61#[doc(hidden)]
62pub struct SegmentNumPoints;
63#[derive(Default)]
64#[doc(hidden)]
65pub struct BoxNumPoints;
66#[derive(Default)]
67#[doc(hidden)]
68pub struct LinestringNumPoints;
69#[derive(Default)]
70#[doc(hidden)]
71pub struct RingNumPoints;
72#[derive(Default)]
73#[doc(hidden)]
74pub struct PolygonNumPoints;
75#[derive(Default)]
76#[doc(hidden)]
77pub struct MultiPointNumPoints;
78#[derive(Default)]
79#[doc(hidden)]
80pub struct MultiLinestringNumPoints;
81#[derive(Default)]
82#[doc(hidden)]
83pub struct MultiPolygonNumPoints;
84
85impl<G: PointTrait> NumPointsStrategy<G> for PointNumPoints {
86    fn num_points(&self, _g: &G) -> usize {
87        1
88    }
89}
90
91impl<G: SegmentTrait> NumPointsStrategy<G> for SegmentNumPoints {
92    fn num_points(&self, _g: &G) -> usize {
93        2
94    }
95}
96
97impl<G: BoxTrait> NumPointsStrategy<G> for BoxNumPoints {
98    fn num_points(&self, _g: &G) -> usize {
99        // Boost counts a box as its `2^D` corner points
100        // (`num_points.hpp:100-101`: `1 << dimension`) — 4 in 2D, 8 in 3D.
101        1usize << <G::Point as PointTrait>::DIM
102    }
103}
104
105impl<G: LinestringTrait> NumPointsStrategy<G> for LinestringNumPoints {
106    fn num_points(&self, g: &G) -> usize {
107        g.points().count()
108    }
109}
110
111impl<G: RingTrait> NumPointsStrategy<G> for RingNumPoints {
112    fn num_points(&self, g: &G) -> usize {
113        g.points().count()
114    }
115}
116
117impl<G: PolygonTrait> NumPointsStrategy<G> for PolygonNumPoints {
118    fn num_points(&self, g: &G) -> usize {
119        let mut n = g.exterior().points().count();
120        for inner in g.interiors() {
121            n += inner.points().count();
122        }
123        n
124    }
125}
126
127impl<G: MultiPointTrait> NumPointsStrategy<G> for MultiPointNumPoints {
128    fn num_points(&self, g: &G) -> usize {
129        g.points().count()
130    }
131}
132
133impl<G: MultiLinestringTrait> NumPointsStrategy<G> for MultiLinestringNumPoints {
134    fn num_points(&self, g: &G) -> usize {
135        // Recurse through the public `num_points` on each member — the
136        // member is a strictly-smaller kind (Linestring), so
137        // monomorphisation terminates on the concrete member type.
138        g.linestrings().map(num_points).sum()
139    }
140}
141
142impl<G: MultiPolygonTrait> NumPointsStrategy<G> for MultiPolygonNumPoints {
143    fn num_points(&self, g: &G) -> usize {
144        g.polygons().map(num_points).sum()
145    }
146}
147
148/// Type-level "which `NumPointsStrategy` struct does this geometry *kind*
149/// use". One impl per [`geometry_tag`] kind tag, keyed on the tag (never
150/// on a concept blanket, which would overlap — E0119), so any
151/// concept-adapted foreign type with the same `Kind` resolves to the same
152/// struct as the equivalent model value. Users call [`num_points`].
153#[doc(hidden)]
154pub trait NumPointsStrategyForKind {
155    /// The per-kind [`NumPointsStrategy`] struct this tag is counted with.
156    type S: Default;
157}
158
159impl NumPointsStrategyForKind for PointTag {
160    type S = PointNumPoints;
161}
162impl NumPointsStrategyForKind for SegmentTag {
163    type S = SegmentNumPoints;
164}
165impl NumPointsStrategyForKind for BoxTag {
166    type S = BoxNumPoints;
167}
168impl NumPointsStrategyForKind for LinestringTag {
169    type S = LinestringNumPoints;
170}
171impl NumPointsStrategyForKind for RingTag {
172    type S = RingNumPoints;
173}
174impl NumPointsStrategyForKind for PolygonTag {
175    type S = PolygonNumPoints;
176}
177impl NumPointsStrategyForKind for MultiPointTag {
178    type S = MultiPointNumPoints;
179}
180impl NumPointsStrategyForKind for MultiLinestringTag {
181    type S = MultiLinestringNumPoints;
182}
183impl NumPointsStrategyForKind for MultiPolygonTag {
184    type S = MultiPolygonNumPoints;
185}
186
187#[cfg(test)]
188mod tests {
189    //! Reference values from `geometry/test/algorithms/num_points.cpp`.
190    //! A box counts as its `2^D` corner points (`num_points.hpp:100-101`)
191    //! — 4 for a 2D box.
192
193    use super::num_points;
194    use geometry_cs::Cartesian;
195    use geometry_model::{
196        Box, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point2D, Polygon, Segment,
197        linestring, polygon,
198    };
199
200    type Pt = Point2D<f64, Cartesian>;
201    type Ls = Linestring<Pt>;
202    type Poly = Polygon<Pt>;
203
204    /// `num_points.cpp:80` — `POINT(0 0)` → 1.
205    #[test]
206    fn point_is_one() {
207        assert_eq!(num_points(&Pt::new(0.0, 0.0)), 1);
208    }
209
210    /// `num_points.cpp:82` — `SEGMENT(0 0,1 1)` → 2.
211    #[test]
212    fn segment_is_two() {
213        let s = Segment::new(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0));
214        assert_eq!(num_points(&s), 2);
215    }
216
217    /// `num_points.hpp:100-101` — a 2D box has `2^2 = 4` corner points.
218    #[test]
219    fn box_2d_is_four() {
220        let b = Box::from_corners(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0));
221        assert_eq!(num_points(&b), 4);
222    }
223
224    /// `num_points.cpp:81` uses a 2-point linestring; the 3-point case
225    /// here matches the plan's `(0,0),(1,1),(2,2)` fixture → 3.
226    #[test]
227    fn linestring_three_points() {
228        let ls: Ls = linestring![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
229        assert_eq!(num_points(&ls), 3);
230    }
231
232    /// `num_points.cpp:84-87` — a ring counts its stored points
233    /// (closed 5-point square → 5).
234    #[test]
235    fn ring_counts_stored_points() {
236        let r: geometry_model::Ring<Pt> = geometry_model::Ring::from_vec(vec![
237            Pt::new(0.0, 0.0),
238            Pt::new(4.0, 0.0),
239            Pt::new(4.0, 3.0),
240            Pt::new(0.0, 3.0),
241            Pt::new(0.0, 0.0),
242        ]);
243        assert_eq!(num_points(&r), 5);
244    }
245
246    /// `num_points.cpp:88` — `POLYGON((0 0,10 10,0 10,0 0))` (outer
247    /// only, closed) → 4; a 5-point closed outer here → 5.
248    #[test]
249    fn polygon_outer_only() {
250        let pg: Poly = polygon![[(0.0, 0.0), (4.0, 0.0), (4.0, 3.0), (0.0, 3.0), (0.0, 0.0)]];
251        assert_eq!(num_points(&pg), 5);
252    }
253
254    /// `num_points.cpp:89` —
255    /// `POLYGON((0 0,0 10,10 10,10 0,0 0),(4 4,6 4,6 6,4 6,4 4))` → 10
256    /// (5 outer + 5 inner).
257    #[test]
258    fn polygon_with_hole_adds_inner_count() {
259        let pg: Poly = polygon![
260            [
261                (0.0, 0.0),
262                (10.0, 0.0),
263                (10.0, 10.0),
264                (0.0, 10.0),
265                (0.0, 0.0)
266            ],
267            [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)],
268        ];
269        assert_eq!(num_points(&pg), 10);
270    }
271
272    /// `num_points.cpp:91` —
273    /// `MULTILINESTRING((0 0,1 1),(2 2,3 3,4 4))` → 5 (2 + 3).
274    #[test]
275    fn multi_linestring_sums_members() {
276        let mls: MultiLinestring<Ls> = MultiLinestring(vec![
277            linestring![(0.0, 0.0), (1.0, 1.0)],
278            linestring![(2.0, 2.0), (3.0, 3.0), (4.0, 4.0)],
279        ]);
280        assert_eq!(num_points(&mls), 5);
281    }
282
283    /// `num_points.cpp:90` — `MULTIPOINT((0 0),(1 1))` → 2.
284    #[test]
285    fn multi_point_counts_members() {
286        let mp = MultiPoint(vec![Pt::new(0.0, 0.0), Pt::new(1.0, 1.0)]);
287        assert_eq!(num_points(&mp), 2);
288    }
289
290    /// `num_points.cpp:92` —
291    /// `MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((0 10,1 10,1 9,0 10)))`
292    /// → 9 (5 + 4).
293    #[test]
294    fn multi_polygon_sums_members() {
295        let mpg: MultiPolygon<Poly> = MultiPolygon(vec![
296            polygon![[
297                (0.0, 0.0),
298                (0.0, 10.0),
299                (10.0, 10.0),
300                (10.0, 0.0),
301                (0.0, 0.0)
302            ]],
303            polygon![[(0.0, 10.0), (1.0, 10.0), (1.0, 9.0), (0.0, 10.0)]],
304        ]);
305        assert_eq!(num_points(&mpg), 9);
306    }
307}