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 (see `specs/open-tag-dispatch/`).
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}