Skip to main content

nodedb_spatial/predicates/
intersects.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! ST_Intersects — two geometries share any space (interior or boundary).
4//!
5//! Inverse of ST_Disjoint. A point on a polygon's edge DOES intersect
6//! (unlike ST_Contains where it does not). This is the most commonly
7//! used spatial predicate.
8//!
9//! Implementation: bbox pre-filter → exact geometry test.
10
11use nodedb_types::geometry::{Geometry, point_in_polygon};
12use nodedb_types::geometry_bbox;
13
14use super::edge::{point_on_ring_boundary, ring_edges, segments_intersect};
15
16/// ST_Intersects(a, b) — do geometries A and B share any space?
17pub fn st_intersects(a: &Geometry, b: &Geometry) -> bool {
18    // Bbox pre-filter.
19    let a_bb = geometry_bbox(a);
20    let b_bb = geometry_bbox(b);
21    if !a_bb.intersects(&b_bb) {
22        return false;
23    }
24
25    match (a, b) {
26        // Point–Point: identical within tolerance.
27        (Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) => {
28            (ca[0] - cb[0]).abs() < 1e-12 && (ca[1] - cb[1]).abs() < 1e-12
29        }
30
31        // Point–LineString: point on any segment.
32        (Geometry::Point { coordinates: pt }, Geometry::LineString { coordinates: line })
33        | (Geometry::LineString { coordinates: line }, Geometry::Point { coordinates: pt }) => {
34            point_on_ring_boundary(*pt, line)
35        }
36
37        // Point–Polygon: point inside OR on boundary.
38        (Geometry::Point { coordinates: pt }, Geometry::Polygon { coordinates: rings })
39        | (Geometry::Polygon { coordinates: rings }, Geometry::Point { coordinates: pt }) => {
40            point_intersects_polygon(*pt, rings)
41        }
42
43        // LineString–LineString: any edge crossing or shared point.
44        (Geometry::LineString { coordinates: la }, Geometry::LineString { coordinates: lb }) => {
45            linestrings_intersect(la, lb)
46        }
47
48        // LineString–Polygon: any edge crossing, or any line point inside polygon.
49        (Geometry::LineString { coordinates: line }, Geometry::Polygon { coordinates: rings })
50        | (Geometry::Polygon { coordinates: rings }, Geometry::LineString { coordinates: line }) => {
51            linestring_intersects_polygon(line, rings)
52        }
53
54        // Polygon–Polygon: edge crossing or one inside the other.
55        (Geometry::Polygon { coordinates: ra }, Geometry::Polygon { coordinates: rb }) => {
56            polygons_intersect(ra, rb)
57        }
58
59        // Multi* types: any component intersects.
60        (Geometry::MultiPoint { coordinates }, other)
61        | (other, Geometry::MultiPoint { coordinates }) => coordinates
62            .iter()
63            .any(|pt| st_intersects(&Geometry::Point { coordinates: *pt }, other)),
64
65        (Geometry::MultiLineString { coordinates }, other)
66        | (other, Geometry::MultiLineString { coordinates }) => coordinates.iter().any(|ls| {
67            st_intersects(
68                &Geometry::LineString {
69                    coordinates: ls.clone(),
70                },
71                other,
72            )
73        }),
74
75        (Geometry::MultiPolygon { coordinates }, other)
76        | (other, Geometry::MultiPolygon { coordinates }) => coordinates.iter().any(|poly| {
77            st_intersects(
78                &Geometry::Polygon {
79                    coordinates: poly.clone(),
80                },
81                other,
82            )
83        }),
84
85        (Geometry::GeometryCollection { geometries }, other)
86        | (other, Geometry::GeometryCollection { geometries }) => {
87            geometries.iter().any(|g| st_intersects(g, other))
88        }
89
90        // Unknown future geometry types — conservatively non-intersecting.
91        (&_, &_) => false,
92    }
93}
94
95/// Point intersects polygon — inside OR on boundary.
96fn point_intersects_polygon(pt: [f64; 2], rings: &[Vec<[f64; 2]>]) -> bool {
97    let Some(exterior) = rings.first() else {
98        return false;
99    };
100
101    // On exterior boundary → intersects.
102    if point_on_ring_boundary(pt, exterior) {
103        return true;
104    }
105
106    // Must be inside exterior.
107    if !point_in_polygon(pt[0], pt[1], exterior) {
108        return false;
109    }
110
111    // Must not be inside a hole (but on hole boundary counts as intersects).
112    for hole in &rings[1..] {
113        if point_on_ring_boundary(pt, hole) {
114            return true;
115        }
116        if point_in_polygon(pt[0], pt[1], hole) {
117            return false;
118        }
119    }
120
121    true
122}
123
124/// Two linestrings intersect if any of their segments cross.
125fn linestrings_intersect(la: &[[f64; 2]], lb: &[[f64; 2]]) -> bool {
126    for i in 0..la.len().saturating_sub(1) {
127        for j in 0..lb.len().saturating_sub(1) {
128            if segments_intersect(la[i], la[i + 1], lb[j], lb[j + 1]) {
129                return true;
130            }
131        }
132    }
133    false
134}
135
136/// LineString intersects polygon — any edge crossing, or any point inside.
137fn linestring_intersects_polygon(line: &[[f64; 2]], rings: &[Vec<[f64; 2]>]) -> bool {
138    // Check if any line vertex is inside/on polygon.
139    for pt in line {
140        if point_intersects_polygon(*pt, rings) {
141            return true;
142        }
143    }
144
145    // Check if any line edge crosses any polygon edge.
146    let Some(exterior) = rings.first() else {
147        return false;
148    };
149    let ext_edges = ring_edges(exterior);
150    for i in 0..line.len().saturating_sub(1) {
151        for &(pe_a, pe_b) in &ext_edges {
152            if segments_intersect(line[i], line[i + 1], pe_a, pe_b) {
153                return true;
154            }
155        }
156    }
157
158    // Check hole edges too.
159    for hole in &rings[1..] {
160        let hole_edges = ring_edges(hole);
161        for i in 0..line.len().saturating_sub(1) {
162            for &(he_a, he_b) in &hole_edges {
163                if segments_intersect(line[i], line[i + 1], he_a, he_b) {
164                    return true;
165                }
166            }
167        }
168    }
169
170    false
171}
172
173/// Two polygons intersect — edge crossing or containment.
174fn polygons_intersect(ra: &[Vec<[f64; 2]>], rb: &[Vec<[f64; 2]>]) -> bool {
175    let Some(ext_a) = ra.first() else {
176        return false;
177    };
178    let Some(ext_b) = rb.first() else {
179        return false;
180    };
181
182    // Check if any vertex of B is inside/on A.
183    for pt in ext_b {
184        if point_intersects_polygon(*pt, ra) {
185            return true;
186        }
187    }
188
189    // Check if any vertex of A is inside/on B.
190    for pt in ext_a {
191        if point_intersects_polygon(*pt, rb) {
192            return true;
193        }
194    }
195
196    // Check edge crossings between exteriors.
197    let a_edges = ring_edges(ext_a);
198    let b_edges = ring_edges(ext_b);
199    for &(a1, a2) in &a_edges {
200        for &(b1, b2) in &b_edges {
201            if segments_intersect(a1, a2, b1, b2) {
202                return true;
203            }
204        }
205    }
206
207    false
208}
209
210#[cfg(test)]
211mod tests {
212    use super::super::{st_disjoint, st_within};
213    use super::*;
214
215    fn square() -> Geometry {
216        Geometry::polygon(vec![vec![
217            [0.0, 0.0],
218            [10.0, 0.0],
219            [10.0, 10.0],
220            [0.0, 10.0],
221            [0.0, 0.0],
222        ]])
223    }
224
225    #[test]
226    fn point_inside_polygon_intersects() {
227        assert!(st_intersects(&Geometry::point(5.0, 5.0), &square()));
228    }
229
230    #[test]
231    fn point_on_edge_intersects() {
232        // Unlike ST_Contains, point on edge DOES intersect.
233        assert!(st_intersects(&Geometry::point(5.0, 0.0), &square()));
234    }
235
236    #[test]
237    fn point_on_vertex_intersects() {
238        assert!(st_intersects(&Geometry::point(0.0, 0.0), &square()));
239    }
240
241    #[test]
242    fn point_outside_does_not_intersect() {
243        assert!(!st_intersects(&Geometry::point(20.0, 20.0), &square()));
244    }
245
246    #[test]
247    fn linestring_crosses_polygon() {
248        let line = Geometry::line_string(vec![[-5.0, 5.0], [15.0, 5.0]]);
249        assert!(st_intersects(&line, &square()));
250    }
251
252    #[test]
253    fn linestring_inside_polygon() {
254        let line = Geometry::line_string(vec![[2.0, 2.0], [8.0, 8.0]]);
255        assert!(st_intersects(&line, &square()));
256    }
257
258    #[test]
259    fn linestring_outside_polygon() {
260        let line = Geometry::line_string(vec![[20.0, 20.0], [30.0, 30.0]]);
261        assert!(!st_intersects(&line, &square()));
262    }
263
264    #[test]
265    fn polygons_overlap() {
266        let other = Geometry::polygon(vec![vec![
267            [5.0, 5.0],
268            [15.0, 5.0],
269            [15.0, 15.0],
270            [5.0, 15.0],
271            [5.0, 5.0],
272        ]]);
273        assert!(st_intersects(&square(), &other));
274    }
275
276    #[test]
277    fn polygons_shared_boundary() {
278        let adjacent = Geometry::polygon(vec![vec![
279            [10.0, 0.0],
280            [20.0, 0.0],
281            [20.0, 10.0],
282            [10.0, 10.0],
283            [10.0, 0.0],
284        ]]);
285        // Shared edge → intersects.
286        assert!(st_intersects(&square(), &adjacent));
287    }
288
289    #[test]
290    fn polygons_disjoint() {
291        let far = Geometry::polygon(vec![vec![
292            [20.0, 20.0],
293            [30.0, 20.0],
294            [30.0, 30.0],
295            [20.0, 30.0],
296            [20.0, 20.0],
297        ]]);
298        assert!(!st_intersects(&square(), &far));
299        assert!(st_disjoint(&square(), &far));
300    }
301
302    #[test]
303    fn linestrings_cross() {
304        let a = Geometry::line_string(vec![[0.0, 0.0], [10.0, 10.0]]);
305        let b = Geometry::line_string(vec![[0.0, 10.0], [10.0, 0.0]]);
306        assert!(st_intersects(&a, &b));
307    }
308
309    #[test]
310    fn linestrings_parallel_no_cross() {
311        let a = Geometry::line_string(vec![[0.0, 0.0], [10.0, 0.0]]);
312        let b = Geometry::line_string(vec![[0.0, 1.0], [10.0, 1.0]]);
313        assert!(!st_intersects(&a, &b));
314    }
315
316    #[test]
317    fn st_within_works() {
318        let inner = Geometry::point(5.0, 5.0);
319        assert!(st_within(&inner, &square()));
320        assert!(!st_within(&square(), &inner));
321    }
322
323    #[test]
324    fn st_disjoint_works() {
325        assert!(!st_disjoint(&Geometry::point(5.0, 5.0), &square()));
326        assert!(st_disjoint(&Geometry::point(20.0, 20.0), &square()));
327    }
328
329    #[test]
330    fn linestring_endpoint_touches_polygon() {
331        let line = Geometry::line_string(vec![[10.0, 5.0], [15.0, 5.0]]);
332        // Endpoint touches polygon edge → intersects.
333        assert!(st_intersects(&line, &square()));
334    }
335
336    #[test]
337    fn polygon_inside_polygon_intersects() {
338        let inner = Geometry::polygon(vec![vec![
339            [2.0, 2.0],
340            [8.0, 2.0],
341            [8.0, 8.0],
342            [2.0, 8.0],
343            [2.0, 2.0],
344        ]]);
345        assert!(st_intersects(&square(), &inner));
346    }
347}