Skip to main content

geometry_algorithm/
intersects.rs

1//! `intersects(&a, &b)` — see
2//! `boost/geometry/algorithms/intersects.hpp`.
3//!
4//! Cartesian-only in v1. Default strategy is
5//! [`geometry_strategy::CartesianIntersects`], which implements every
6//! pair in one canonical direction; the
7//! [`geometry_strategy::intersects::Reversed`] blanket lifts each
8//! pair to its swap.
9
10use geometry_strategy::intersects::Reversed;
11use geometry_strategy::{CartesianIntersects, IntersectsStrategy};
12
13/// `true` iff `a` and `b` share at least one point.
14///
15/// Mirrors `boost::geometry::intersects(a, b)` from
16/// `boost/geometry/algorithms/intersects.hpp`. Tries the canonical
17/// pair direction first, falling back to the reversed direction
18/// through the `Reversed<CartesianIntersects>` blanket so callers
19/// never have to remember which argument order has an explicit impl.
20#[inline]
21#[must_use]
22pub fn intersects<A, B>(a: &A, b: &B) -> bool
23where
24    CartesianIntersects: IntersectsStrategy<A, B>,
25{
26    CartesianIntersects.intersects(a, b)
27}
28
29/// Reversed-direction entry point — used when only `(B, A)` has an
30/// explicit impl. Mirrors the Boost `reverse_dispatch` fallback in
31/// `algorithms/detail/intersects/interface.hpp`.
32#[inline]
33#[must_use]
34pub fn intersects_reversed<A, B>(a: &A, b: &B) -> bool
35where
36    Reversed<CartesianIntersects>: IntersectsStrategy<A, B>,
37{
38    Reversed(CartesianIntersects).intersects(a, b)
39}
40
41#[cfg(test)]
42mod tests {
43    //! Reference values from
44    //! `geometry/test/algorithms/intersects/intersects.cpp:38-79`.
45    //! Each test cites the C++ line it mirrors.
46
47    use super::intersects;
48    use geometry_cs::Cartesian;
49    use geometry_model::{Linestring, Point2D, Polygon, Segment, linestring, polygon};
50
51    type P = Point2D<f64, Cartesian>;
52    type LS = Linestring<P>;
53
54    fn pt(x: f64, y: f64) -> P {
55        Point2D::new(x, y)
56    }
57
58    /// `intersects.cpp:38` — linestring crosses segment.
59    #[test]
60    fn ls_crosses_segment() {
61        let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0), (2.0, 5.0)];
62        let s = Segment::new(pt(2.0, 0.0), pt(2.0, 6.0));
63        assert!(intersects(&ls, &s));
64    }
65
66    /// `intersects.cpp:39` — linestring touches segment endpoint.
67    #[test]
68    fn ls_touches_segment_endpoint() {
69        let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0)];
70        let s = Segment::new(pt(1.0, 0.0), pt(1.0, 1.0));
71        assert!(intersects(&ls, &s));
72    }
73
74    /// `intersects.cpp:41` — disjoint linestring and segment.
75    #[test]
76    fn ls_disjoint_from_segment() {
77        let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0)];
78        let s = Segment::new(pt(3.0, 0.0), pt(4.0, 1.0));
79        assert!(!intersects(&ls, &s));
80    }
81
82    /// `intersects.cpp:50` — linestring/linestring proper crossing.
83    #[test]
84    fn ls_crosses_ls() {
85        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
86        let b: LS = linestring![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
87        assert!(intersects(&a, &b));
88    }
89
90    /// `intersects.cpp:55` — collinear overlap.
91    #[test]
92    fn ls_overlap_collinear() {
93        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
94        let b: LS = linestring![(1.0, 0.0), (4.0, 0.0), (5.0, 0.0)];
95        assert!(intersects(&a, &b));
96    }
97
98    /// `intersects.cpp:69` — linestring inside polygon.
99    #[test]
100    fn ls_inside_polygon() {
101        let ls: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
102        let p: Polygon<P> = polygon![[
103            (0.0, 0.0),
104            (10.0, 0.0),
105            (10.0, 10.0),
106            (0.0, 10.0),
107            (0.0, 0.0)
108        ]];
109        assert!(intersects(&ls, &p));
110    }
111
112    /// `intersects.cpp:71` — linestring outside polygon.
113    #[test]
114    fn ls_outside_polygon() {
115        let ls: LS = linestring![(11.0, 0.0), (12.0, 12.0)];
116        let p: Polygon<P> = polygon![[
117            (0.0, 0.0),
118            (10.0, 0.0),
119            (10.0, 10.0),
120            (0.0, 10.0),
121            (0.0, 0.0)
122        ]];
123        assert!(!intersects(&ls, &p));
124    }
125
126    /// Reverse-direction call — `intersects(polygon, linestring)`
127    /// resolves through the per-pair reverse impl on
128    /// `CartesianIntersects` and agrees with the canonical direction.
129    #[test]
130    fn reversed_pair_agrees() {
131        let ls: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
132        let p: Polygon<P> = polygon![[
133            (0.0, 0.0),
134            (10.0, 0.0),
135            (10.0, 10.0),
136            (0.0, 10.0),
137            (0.0, 0.0)
138        ]];
139        assert_eq!(intersects(&ls, &p), intersects(&p, &ls));
140    }
141
142    fn square(shift: f64) -> Polygon<P> {
143        polygon![[
144            (shift, shift),
145            (shift + 4.0, shift),
146            (shift + 4.0, shift + 4.0),
147            (shift, shift + 4.0),
148            (shift, shift)
149        ]]
150    }
151
152    /// Point × Point intersects iff the two points are coordinate-equal.
153    #[test]
154    fn point_point_intersects_iff_equal() {
155        assert!(intersects(&pt(1.0, 2.0), &pt(1.0, 2.0)));
156        assert!(!intersects(&pt(1.0, 2.0), &pt(1.0, 2.5)));
157    }
158
159    /// Point × Segment is true exactly when the point lies on the closed
160    /// segment (interior, endpoint) and false off it.
161    #[test]
162    fn point_segment_membership() {
163        let s = Segment::new(pt(0.0, 0.0), pt(4.0, 4.0));
164        assert!(intersects(&pt(2.0, 2.0), &s)); // interior
165        assert!(intersects(&pt(0.0, 0.0), &s)); // endpoint
166        assert!(!intersects(&pt(2.0, 3.0), &s)); // off the line
167        assert!(!intersects(&pt(5.0, 5.0), &s)); // collinear, past end
168    }
169
170    /// Segment × Segment detects a proper crossing and rejects a
171    /// parallel offset pair.
172    #[test]
173    fn segment_segment_crossing_and_parallel() {
174        let a = Segment::new(pt(0.0, 0.0), pt(4.0, 4.0));
175        let cross = Segment::new(pt(0.0, 4.0), pt(4.0, 0.0));
176        let parallel = Segment::new(pt(0.0, 1.0), pt(4.0, 5.0));
177        assert!(intersects(&a, &cross));
178        assert!(!intersects(&a, &parallel));
179    }
180
181    /// An empty linestring operand never intersects (the `it.next()`
182    /// guard) — in either position.
183    #[test]
184    fn empty_linestring_operands_do_not_intersect() {
185        let empty: LS = linestring![];
186        let s = Segment::new(pt(0.0, 0.0), pt(1.0, 1.0));
187        assert!(!intersects(&empty, &s));
188
189        let non_empty: LS = linestring![(0.0, 0.0), (1.0, 1.0)];
190        assert!(!intersects(&empty, &non_empty));
191        assert!(!intersects(&non_empty, &empty));
192    }
193
194    /// Point × Polygon is `covered_by`: interior true, boundary true,
195    /// exterior false.
196    #[test]
197    fn point_polygon_is_covered_by() {
198        let sq = square(0.0);
199        assert!(intersects(&pt(2.0, 2.0), &sq)); // interior
200        assert!(intersects(&pt(0.0, 2.0), &sq)); // on edge
201        assert!(!intersects(&pt(9.0, 9.0), &sq)); // outside
202    }
203
204    /// Polygon × Polygon: containment (either direction), edge crossing,
205    /// and full disjointness are each classified correctly.
206    #[test]
207    fn polygon_polygon_containment_crossing_disjoint() {
208        let a = square(0.0); // (0,0)-(4,4)
209        let contained: Polygon<P> =
210            polygon![[(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)]];
211        let overlapping = square(2.0); // (2,2)-(6,6): edges cross
212        let disjoint = square(100.0);
213
214        assert!(intersects(&a, &contained)); // B inside A
215        assert!(intersects(&contained, &a)); // A inside B (other arm)
216        assert!(intersects(&a, &overlapping)); // edge crossing
217        assert!(!intersects(&a, &disjoint)); // fully apart
218    }
219
220    /// A polygon with a hole crosses another polygon's exterior through
221    /// its hole edge — exercises the interior-ring crossing arms.
222    #[test]
223    fn polygon_polygon_hole_edges_are_tested() {
224        // `a` is a 10x10 square with a central 4x4 hole (2..6).
225        let a: Polygon<P> = polygon![
226            [
227                (0.0, 0.0),
228                (10.0, 0.0),
229                (10.0, 10.0),
230                (0.0, 10.0),
231                (0.0, 0.0)
232            ],
233            [(2.0, 2.0), (6.0, 2.0), (6.0, 6.0), (2.0, 6.0), (2.0, 2.0)]
234        ];
235        // `b` sits inside the hole but its edges cross the hole boundary.
236        let b: Polygon<P> = polygon![[(4.0, 4.0), (8.0, 4.0), (8.0, 8.0), (4.0, 8.0), (4.0, 4.0)]];
237        assert!(intersects(&a, &b));
238    }
239
240    /// Every reverse-ordered pair agrees with its forward result — the
241    /// reverse per-pair impls delegate to the forward kernels.
242    #[test]
243    fn reverse_pairs_delegate_to_forward() {
244        let s = Segment::new(pt(0.0, 0.0), pt(4.0, 4.0));
245        let p = pt(2.0, 2.0);
246        assert_eq!(intersects(&s, &p), intersects(&p, &s));
247
248        let ls: LS = linestring![(1.0, 1.0), (3.0, 3.0), (2.0, 5.0)];
249        assert_eq!(intersects(&s, &ls), intersects(&ls, &s));
250
251        let sq = square(0.0);
252        assert_eq!(intersects(&sq, &p), intersects(&p, &sq));
253
254        let ls_in: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
255        assert_eq!(intersects(&sq, &ls_in), intersects(&ls_in, &sq));
256        // And the delegations are all `true` for these intersecting inputs.
257        assert!(intersects(&s, &p));
258        assert!(intersects(&sq, &p));
259        assert!(intersects(&sq, &ls_in));
260    }
261
262    /// Point × Point compares all three ordinates for 3D points — the
263    /// `2 =>` arm only a 3D point reaches.
264    #[test]
265    fn point_point_intersects_in_three_dimensions() {
266        use geometry_model::Point3D;
267        type P3 = Point3D<f64, Cartesian>;
268        let a = P3::new(1.0, 2.0, 3.0);
269        let same = P3::new(1.0, 2.0, 3.0);
270        let diff_z = P3::new(1.0, 2.0, 9.0);
271        assert!(intersects(&a, &same));
272        assert!(!intersects(&a, &diff_z));
273    }
274
275    /// A linestring lying inside a polygon (no edge crossing) intersects
276    /// via the vertex-`covered_by` fast path.
277    #[test]
278    fn linestring_polygon_inside_via_vertex_path() {
279        let ls: LS = linestring![(1.0, 1.0), (2.0, 2.0)];
280        assert!(intersects(&ls, &square(0.0)));
281    }
282}