Skip to main content

geometry_algorithm/
within.rs

1//! `within(&p, &g)` / `covered_by(&p, &g)` — point-in-geometry
2//! containment, see `boost/geometry/algorithms/within.hpp` and
3//! `boost/geometry/algorithms/covered_by.hpp`.
4//!
5//! Cartesian-only in v1, point-as-first-argument only. The default
6//! strategy is the winding-number PIP from
7//! `boost/geometry/strategies/cartesian/point_in_poly_winding.hpp`,
8//! selected per geometry *kind* by the tag-keyed
9//! [`geometry_strategy::WithinStrategyForKind`] picker, so any
10//! concept-adapted foreign type resolves through the same path as the
11//! equivalent `geometry-model` value.
12//! Spherical / geographic variants arrive alongside the Haversine /
13//! Andoyer / Vincenty distance work in later tasks.
14
15use geometry_strategy::{WithinStrategy, WithinStrategyForKind};
16use geometry_trait::{Geometry, Point};
17
18/// `true` iff `p` lies in the strict interior of `g`.
19///
20/// Mirrors `boost::geometry::within(p, g)` from
21/// `boost/geometry/algorithms/within.hpp`. The default strategy is
22/// selected per geometry kind by the tag-keyed
23/// [`geometry_strategy::WithinStrategyForKind`] picker (see
24/// [`geometry_strategy::within`]), mirroring Boost's
25/// `services::default_strategy` resolution at
26/// `strategies/cartesian/point_in_poly_winding.hpp:254-258`.
27///
28/// Boundary semantics follow Boost: a point on a segment, vertex, or
29/// box face returns `false` from `within` and `true` from
30/// [`covered_by`]. See `test/strategies/winding.cpp:34-43` for the
31/// reference cases (all corners and sides "officially false").
32#[inline]
33#[must_use]
34pub fn within<P, G>(p: &P, g: &G) -> bool
35where
36    P: Point,
37    G: Geometry,
38    G::Kind: WithinStrategyForKind,
39    <G::Kind as WithinStrategyForKind>::S: WithinStrategy<P, G>,
40{
41    <<G::Kind as WithinStrategyForKind>::S as Default>::default().within(p, g)
42}
43
44/// `true` iff `p` lies in the strict interior **or** on the boundary
45/// of `g`.
46///
47/// Mirrors `boost::geometry::covered_by(p, g)` from
48/// `boost/geometry/algorithms/covered_by.hpp`. Same dispatch story as
49/// [`within`]; the per-geometry impls differ only in how they treat
50/// "result code 0" (boundary) — see the result-code table in
51/// [`geometry_strategy::within`].
52#[inline]
53#[must_use]
54pub fn covered_by<P, G>(p: &P, g: &G) -> bool
55where
56    P: Point,
57    G: Geometry,
58    G::Kind: WithinStrategyForKind,
59    <G::Kind as WithinStrategyForKind>::S: WithinStrategy<P, G>,
60{
61    <<G::Kind as WithinStrategyForKind>::S as Default>::default().covered_by(p, g)
62}
63
64#[cfg(test)]
65mod tests {
66    //! Reference values from `geometry/test/strategies/winding.cpp:19-73`
67    //! (the Cartesian section). Each test cites the C++ line(s) it
68    //! mirrors.
69
70    use super::{covered_by, within};
71    use geometry_cs::Cartesian;
72    use geometry_model::{Box, Point2D, Polygon, Ring, polygon};
73
74    type P = Point2D<f64, Cartesian>;
75
76    fn pt(x: f64, y: f64) -> P {
77        Point2D::new(x, y)
78    }
79
80    fn box_polygon() -> Polygon<P> {
81        polygon![[(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0)]]
82    }
83
84    /// `winding.cpp:30` — `b1` interior point.
85    #[test]
86    fn box_b1_inside() {
87        assert!(within(&pt(1.0, 1.0), &box_polygon()));
88    }
89
90    /// `winding.cpp:31` — `b2` exterior point.
91    #[test]
92    fn box_b2_outside() {
93        assert!(!within(&pt(3.0, 3.0), &box_polygon()));
94    }
95
96    /// `winding.cpp:34-37` — every corner is "officially false".
97    #[test]
98    fn box_corners_excluded() {
99        let p = box_polygon();
100        for (x, y) in [(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)] {
101            assert!(!within(&pt(x, y), &p), "corner ({x},{y})");
102        }
103    }
104
105    /// `winding.cpp:40-43` — every side is "officially false".
106    #[test]
107    fn box_sides_excluded() {
108        let p = box_polygon();
109        for (x, y) in [(0.0, 1.0), (1.0, 2.0), (2.0, 1.0), (1.0, 0.0)] {
110            assert!(!within(&pt(x, y), &p), "side ({x},{y})");
111        }
112    }
113
114    fn triangle() -> Polygon<P> {
115        polygon![[(0.0, 0.0), (0.0, 4.0), (6.0, 0.0), (0.0, 0.0)]]
116    }
117
118    /// `winding.cpp:46`.
119    #[test]
120    fn triangle_t1_inside() {
121        assert!(within(&pt(1.0, 1.0), &triangle()));
122    }
123
124    /// `winding.cpp:47`.
125    #[test]
126    fn triangle_t2_outside() {
127        assert!(!within(&pt(3.0, 3.0), &triangle()));
128    }
129
130    fn with_hole() -> Polygon<P> {
131        polygon![
132            [(0.0, 0.0), (0.0, 3.0), (3.0, 3.0), (3.0, 0.0), (0.0, 0.0)],
133            [(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)]
134        ]
135    }
136
137    /// `winding.cpp:58` — between outer and hole.
138    #[test]
139    fn hole_h1_in_outer_not_hole() {
140        assert!(within(&pt(0.5, 0.5), &with_hole()));
141    }
142
143    /// `winding.cpp:59` — strict interior of the hole.
144    #[test]
145    fn hole_h2a_in_hole_not_within() {
146        assert!(!within(&pt(1.5, 1.5), &with_hole()));
147    }
148
149    /// `covered_by` inverts the boundary rule: corners and sides ARE
150    /// covered, exterior points are not.
151    #[test]
152    fn covered_by_includes_boundary() {
153        let p = box_polygon();
154        assert!(covered_by(&pt(0.0, 0.0), &p)); // corner
155        assert!(covered_by(&pt(0.0, 1.0), &p)); // side
156        assert!(covered_by(&pt(1.0, 1.0), &p)); // interior
157        assert!(!covered_by(&pt(3.0, 3.0), &p)); // outside
158    }
159
160    /// `quickstart.qbk` — point `(3.7, 2.0)` in the quickstart
161    /// polygon is within. Adds doc-visible coverage at the algorithm
162    /// layer.
163    #[test]
164    fn quickstart_polygon_3_7_2_0_is_within() {
165        let q: Polygon<P> = polygon![[
166            (2.0, 1.3),
167            (2.4, 1.7),
168            (2.8, 1.8),
169            (3.4, 1.2),
170            (3.7, 1.6),
171            (3.4, 2.0),
172            (4.1, 3.0),
173            (5.3, 2.6),
174            (5.4, 1.2),
175            (4.9, 0.8),
176            (2.9, 0.7),
177            (2.0, 1.3)
178        ]];
179        assert!(within(&pt(3.7, 2.0), &q));
180    }
181
182    /// Box geometry directly (not a polygon): strict-vs-non-strict
183    /// per-dimension.
184    #[test]
185    fn box_geometry_within_and_covered_by() {
186        let b = Box::from_corners(pt(0.0, 0.0), pt(2.0, 2.0));
187        assert!(within(&pt(1.0, 1.0), &b));
188        assert!(!within(&pt(0.0, 0.0), &b));
189        assert!(covered_by(&pt(0.0, 0.0), &b));
190        assert!(!covered_by(&pt(3.0, 3.0), &b));
191    }
192
193    /// Ring geometry directly (no exterior/interior split).
194    #[test]
195    fn ring_within_and_covered_by() {
196        let r: Ring<P> = Ring::from_vec(vec![
197            pt(0.0, 0.0),
198            pt(0.0, 2.0),
199            pt(2.0, 2.0),
200            pt(2.0, 0.0),
201            pt(0.0, 0.0),
202        ]);
203        assert!(within(&pt(1.0, 1.0), &r));
204        assert!(!within(&pt(0.0, 0.0), &r));
205        assert!(covered_by(&pt(0.0, 0.0), &r));
206    }
207}