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 (see `specs/open-tag-dispatch/`).
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 of
50/// `specs/open-tag-dispatch/`); users call [`num_points`], not this
51/// trait.
52#[doc(hidden)]
53pub trait NumPointsStrategy<G> {
54    /// Total number of points in `g`.
55    fn num_points(&self, g: &G) -> usize;
56}
57
58#[derive(Default)]
59#[doc(hidden)]
60pub struct PointNumPoints;
61#[derive(Default)]
62#[doc(hidden)]
63pub struct SegmentNumPoints;
64#[derive(Default)]
65#[doc(hidden)]
66pub struct BoxNumPoints;
67#[derive(Default)]
68#[doc(hidden)]
69pub struct LinestringNumPoints;
70#[derive(Default)]
71#[doc(hidden)]
72pub struct RingNumPoints;
73#[derive(Default)]
74#[doc(hidden)]
75pub struct PolygonNumPoints;
76#[derive(Default)]
77#[doc(hidden)]
78pub struct MultiPointNumPoints;
79#[derive(Default)]
80#[doc(hidden)]
81pub struct MultiLinestringNumPoints;
82#[derive(Default)]
83#[doc(hidden)]
84pub struct MultiPolygonNumPoints;
85
86impl<G: PointTrait> NumPointsStrategy<G> for PointNumPoints {
87    fn num_points(&self, _g: &G) -> usize {
88        1
89    }
90}
91
92impl<G: SegmentTrait> NumPointsStrategy<G> for SegmentNumPoints {
93    fn num_points(&self, _g: &G) -> usize {
94        2
95    }
96}
97
98impl<G: BoxTrait> NumPointsStrategy<G> for BoxNumPoints {
99    fn num_points(&self, _g: &G) -> usize {
100        // Boost counts a box as its `2^D` corner points
101        // (`num_points.hpp:100-101`: `1 << dimension`) — 4 in 2D, 8 in 3D.
102        1usize << <G::Point as PointTrait>::DIM
103    }
104}
105
106impl<G: LinestringTrait> NumPointsStrategy<G> for LinestringNumPoints {
107    fn num_points(&self, g: &G) -> usize {
108        g.points().count()
109    }
110}
111
112impl<G: RingTrait> NumPointsStrategy<G> for RingNumPoints {
113    fn num_points(&self, g: &G) -> usize {
114        g.points().count()
115    }
116}
117
118impl<G: PolygonTrait> NumPointsStrategy<G> for PolygonNumPoints {
119    fn num_points(&self, g: &G) -> usize {
120        let mut n = g.exterior().points().count();
121        for inner in g.interiors() {
122            n += inner.points().count();
123        }
124        n
125    }
126}
127
128impl<G: MultiPointTrait> NumPointsStrategy<G> for MultiPointNumPoints {
129    fn num_points(&self, g: &G) -> usize {
130        g.points().count()
131    }
132}
133
134impl<G: MultiLinestringTrait> NumPointsStrategy<G> for MultiLinestringNumPoints {
135    fn num_points(&self, g: &G) -> usize {
136        // Recurse through the public `num_points` on each member — the
137        // member is a strictly-smaller kind (Linestring), so
138        // monomorphisation terminates on the concrete member type.
139        g.linestrings().map(num_points).sum()
140    }
141}
142
143impl<G: MultiPolygonTrait> NumPointsStrategy<G> for MultiPolygonNumPoints {
144    fn num_points(&self, g: &G) -> usize {
145        g.polygons().map(num_points).sum()
146    }
147}
148
149/// Type-level "which `NumPointsStrategy` struct does this geometry *kind*
150/// use". One impl per [`geometry_tag`] kind tag, keyed on the tag (never
151/// on a concept blanket, which would overlap — E0119), so any
152/// concept-adapted foreign type with the same `Kind` resolves to the same
153/// struct as the equivalent model value. Users call [`num_points`].
154#[doc(hidden)]
155pub trait NumPointsStrategyForKind {
156    /// The per-kind [`NumPointsStrategy`] struct this tag is counted with.
157    type S: Default;
158}
159
160impl NumPointsStrategyForKind for PointTag {
161    type S = PointNumPoints;
162}
163impl NumPointsStrategyForKind for SegmentTag {
164    type S = SegmentNumPoints;
165}
166impl NumPointsStrategyForKind for BoxTag {
167    type S = BoxNumPoints;
168}
169impl NumPointsStrategyForKind for LinestringTag {
170    type S = LinestringNumPoints;
171}
172impl NumPointsStrategyForKind for RingTag {
173    type S = RingNumPoints;
174}
175impl NumPointsStrategyForKind for PolygonTag {
176    type S = PolygonNumPoints;
177}
178impl NumPointsStrategyForKind for MultiPointTag {
179    type S = MultiPointNumPoints;
180}
181impl NumPointsStrategyForKind for MultiLinestringTag {
182    type S = MultiLinestringNumPoints;
183}
184impl NumPointsStrategyForKind for MultiPolygonTag {
185    type S = MultiPolygonNumPoints;
186}
187
188#[cfg(test)]
189mod tests {
190    //! Reference values from `geometry/test/algorithms/num_points.cpp`.
191    //! A box counts as its `2^D` corner points (`num_points.hpp:100-101`)
192    //! — 4 for a 2D box.
193
194    use super::num_points;
195    use geometry_cs::Cartesian;
196    use geometry_model::{
197        Box, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point2D, Polygon, Segment,
198        linestring, polygon,
199    };
200
201    type Pt = Point2D<f64, Cartesian>;
202    type Ls = Linestring<Pt>;
203    type Poly = Polygon<Pt>;
204
205    /// `num_points.cpp:80` — `POINT(0 0)` → 1.
206    #[test]
207    fn point_is_one() {
208        assert_eq!(num_points(&Pt::new(0.0, 0.0)), 1);
209    }
210
211    /// `num_points.cpp:82` — `SEGMENT(0 0,1 1)` → 2.
212    #[test]
213    fn segment_is_two() {
214        let s = Segment::new(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0));
215        assert_eq!(num_points(&s), 2);
216    }
217
218    /// `num_points.hpp:100-101` — a 2D box has `2^2 = 4` corner points.
219    #[test]
220    fn box_2d_is_four() {
221        let b = Box::from_corners(Pt::new(0.0, 0.0), Pt::new(1.0, 1.0));
222        assert_eq!(num_points(&b), 4);
223    }
224
225    /// `num_points.cpp:81` uses a 2-point linestring; the 3-point case
226    /// here matches the plan's `(0,0),(1,1),(2,2)` fixture → 3.
227    #[test]
228    fn linestring_three_points() {
229        let ls: Ls = linestring![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
230        assert_eq!(num_points(&ls), 3);
231    }
232
233    /// `num_points.cpp:88` — `POLYGON((0 0,10 10,0 10,0 0))` (outer
234    /// only, closed) → 4; a 5-point closed outer here → 5.
235    #[test]
236    fn polygon_outer_only() {
237        let pg: Poly = polygon![[(0.0, 0.0), (4.0, 0.0), (4.0, 3.0), (0.0, 3.0), (0.0, 0.0)]];
238        assert_eq!(num_points(&pg), 5);
239    }
240
241    /// `num_points.cpp:89` —
242    /// `POLYGON((0 0,0 10,10 10,10 0,0 0),(4 4,6 4,6 6,4 6,4 4))` → 10
243    /// (5 outer + 5 inner).
244    #[test]
245    fn polygon_with_hole_adds_inner_count() {
246        let pg: Poly = polygon![
247            [
248                (0.0, 0.0),
249                (10.0, 0.0),
250                (10.0, 10.0),
251                (0.0, 10.0),
252                (0.0, 0.0)
253            ],
254            [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)],
255        ];
256        assert_eq!(num_points(&pg), 10);
257    }
258
259    /// `num_points.cpp:91` —
260    /// `MULTILINESTRING((0 0,1 1),(2 2,3 3,4 4))` → 5 (2 + 3).
261    #[test]
262    fn multi_linestring_sums_members() {
263        let mls: MultiLinestring<Ls> = MultiLinestring(vec![
264            linestring![(0.0, 0.0), (1.0, 1.0)],
265            linestring![(2.0, 2.0), (3.0, 3.0), (4.0, 4.0)],
266        ]);
267        assert_eq!(num_points(&mls), 5);
268    }
269
270    /// `num_points.cpp:90` — `MULTIPOINT((0 0),(1 1))` → 2.
271    #[test]
272    fn multi_point_counts_members() {
273        let mp = MultiPoint(vec![Pt::new(0.0, 0.0), Pt::new(1.0, 1.0)]);
274        assert_eq!(num_points(&mp), 2);
275    }
276
277    /// `num_points.cpp:92` —
278    /// `MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((0 10,1 10,1 9,0 10)))`
279    /// → 9 (5 + 4).
280    #[test]
281    fn multi_polygon_sums_members() {
282        let mpg: MultiPolygon<Poly> = MultiPolygon(vec![
283            polygon![[
284                (0.0, 0.0),
285                (0.0, 10.0),
286                (10.0, 10.0),
287                (10.0, 0.0),
288                (0.0, 0.0)
289            ]],
290            polygon![[(0.0, 10.0), (1.0, 10.0), (1.0, 9.0), (0.0, 10.0)]],
291        ]);
292        assert_eq!(num_points(&mpg), 9);
293    }
294}