Skip to main content

oxigdal_algorithms/vector/
contains.rs

1//! Spatial predicates for geometric relationships
2//!
3//! This module provides binary spatial predicates that test topological
4//! relationships between geometries following the DE-9IM model.
5//!
6//! # Predicates
7//!
8//! - **Contains**: Tests if geometry A completely contains geometry B
9//! - **Within**: Tests if geometry A is completely within geometry B
10//! - **Intersects**: Tests if geometries share any points
11//! - **Touches**: Tests if geometries share boundary points but not interior points
12//! - **Disjoint**: Tests if geometries share no points
13//! - **Overlaps**: Tests if geometries share some but not all points
14//! - **Covers**: Tests if every point of B is a point of A
15//! - **CoveredBy**: Tests if every point of A is a point of B
16//!
17//! # Examples
18//!
19//! ```
20//! use oxigdal_algorithms::vector::{Polygon, LineString, Coordinate, point_in_polygon_or_boundary};
21//!
22//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! let coords = vec![
24//!     Coordinate::new_2d(0.0, 0.0),
25//!     Coordinate::new_2d(4.0, 0.0),
26//!     Coordinate::new_2d(4.0, 4.0),
27//!     Coordinate::new_2d(0.0, 4.0),
28//!     Coordinate::new_2d(0.0, 0.0),
29//! ];
30//! let exterior = LineString::new(coords)?;
31//! let polygon = Polygon::new(exterior, vec![])?;
32//! let point = Coordinate::new_2d(2.0, 2.0);
33//! let result = point_in_polygon_or_boundary(&point, &polygon);
34//! // result should be true
35//! # Ok(())
36//! # }
37//! ```
38
39use crate::error::Result;
40use oxigdal_core::vector::{Coordinate, Point, Polygon};
41
42/// Tests if geometry A contains geometry B
43///
44/// Geometry A contains B if:
45/// - No points of B lie in the exterior of A
46/// - At least one point of the interior of B lies in the interior of A
47///
48/// # Arguments
49///
50/// * `a` - Container geometry
51/// * `b` - Contained geometry
52///
53/// # Returns
54///
55/// True if A contains B
56///
57/// # Errors
58///
59/// Returns error if geometries are invalid
60pub fn contains<T: ContainsPredicate>(a: &T, b: &T) -> Result<bool> {
61    a.contains(b)
62}
63
64/// Tests if geometry A is within geometry B (inverse of contains)
65///
66/// # Arguments
67///
68/// * `a` - Inner geometry
69/// * `b` - Outer geometry
70///
71/// # Returns
72///
73/// True if A is within B
74///
75/// # Errors
76///
77/// Returns error if geometries are invalid
78pub fn within<T: ContainsPredicate>(a: &T, b: &T) -> Result<bool> {
79    b.contains(a)
80}
81
82/// Tests if geometries intersect (share any points)
83///
84/// # Arguments
85///
86/// * `a` - First geometry
87/// * `b` - Second geometry
88///
89/// # Returns
90///
91/// True if geometries intersect
92///
93/// # Errors
94///
95/// Returns error if geometries are invalid
96pub fn intersects<T: IntersectsPredicate>(a: &T, b: &T) -> Result<bool> {
97    a.intersects(b)
98}
99
100/// Tests if geometries are disjoint (share no points)
101///
102/// # Arguments
103///
104/// * `a` - First geometry
105/// * `b` - Second geometry
106///
107/// # Returns
108///
109/// True if geometries are disjoint
110///
111/// # Errors
112///
113/// Returns error if geometries are invalid
114pub fn disjoint<T: IntersectsPredicate>(a: &T, b: &T) -> Result<bool> {
115    Ok(!a.intersects(b)?)
116}
117
118/// Tests if geometries touch (share boundary but not interior)
119///
120/// # Arguments
121///
122/// * `a` - First geometry
123/// * `b` - Second geometry
124///
125/// # Returns
126///
127/// True if geometries touch
128///
129/// # Errors
130///
131/// Returns error if geometries are invalid
132pub fn touches<T: TouchesPredicate>(a: &T, b: &T) -> Result<bool> {
133    a.touches(b)
134}
135
136/// Tests if geometries overlap (share some but not all points)
137///
138/// Two geometries overlap if:
139/// - They have the same dimension
140/// - Their interiors intersect
141/// - Neither geometry completely contains the other
142///
143/// # Arguments
144///
145/// * `a` - First geometry
146/// * `b` - Second geometry
147///
148/// # Returns
149///
150/// True if geometries overlap
151///
152/// # Errors
153///
154/// Returns error if geometries are invalid
155pub fn overlaps<T: OverlapsPredicate>(a: &T, b: &T) -> Result<bool> {
156    a.overlaps(b)
157}
158
159/// Tests if one geometry crosses another
160///
161/// Two geometries cross if:
162/// - They have some but not all interior points in common
163/// - The dimension of the intersection is less than the maximum dimension of the two geometries
164///
165/// # Arguments
166///
167/// * `a` - First geometry
168/// * `b` - Second geometry
169///
170/// # Returns
171///
172/// True if geometries cross
173///
174/// # Errors
175///
176/// Returns error if geometries are invalid
177pub fn crosses<T: CrossesPredicate>(a: &T, b: &T) -> Result<bool> {
178    a.crosses(b)
179}
180
181/// Trait for geometries that support contains predicate
182pub trait ContainsPredicate {
183    /// Tests if this geometry contains another
184    fn contains(&self, other: &Self) -> Result<bool>;
185}
186
187/// Trait for geometries that support intersects predicate
188pub trait IntersectsPredicate {
189    /// Tests if this geometry intersects another
190    fn intersects(&self, other: &Self) -> Result<bool>;
191}
192
193/// Trait for geometries that support touches predicate
194pub trait TouchesPredicate {
195    /// Tests if this geometry touches another
196    fn touches(&self, other: &Self) -> Result<bool>;
197}
198
199/// Trait for geometries that support overlaps predicate
200pub trait OverlapsPredicate {
201    /// Tests if this geometry overlaps another
202    fn overlaps(&self, other: &Self) -> Result<bool>;
203}
204
205/// Trait for geometries that support crosses predicate
206pub trait CrossesPredicate {
207    /// Tests if this geometry crosses another
208    fn crosses(&self, other: &Self) -> Result<bool>;
209}
210
211// Implement ContainsPredicate for Point
212impl ContainsPredicate for Point {
213    fn contains(&self, other: &Self) -> Result<bool> {
214        // A point contains another point only if they're the same
215        Ok((self.coord.x - other.coord.x).abs() < f64::EPSILON
216            && (self.coord.y - other.coord.y).abs() < f64::EPSILON)
217    }
218}
219
220// Implement ContainsPredicate for Polygon
221impl ContainsPredicate for Polygon {
222    fn contains(&self, other: &Self) -> Result<bool> {
223        // Check if all vertices of other are inside or on boundary of self
224        for coord in &other.exterior.coords {
225            if !point_in_polygon_or_boundary(coord, self) {
226                return Ok(false);
227            }
228        }
229
230        // Check if any vertex is strictly inside (not just on boundary)
231        let mut has_interior_point = false;
232        for coord in &other.exterior.coords {
233            if point_strictly_inside_polygon(coord, self) {
234                has_interior_point = true;
235                break;
236            }
237        }
238
239        Ok(has_interior_point)
240    }
241}
242
243// Implement IntersectsPredicate for Point
244impl IntersectsPredicate for Point {
245    fn intersects(&self, other: &Self) -> Result<bool> {
246        self.contains(other)
247    }
248}
249
250// Implement IntersectsPredicate for Polygon
251impl IntersectsPredicate for Polygon {
252    fn intersects(&self, other: &Self) -> Result<bool> {
253        // Check if any vertices are inside
254        for coord in &other.exterior.coords {
255            if point_in_polygon_or_boundary(coord, self) {
256                return Ok(true);
257            }
258        }
259
260        for coord in &self.exterior.coords {
261            if point_in_polygon_or_boundary(coord, other) {
262                return Ok(true);
263            }
264        }
265
266        // Check if any edges intersect
267        Ok(rings_intersect(
268            &self.exterior.coords,
269            &other.exterior.coords,
270        ))
271    }
272}
273
274// Implement TouchesPredicate for Polygon
275impl TouchesPredicate for Polygon {
276    fn touches(&self, other: &Self) -> Result<bool> {
277        let mut has_boundary_contact = false;
278        let mut has_interior_contact = false;
279
280        // Check vertices of other against self
281        for coord in &other.exterior.coords {
282            if point_on_polygon_boundary(coord, self) {
283                has_boundary_contact = true;
284            } else if point_strictly_inside_polygon(coord, self) {
285                has_interior_contact = true;
286            }
287        }
288
289        // Check vertices of self against other
290        for coord in &self.exterior.coords {
291            if point_strictly_inside_polygon(coord, other) {
292                has_interior_contact = true;
293            }
294        }
295
296        Ok(has_boundary_contact && !has_interior_contact)
297    }
298}
299
300// Implement OverlapsPredicate for Point
301impl OverlapsPredicate for Point {
302    fn overlaps(&self, _other: &Self) -> Result<bool> {
303        // Points cannot overlap - they are either the same (equal) or disjoint
304        Ok(false)
305    }
306}
307
308// Implement OverlapsPredicate for Polygon
309impl OverlapsPredicate for Polygon {
310    fn overlaps(&self, other: &Self) -> Result<bool> {
311        // Two polygons overlap if:
312        // 1. They intersect
313        // 2. Neither completely contains the other
314        // 3. They have interior points in common
315
316        // First check if they intersect at all
317        if !self.intersects(other)? {
318            return Ok(false);
319        }
320
321        // Check if either polygon completely contains the other
322        if self.contains(other)? || other.contains(self)? {
323            return Ok(false);
324        }
325
326        // Check if they have interior points in common
327        // If they intersect and neither contains the other, they must overlap
328        Ok(true)
329    }
330}
331
332// Implement CrossesPredicate for Point
333impl CrossesPredicate for Point {
334    fn crosses(&self, _other: &Self) -> Result<bool> {
335        // Points cannot cross each other
336        Ok(false)
337    }
338}
339
340// Implement CrossesPredicate for Polygon
341impl CrossesPredicate for Polygon {
342    fn crosses(&self, _other: &Self) -> Result<bool> {
343        // Per OGC DE-9IM, "crosses" is undefined for same-dimension geometries
344        // (Polygon/Polygon, both dimension 2). The predicate always returns false.
345        // Use `overlaps()` instead for partial overlap between polygons.
346        Ok(false)
347    }
348}
349
350/// Tests if a point is inside or on the boundary of a polygon
351pub fn point_in_polygon_or_boundary(point: &Coordinate, polygon: &Polygon) -> bool {
352    point_in_polygon_boundary(point, polygon) || point_on_polygon_boundary(point, polygon)
353}
354
355/// Tests if a point is strictly inside a polygon (not on boundary)
356pub fn point_strictly_inside_polygon(point: &Coordinate, polygon: &Polygon) -> bool {
357    point_in_polygon_boundary(point, polygon) && !point_on_polygon_boundary(point, polygon)
358}
359
360/// Tests if a point is on the boundary of a polygon
361pub fn point_on_polygon_boundary(point: &Coordinate, polygon: &Polygon) -> bool {
362    point_on_ring(&polygon.exterior.coords, point)
363        || polygon
364            .interiors
365            .iter()
366            .any(|hole| point_on_ring(&hole.coords, point))
367}
368
369/// Tests if a point is on a ring (linestring)
370fn point_on_ring(ring: &[Coordinate], point: &Coordinate) -> bool {
371    for i in 0..ring.len().saturating_sub(1) {
372        if point_on_segment(point, &ring[i], &ring[i + 1]) {
373            return true;
374        }
375    }
376    false
377}
378
379/// Tests if a point lies on a line segment
380fn point_on_segment(point: &Coordinate, seg_start: &Coordinate, seg_end: &Coordinate) -> bool {
381    // Check if point is collinear with segment
382    let cross = (seg_end.y - seg_start.y) * (point.x - seg_start.x)
383        - (seg_end.x - seg_start.x) * (point.y - seg_start.y);
384
385    if cross.abs() > f64::EPSILON {
386        return false;
387    }
388
389    // Check if point is within segment bounds
390    let dot = (point.x - seg_start.x) * (seg_end.x - seg_start.x)
391        + (point.y - seg_start.y) * (seg_end.y - seg_start.y);
392
393    let len_sq = (seg_end.x - seg_start.x).powi(2) + (seg_end.y - seg_start.y).powi(2);
394
395    if dot < -f64::EPSILON || dot > len_sq + f64::EPSILON {
396        return false;
397    }
398
399    true
400}
401
402/// Ray casting algorithm for point-in-polygon test
403fn point_in_polygon_boundary(point: &Coordinate, polygon: &Polygon) -> bool {
404    let mut inside = ray_casting_test(point, &polygon.exterior.coords);
405
406    // Subtract holes using XOR
407    for hole in &polygon.interiors {
408        if ray_casting_test(point, &hole.coords) {
409            inside = !inside;
410        }
411    }
412
413    inside
414}
415
416/// Ray casting algorithm implementation
417fn ray_casting_test(point: &Coordinate, ring: &[Coordinate]) -> bool {
418    let mut inside = false;
419    let n = ring.len();
420
421    // Guard against empty rings: `Polygon`'s public fields allow constructing a
422    // polygon with an empty interior ring (bypassing `Polygon::new`'s length
423    // validation), and `n - 1` would underflow `usize` and panic under
424    // overflow-checked (debug/test) builds. An empty ring contains no points.
425    if n == 0 {
426        return false;
427    }
428
429    let mut j = n - 1;
430    for i in 0..n {
431        let xi = ring[i].x;
432        let yi = ring[i].y;
433        let xj = ring[j].x;
434        let yj = ring[j].y;
435
436        let intersect = ((yi > point.y) != (yj > point.y))
437            && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
438
439        if intersect {
440            inside = !inside;
441        }
442
443        j = i;
444    }
445
446    inside
447}
448
449/// Winding number algorithm for point-in-polygon test (alternative to ray casting)
450///
451/// More robust than ray casting for edge cases.
452#[allow(dead_code)]
453fn winding_number_test(point: &Coordinate, ring: &[Coordinate]) -> bool {
454    let mut winding_number = 0;
455    let n = ring.len();
456
457    // Guard against empty rings: `0..n - 1` would underflow `usize` when `n == 0`
458    // and panic under overflow-checked builds. An empty ring has winding 0.
459    if n == 0 {
460        return false;
461    }
462
463    for i in 0..n - 1 {
464        let p1 = &ring[i];
465        let p2 = &ring[i + 1];
466
467        if p1.y <= point.y {
468            if p2.y > point.y {
469                // Upward crossing
470                if is_left(p1, p2, point) > 0.0 {
471                    winding_number += 1;
472                }
473            }
474        } else if p2.y <= point.y {
475            // Downward crossing
476            if is_left(p1, p2, point) < 0.0 {
477                winding_number -= 1;
478            }
479        }
480    }
481
482    winding_number != 0
483}
484
485/// Tests if a point is to the left of a line
486fn is_left(p1: &Coordinate, p2: &Coordinate, point: &Coordinate) -> f64 {
487    (p2.x - p1.x) * (point.y - p1.y) - (point.x - p1.x) * (p2.y - p1.y)
488}
489
490/// Tests if two rings intersect
491fn rings_intersect(ring1: &[Coordinate], ring2: &[Coordinate]) -> bool {
492    for i in 0..ring1.len().saturating_sub(1) {
493        for j in 0..ring2.len().saturating_sub(1) {
494            if segments_intersect(&ring1[i], &ring1[i + 1], &ring2[j], &ring2[j + 1]) {
495                return true;
496            }
497        }
498    }
499    false
500}
501
502/// Tests if two line segments intersect
503fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
504    let d1 = direction(p3, p4, p1);
505    let d2 = direction(p3, p4, p2);
506    let d3 = direction(p1, p2, p3);
507    let d4 = direction(p1, p2, p4);
508
509    if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
510        && ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
511    {
512        return true;
513    }
514
515    // Check collinear cases
516    if d1.abs() < f64::EPSILON && on_segment(p3, p1, p4) {
517        return true;
518    }
519    if d2.abs() < f64::EPSILON && on_segment(p3, p2, p4) {
520        return true;
521    }
522    if d3.abs() < f64::EPSILON && on_segment(p1, p3, p2) {
523        return true;
524    }
525    if d4.abs() < f64::EPSILON && on_segment(p1, p4, p2) {
526        return true;
527    }
528
529    false
530}
531
532/// Computes the direction/orientation
533fn direction(a: &Coordinate, b: &Coordinate, p: &Coordinate) -> f64 {
534    (b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y)
535}
536
537/// Tests if point q lies on segment pr
538fn on_segment(p: &Coordinate, q: &Coordinate, r: &Coordinate) -> bool {
539    q.x <= p.x.max(r.x) && q.x >= p.x.min(r.x) && q.y <= p.y.max(r.y) && q.y >= p.y.min(r.y)
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545    use crate::error::AlgorithmError;
546    use oxigdal_core::vector::LineString;
547
548    fn create_square() -> Result<Polygon> {
549        let coords = vec![
550            Coordinate::new_2d(0.0, 0.0),
551            Coordinate::new_2d(4.0, 0.0),
552            Coordinate::new_2d(4.0, 4.0),
553            Coordinate::new_2d(0.0, 4.0),
554            Coordinate::new_2d(0.0, 0.0),
555        ];
556        let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
557        Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
558    }
559
560    #[test]
561    fn test_point_contains_point() {
562        let p1 = Point::new(1.0, 2.0);
563        let p2 = Point::new(1.0, 2.0);
564        let p3 = Point::new(3.0, 4.0);
565
566        let result1 = p1.contains(&p2);
567        assert!(result1.is_ok());
568        if let Ok(contains) = result1 {
569            assert!(contains);
570        }
571
572        let result2 = p1.contains(&p3);
573        assert!(result2.is_ok());
574        if let Ok(contains) = result2 {
575            assert!(!contains);
576        }
577    }
578
579    #[test]
580    fn test_polygon_contains_point() {
581        let poly = create_square();
582        assert!(poly.is_ok());
583
584        if let Ok(p) = poly {
585            // Point inside
586            let inside = Coordinate::new_2d(2.0, 2.0);
587            assert!(point_strictly_inside_polygon(&inside, &p));
588
589            // Point outside
590            let outside = Coordinate::new_2d(5.0, 5.0);
591            assert!(!point_in_polygon_or_boundary(&outside, &p));
592
593            // Point on boundary
594            let boundary = Coordinate::new_2d(0.0, 2.0);
595            assert!(point_on_polygon_boundary(&boundary, &p));
596        }
597    }
598
599    #[test]
600    fn test_point_in_polygon_boundary() {
601        let poly = create_square();
602        assert!(poly.is_ok());
603
604        if let Ok(p) = poly {
605            let inside = Coordinate::new_2d(2.0, 2.0);
606            assert!(point_in_polygon_boundary(&inside, &p));
607
608            let outside = Coordinate::new_2d(5.0, 5.0);
609            assert!(!point_in_polygon_boundary(&outside, &p));
610        }
611    }
612
613    #[test]
614    fn test_point_on_segment() {
615        let seg_start = Coordinate::new_2d(0.0, 0.0);
616        let seg_end = Coordinate::new_2d(4.0, 0.0);
617
618        // Point on segment
619        let on = Coordinate::new_2d(2.0, 0.0);
620        assert!(point_on_segment(&on, &seg_start, &seg_end));
621
622        // Point off segment
623        let off = Coordinate::new_2d(2.0, 1.0);
624        assert!(!point_on_segment(&off, &seg_start, &seg_end));
625    }
626
627    #[test]
628    fn test_polygon_intersects_polygon() {
629        let poly1 = create_square();
630        assert!(poly1.is_ok());
631
632        // Overlapping polygon
633        let coords2 = vec![
634            Coordinate::new_2d(2.0, 2.0),
635            Coordinate::new_2d(6.0, 2.0),
636            Coordinate::new_2d(6.0, 6.0),
637            Coordinate::new_2d(2.0, 6.0),
638            Coordinate::new_2d(2.0, 2.0),
639        ];
640        let exterior2 = LineString::new(coords2);
641        assert!(exterior2.is_ok());
642
643        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
644            let poly2 = Polygon::new(ext2, vec![]);
645            assert!(poly2.is_ok());
646
647            if let Ok(p2) = poly2 {
648                let result: crate::error::Result<bool> = intersects(&p1, &p2);
649                assert!(result.is_ok());
650
651                if let Ok(do_intersect) = result {
652                    assert!(do_intersect);
653                }
654            }
655        }
656    }
657
658    #[test]
659    fn test_disjoint_polygons() {
660        let poly1 = create_square();
661
662        // Disjoint polygon
663        let coords2 = vec![
664            Coordinate::new_2d(10.0, 10.0),
665            Coordinate::new_2d(14.0, 10.0),
666            Coordinate::new_2d(14.0, 14.0),
667            Coordinate::new_2d(10.0, 14.0),
668            Coordinate::new_2d(10.0, 10.0),
669        ];
670        let exterior2 = LineString::new(coords2);
671
672        assert!(poly1.is_ok() && exterior2.is_ok());
673
674        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
675            let poly2 = Polygon::new(ext2, vec![]);
676            assert!(poly2.is_ok());
677
678            if let Ok(p2) = poly2 {
679                let result: crate::error::Result<bool> = intersects(&p1, &p2);
680                assert!(result.is_ok());
681
682                if let Ok(do_intersect) = result {
683                    assert!(!do_intersect);
684                }
685            }
686        }
687    }
688
689    #[test]
690    fn test_segments_intersect() {
691        // Crossing segments
692        let p1 = Coordinate::new_2d(0.0, 0.0);
693        let p2 = Coordinate::new_2d(2.0, 2.0);
694        let p3 = Coordinate::new_2d(0.0, 2.0);
695        let p4 = Coordinate::new_2d(2.0, 0.0);
696
697        assert!(segments_intersect(&p1, &p2, &p3, &p4));
698    }
699
700    #[test]
701    fn test_segments_no_intersect() {
702        // Parallel segments
703        let p1 = Coordinate::new_2d(0.0, 0.0);
704        let p2 = Coordinate::new_2d(2.0, 0.0);
705        let p3 = Coordinate::new_2d(0.0, 1.0);
706        let p4 = Coordinate::new_2d(2.0, 1.0);
707
708        assert!(!segments_intersect(&p1, &p2, &p3, &p4));
709    }
710
711    #[test]
712    fn test_ray_casting() {
713        let ring = vec![
714            Coordinate::new_2d(0.0, 0.0),
715            Coordinate::new_2d(4.0, 0.0),
716            Coordinate::new_2d(4.0, 4.0),
717            Coordinate::new_2d(0.0, 4.0),
718            Coordinate::new_2d(0.0, 0.0),
719        ];
720
721        let inside = Coordinate::new_2d(2.0, 2.0);
722        assert!(ray_casting_test(&inside, &ring));
723
724        let outside = Coordinate::new_2d(5.0, 5.0);
725        assert!(!ray_casting_test(&outside, &ring));
726    }
727
728    #[test]
729    fn test_winding_number() {
730        let ring = vec![
731            Coordinate::new_2d(0.0, 0.0),
732            Coordinate::new_2d(4.0, 0.0),
733            Coordinate::new_2d(4.0, 4.0),
734            Coordinate::new_2d(0.0, 4.0),
735            Coordinate::new_2d(0.0, 0.0),
736        ];
737
738        let inside = Coordinate::new_2d(2.0, 2.0);
739        assert!(winding_number_test(&inside, &ring));
740
741        let outside = Coordinate::new_2d(5.0, 5.0);
742        assert!(!winding_number_test(&outside, &ring));
743    }
744
745    #[test]
746    fn test_overlaps_polygons_partial() {
747        // Two polygons that partially overlap
748        let poly1 = create_square();
749        assert!(poly1.is_ok());
750
751        let coords2 = vec![
752            Coordinate::new_2d(2.0, 2.0),
753            Coordinate::new_2d(6.0, 2.0),
754            Coordinate::new_2d(6.0, 6.0),
755            Coordinate::new_2d(2.0, 6.0),
756            Coordinate::new_2d(2.0, 2.0),
757        ];
758        let exterior2 = LineString::new(coords2);
759        assert!(exterior2.is_ok());
760
761        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
762            let poly2 = Polygon::new(ext2, vec![]);
763            assert!(poly2.is_ok());
764
765            if let Ok(p2) = poly2 {
766                let result = overlaps(&p1, &p2);
767                assert!(result.is_ok());
768                if let Ok(do_overlap) = result {
769                    assert!(do_overlap, "Partially overlapping polygons should overlap");
770                }
771            }
772        }
773    }
774
775    #[test]
776    fn test_overlaps_polygons_disjoint() {
777        // Two polygons that don't overlap (disjoint)
778        let poly1 = create_square();
779        assert!(poly1.is_ok());
780
781        let coords2 = vec![
782            Coordinate::new_2d(10.0, 10.0),
783            Coordinate::new_2d(14.0, 10.0),
784            Coordinate::new_2d(14.0, 14.0),
785            Coordinate::new_2d(10.0, 14.0),
786            Coordinate::new_2d(10.0, 10.0),
787        ];
788        let exterior2 = LineString::new(coords2);
789        assert!(exterior2.is_ok());
790
791        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
792            let poly2 = Polygon::new(ext2, vec![]);
793            assert!(poly2.is_ok());
794
795            if let Ok(p2) = poly2 {
796                let result = overlaps(&p1, &p2);
797                assert!(result.is_ok());
798                if let Ok(do_overlap) = result {
799                    assert!(!do_overlap, "Disjoint polygons should not overlap");
800                }
801            }
802        }
803    }
804
805    #[test]
806    fn test_overlaps_polygons_contained() {
807        // One polygon completely contains another
808        let poly1 = create_square();
809        assert!(poly1.is_ok());
810
811        let coords2 = vec![
812            Coordinate::new_2d(1.0, 1.0),
813            Coordinate::new_2d(3.0, 1.0),
814            Coordinate::new_2d(3.0, 3.0),
815            Coordinate::new_2d(1.0, 3.0),
816            Coordinate::new_2d(1.0, 1.0),
817        ];
818        let exterior2 = LineString::new(coords2);
819        assert!(exterior2.is_ok());
820
821        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
822            let poly2 = Polygon::new(ext2, vec![]);
823            assert!(poly2.is_ok());
824
825            if let Ok(p2) = poly2 {
826                let result = overlaps(&p1, &p2);
827                assert!(result.is_ok());
828                if let Ok(do_overlap) = result {
829                    assert!(!do_overlap, "Contained polygons should not overlap");
830                }
831            }
832        }
833    }
834
835    #[test]
836    fn test_overlaps_points() {
837        let p1 = Point::new(1.0, 2.0);
838        let p2 = Point::new(1.0, 2.0);
839
840        let result = overlaps(&p1, &p2);
841        assert!(result.is_ok());
842        if let Ok(do_overlap) = result {
843            assert!(!do_overlap, "Points should not overlap");
844        }
845    }
846
847    #[test]
848    fn test_crosses_polygons() {
849        // Per OGC DE-9IM, Polygon/Polygon crosses is undefined and must return false.
850        // Use overlaps() for partial polygon intersection instead.
851        let poly1 = create_square();
852        assert!(poly1.is_ok());
853
854        let coords2 = vec![
855            Coordinate::new_2d(-1.0, 2.0),
856            Coordinate::new_2d(5.0, 2.0),
857            Coordinate::new_2d(5.0, 3.0),
858            Coordinate::new_2d(-1.0, 3.0),
859            Coordinate::new_2d(-1.0, 2.0),
860        ];
861        let exterior2 = LineString::new(coords2);
862        assert!(exterior2.is_ok());
863
864        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
865            let poly2 = Polygon::new(ext2, vec![]);
866            assert!(poly2.is_ok());
867
868            if let Ok(p2) = poly2 {
869                let result = crosses(&p1, &p2);
870                assert!(result.is_ok());
871                if let Ok(do_cross) = result {
872                    assert!(
873                        !do_cross,
874                        "Polygon/Polygon crosses is undefined per OGC, must return false"
875                    );
876                }
877            }
878        }
879    }
880
881    #[test]
882    fn test_crosses_polygons_disjoint() {
883        // Two polygons that don't cross (disjoint)
884        let poly1 = create_square();
885        assert!(poly1.is_ok());
886
887        let coords2 = vec![
888            Coordinate::new_2d(10.0, 10.0),
889            Coordinate::new_2d(14.0, 10.0),
890            Coordinate::new_2d(14.0, 14.0),
891            Coordinate::new_2d(10.0, 14.0),
892            Coordinate::new_2d(10.0, 10.0),
893        ];
894        let exterior2 = LineString::new(coords2);
895        assert!(exterior2.is_ok());
896
897        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
898            let poly2 = Polygon::new(ext2, vec![]);
899            assert!(poly2.is_ok());
900
901            if let Ok(p2) = poly2 {
902                let result = crosses(&p1, &p2);
903                assert!(result.is_ok());
904                if let Ok(do_cross) = result {
905                    assert!(!do_cross, "Disjoint polygons should not cross");
906                }
907            }
908        }
909    }
910
911    #[test]
912    fn test_crosses_points() {
913        let p1 = Point::new(1.0, 2.0);
914        let p2 = Point::new(3.0, 4.0);
915
916        let result = crosses(&p1, &p2);
917        assert!(result.is_ok());
918        if let Ok(do_cross) = result {
919            assert!(!do_cross, "Points should not cross");
920        }
921    }
922
923    #[test]
924    fn test_touches_adjacent_polygons() {
925        // Two polygons that share a boundary
926        let poly1 = create_square();
927        assert!(poly1.is_ok());
928
929        let coords2 = vec![
930            Coordinate::new_2d(4.0, 0.0),
931            Coordinate::new_2d(8.0, 0.0),
932            Coordinate::new_2d(8.0, 4.0),
933            Coordinate::new_2d(4.0, 4.0),
934            Coordinate::new_2d(4.0, 0.0),
935        ];
936        let exterior2 = LineString::new(coords2);
937        assert!(exterior2.is_ok());
938
939        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
940            let poly2 = Polygon::new(ext2, vec![]);
941            assert!(poly2.is_ok());
942
943            if let Ok(p2) = poly2 {
944                let result = touches(&p1, &p2);
945                assert!(result.is_ok());
946                if let Ok(do_touch) = result {
947                    assert!(do_touch, "Adjacent polygons should touch");
948                }
949            }
950        }
951    }
952
953    #[test]
954    fn test_within_polygon() {
955        // Small polygon within larger polygon
956        let poly1 = create_square();
957        assert!(poly1.is_ok());
958
959        let coords2 = vec![
960            Coordinate::new_2d(1.0, 1.0),
961            Coordinate::new_2d(3.0, 1.0),
962            Coordinate::new_2d(3.0, 3.0),
963            Coordinate::new_2d(1.0, 3.0),
964            Coordinate::new_2d(1.0, 1.0),
965        ];
966        let exterior2 = LineString::new(coords2);
967        assert!(exterior2.is_ok());
968
969        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
970            let poly2 = Polygon::new(ext2, vec![]);
971            assert!(poly2.is_ok());
972
973            if let Ok(p2) = poly2 {
974                let result = within(&p2, &p1);
975                assert!(result.is_ok());
976                if let Ok(is_within) = result {
977                    assert!(is_within, "Small polygon should be within larger polygon");
978                }
979            }
980        }
981    }
982
983    #[test]
984    fn test_contains_polygon() {
985        // Large polygon contains smaller polygon
986        let poly1 = create_square();
987        assert!(poly1.is_ok());
988
989        let coords2 = vec![
990            Coordinate::new_2d(1.0, 1.0),
991            Coordinate::new_2d(3.0, 1.0),
992            Coordinate::new_2d(3.0, 3.0),
993            Coordinate::new_2d(1.0, 3.0),
994            Coordinate::new_2d(1.0, 1.0),
995        ];
996        let exterior2 = LineString::new(coords2);
997        assert!(exterior2.is_ok());
998
999        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
1000            let poly2 = Polygon::new(ext2, vec![]);
1001            assert!(poly2.is_ok());
1002
1003            if let Ok(p2) = poly2 {
1004                let result = contains(&p1, &p2);
1005                assert!(result.is_ok());
1006                if let Ok(does_contain) = result {
1007                    assert!(does_contain, "Large polygon should contain smaller polygon");
1008                }
1009            }
1010        }
1011    }
1012
1013    #[test]
1014    fn test_disjoint_polygons_separated() {
1015        let poly1 = create_square();
1016        assert!(poly1.is_ok());
1017
1018        let coords2 = vec![
1019            Coordinate::new_2d(10.0, 10.0),
1020            Coordinate::new_2d(14.0, 10.0),
1021            Coordinate::new_2d(14.0, 14.0),
1022            Coordinate::new_2d(10.0, 14.0),
1023            Coordinate::new_2d(10.0, 10.0),
1024        ];
1025        let exterior2 = LineString::new(coords2);
1026        assert!(exterior2.is_ok());
1027
1028        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
1029            let poly2 = Polygon::new(ext2, vec![]);
1030            assert!(poly2.is_ok());
1031
1032            if let Ok(p2) = poly2 {
1033                let result = disjoint(&p1, &p2);
1034                assert!(result.is_ok());
1035                if let Ok(are_disjoint) = result {
1036                    assert!(are_disjoint, "Separated polygons should be disjoint");
1037                }
1038            }
1039        }
1040    }
1041
1042    #[test]
1043    fn test_ray_casting_empty_hole_does_not_panic() {
1044        // `Polygon`'s public fields let a caller build a polygon with an empty
1045        // interior ring, bypassing `Polygon::new`'s length validation. The
1046        // point-in-polygon predicates must not underflow/panic on such input.
1047        let exterior = LineString::new(vec![
1048            Coordinate::new_2d(0.0, 0.0),
1049            Coordinate::new_2d(4.0, 0.0),
1050            Coordinate::new_2d(4.0, 4.0),
1051            Coordinate::new_2d(0.0, 4.0),
1052            Coordinate::new_2d(0.0, 0.0),
1053        ])
1054        .expect("valid exterior");
1055
1056        // Struct-literal construction with an empty interior ring (0 coords).
1057        let polygon = Polygon {
1058            exterior,
1059            interiors: vec![LineString::empty()],
1060        };
1061
1062        let inside = Coordinate::new_2d(2.0, 2.0);
1063        let outside = Coordinate::new_2d(10.0, 10.0);
1064
1065        // Must return sensible results (and, crucially, must NOT panic).
1066        assert!(point_in_polygon_or_boundary(&inside, &polygon));
1067        assert!(!point_in_polygon_or_boundary(&outside, &polygon));
1068        // Direct empty-ring guard behaviour.
1069        assert!(!ray_casting_test(&inside, &[]));
1070        assert!(!winding_number_test(&inside, &[]));
1071    }
1072}