Skip to main content

nodedb_spatial/predicates/
intersects.rs

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