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    let mut j = n - 1;
422    for i in 0..n {
423        let xi = ring[i].x;
424        let yi = ring[i].y;
425        let xj = ring[j].x;
426        let yj = ring[j].y;
427
428        let intersect = ((yi > point.y) != (yj > point.y))
429            && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
430
431        if intersect {
432            inside = !inside;
433        }
434
435        j = i;
436    }
437
438    inside
439}
440
441/// Winding number algorithm for point-in-polygon test (alternative to ray casting)
442///
443/// More robust than ray casting for edge cases.
444#[allow(dead_code)]
445fn winding_number_test(point: &Coordinate, ring: &[Coordinate]) -> bool {
446    let mut winding_number = 0;
447    let n = ring.len();
448
449    for i in 0..n - 1 {
450        let p1 = &ring[i];
451        let p2 = &ring[i + 1];
452
453        if p1.y <= point.y {
454            if p2.y > point.y {
455                // Upward crossing
456                if is_left(p1, p2, point) > 0.0 {
457                    winding_number += 1;
458                }
459            }
460        } else if p2.y <= point.y {
461            // Downward crossing
462            if is_left(p1, p2, point) < 0.0 {
463                winding_number -= 1;
464            }
465        }
466    }
467
468    winding_number != 0
469}
470
471/// Tests if a point is to the left of a line
472fn is_left(p1: &Coordinate, p2: &Coordinate, point: &Coordinate) -> f64 {
473    (p2.x - p1.x) * (point.y - p1.y) - (point.x - p1.x) * (p2.y - p1.y)
474}
475
476/// Tests if two rings intersect
477fn rings_intersect(ring1: &[Coordinate], ring2: &[Coordinate]) -> bool {
478    for i in 0..ring1.len().saturating_sub(1) {
479        for j in 0..ring2.len().saturating_sub(1) {
480            if segments_intersect(&ring1[i], &ring1[i + 1], &ring2[j], &ring2[j + 1]) {
481                return true;
482            }
483        }
484    }
485    false
486}
487
488/// Tests if two line segments intersect
489fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
490    let d1 = direction(p3, p4, p1);
491    let d2 = direction(p3, p4, p2);
492    let d3 = direction(p1, p2, p3);
493    let d4 = direction(p1, p2, p4);
494
495    if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
496        && ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
497    {
498        return true;
499    }
500
501    // Check collinear cases
502    if d1.abs() < f64::EPSILON && on_segment(p3, p1, p4) {
503        return true;
504    }
505    if d2.abs() < f64::EPSILON && on_segment(p3, p2, p4) {
506        return true;
507    }
508    if d3.abs() < f64::EPSILON && on_segment(p1, p3, p2) {
509        return true;
510    }
511    if d4.abs() < f64::EPSILON && on_segment(p1, p4, p2) {
512        return true;
513    }
514
515    false
516}
517
518/// Computes the direction/orientation
519fn direction(a: &Coordinate, b: &Coordinate, p: &Coordinate) -> f64 {
520    (b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y)
521}
522
523/// Tests if point q lies on segment pr
524fn on_segment(p: &Coordinate, q: &Coordinate, r: &Coordinate) -> bool {
525    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)
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531    use crate::error::AlgorithmError;
532    use oxigdal_core::vector::LineString;
533
534    fn create_square() -> Result<Polygon> {
535        let coords = vec![
536            Coordinate::new_2d(0.0, 0.0),
537            Coordinate::new_2d(4.0, 0.0),
538            Coordinate::new_2d(4.0, 4.0),
539            Coordinate::new_2d(0.0, 4.0),
540            Coordinate::new_2d(0.0, 0.0),
541        ];
542        let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
543        Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
544    }
545
546    #[test]
547    fn test_point_contains_point() {
548        let p1 = Point::new(1.0, 2.0);
549        let p2 = Point::new(1.0, 2.0);
550        let p3 = Point::new(3.0, 4.0);
551
552        let result1 = p1.contains(&p2);
553        assert!(result1.is_ok());
554        if let Ok(contains) = result1 {
555            assert!(contains);
556        }
557
558        let result2 = p1.contains(&p3);
559        assert!(result2.is_ok());
560        if let Ok(contains) = result2 {
561            assert!(!contains);
562        }
563    }
564
565    #[test]
566    fn test_polygon_contains_point() {
567        let poly = create_square();
568        assert!(poly.is_ok());
569
570        if let Ok(p) = poly {
571            // Point inside
572            let inside = Coordinate::new_2d(2.0, 2.0);
573            assert!(point_strictly_inside_polygon(&inside, &p));
574
575            // Point outside
576            let outside = Coordinate::new_2d(5.0, 5.0);
577            assert!(!point_in_polygon_or_boundary(&outside, &p));
578
579            // Point on boundary
580            let boundary = Coordinate::new_2d(0.0, 2.0);
581            assert!(point_on_polygon_boundary(&boundary, &p));
582        }
583    }
584
585    #[test]
586    fn test_point_in_polygon_boundary() {
587        let poly = create_square();
588        assert!(poly.is_ok());
589
590        if let Ok(p) = poly {
591            let inside = Coordinate::new_2d(2.0, 2.0);
592            assert!(point_in_polygon_boundary(&inside, &p));
593
594            let outside = Coordinate::new_2d(5.0, 5.0);
595            assert!(!point_in_polygon_boundary(&outside, &p));
596        }
597    }
598
599    #[test]
600    fn test_point_on_segment() {
601        let seg_start = Coordinate::new_2d(0.0, 0.0);
602        let seg_end = Coordinate::new_2d(4.0, 0.0);
603
604        // Point on segment
605        let on = Coordinate::new_2d(2.0, 0.0);
606        assert!(point_on_segment(&on, &seg_start, &seg_end));
607
608        // Point off segment
609        let off = Coordinate::new_2d(2.0, 1.0);
610        assert!(!point_on_segment(&off, &seg_start, &seg_end));
611    }
612
613    #[test]
614    fn test_polygon_intersects_polygon() {
615        let poly1 = create_square();
616        assert!(poly1.is_ok());
617
618        // Overlapping polygon
619        let coords2 = vec![
620            Coordinate::new_2d(2.0, 2.0),
621            Coordinate::new_2d(6.0, 2.0),
622            Coordinate::new_2d(6.0, 6.0),
623            Coordinate::new_2d(2.0, 6.0),
624            Coordinate::new_2d(2.0, 2.0),
625        ];
626        let exterior2 = LineString::new(coords2);
627        assert!(exterior2.is_ok());
628
629        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
630            let poly2 = Polygon::new(ext2, vec![]);
631            assert!(poly2.is_ok());
632
633            if let Ok(p2) = poly2 {
634                let result: crate::error::Result<bool> = intersects(&p1, &p2);
635                assert!(result.is_ok());
636
637                if let Ok(do_intersect) = result {
638                    assert!(do_intersect);
639                }
640            }
641        }
642    }
643
644    #[test]
645    fn test_disjoint_polygons() {
646        let poly1 = create_square();
647
648        // Disjoint polygon
649        let coords2 = vec![
650            Coordinate::new_2d(10.0, 10.0),
651            Coordinate::new_2d(14.0, 10.0),
652            Coordinate::new_2d(14.0, 14.0),
653            Coordinate::new_2d(10.0, 14.0),
654            Coordinate::new_2d(10.0, 10.0),
655        ];
656        let exterior2 = LineString::new(coords2);
657
658        assert!(poly1.is_ok() && exterior2.is_ok());
659
660        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
661            let poly2 = Polygon::new(ext2, vec![]);
662            assert!(poly2.is_ok());
663
664            if let Ok(p2) = poly2 {
665                let result: crate::error::Result<bool> = intersects(&p1, &p2);
666                assert!(result.is_ok());
667
668                if let Ok(do_intersect) = result {
669                    assert!(!do_intersect);
670                }
671            }
672        }
673    }
674
675    #[test]
676    fn test_segments_intersect() {
677        // Crossing segments
678        let p1 = Coordinate::new_2d(0.0, 0.0);
679        let p2 = Coordinate::new_2d(2.0, 2.0);
680        let p3 = Coordinate::new_2d(0.0, 2.0);
681        let p4 = Coordinate::new_2d(2.0, 0.0);
682
683        assert!(segments_intersect(&p1, &p2, &p3, &p4));
684    }
685
686    #[test]
687    fn test_segments_no_intersect() {
688        // Parallel segments
689        let p1 = Coordinate::new_2d(0.0, 0.0);
690        let p2 = Coordinate::new_2d(2.0, 0.0);
691        let p3 = Coordinate::new_2d(0.0, 1.0);
692        let p4 = Coordinate::new_2d(2.0, 1.0);
693
694        assert!(!segments_intersect(&p1, &p2, &p3, &p4));
695    }
696
697    #[test]
698    fn test_ray_casting() {
699        let ring = vec![
700            Coordinate::new_2d(0.0, 0.0),
701            Coordinate::new_2d(4.0, 0.0),
702            Coordinate::new_2d(4.0, 4.0),
703            Coordinate::new_2d(0.0, 4.0),
704            Coordinate::new_2d(0.0, 0.0),
705        ];
706
707        let inside = Coordinate::new_2d(2.0, 2.0);
708        assert!(ray_casting_test(&inside, &ring));
709
710        let outside = Coordinate::new_2d(5.0, 5.0);
711        assert!(!ray_casting_test(&outside, &ring));
712    }
713
714    #[test]
715    fn test_winding_number() {
716        let ring = vec![
717            Coordinate::new_2d(0.0, 0.0),
718            Coordinate::new_2d(4.0, 0.0),
719            Coordinate::new_2d(4.0, 4.0),
720            Coordinate::new_2d(0.0, 4.0),
721            Coordinate::new_2d(0.0, 0.0),
722        ];
723
724        let inside = Coordinate::new_2d(2.0, 2.0);
725        assert!(winding_number_test(&inside, &ring));
726
727        let outside = Coordinate::new_2d(5.0, 5.0);
728        assert!(!winding_number_test(&outside, &ring));
729    }
730
731    #[test]
732    fn test_overlaps_polygons_partial() {
733        // Two polygons that partially overlap
734        let poly1 = create_square();
735        assert!(poly1.is_ok());
736
737        let coords2 = vec![
738            Coordinate::new_2d(2.0, 2.0),
739            Coordinate::new_2d(6.0, 2.0),
740            Coordinate::new_2d(6.0, 6.0),
741            Coordinate::new_2d(2.0, 6.0),
742            Coordinate::new_2d(2.0, 2.0),
743        ];
744        let exterior2 = LineString::new(coords2);
745        assert!(exterior2.is_ok());
746
747        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
748            let poly2 = Polygon::new(ext2, vec![]);
749            assert!(poly2.is_ok());
750
751            if let Ok(p2) = poly2 {
752                let result = overlaps(&p1, &p2);
753                assert!(result.is_ok());
754                if let Ok(do_overlap) = result {
755                    assert!(do_overlap, "Partially overlapping polygons should overlap");
756                }
757            }
758        }
759    }
760
761    #[test]
762    fn test_overlaps_polygons_disjoint() {
763        // Two polygons that don't overlap (disjoint)
764        let poly1 = create_square();
765        assert!(poly1.is_ok());
766
767        let coords2 = vec![
768            Coordinate::new_2d(10.0, 10.0),
769            Coordinate::new_2d(14.0, 10.0),
770            Coordinate::new_2d(14.0, 14.0),
771            Coordinate::new_2d(10.0, 14.0),
772            Coordinate::new_2d(10.0, 10.0),
773        ];
774        let exterior2 = LineString::new(coords2);
775        assert!(exterior2.is_ok());
776
777        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
778            let poly2 = Polygon::new(ext2, vec![]);
779            assert!(poly2.is_ok());
780
781            if let Ok(p2) = poly2 {
782                let result = overlaps(&p1, &p2);
783                assert!(result.is_ok());
784                if let Ok(do_overlap) = result {
785                    assert!(!do_overlap, "Disjoint polygons should not overlap");
786                }
787            }
788        }
789    }
790
791    #[test]
792    fn test_overlaps_polygons_contained() {
793        // One polygon completely contains another
794        let poly1 = create_square();
795        assert!(poly1.is_ok());
796
797        let coords2 = vec![
798            Coordinate::new_2d(1.0, 1.0),
799            Coordinate::new_2d(3.0, 1.0),
800            Coordinate::new_2d(3.0, 3.0),
801            Coordinate::new_2d(1.0, 3.0),
802            Coordinate::new_2d(1.0, 1.0),
803        ];
804        let exterior2 = LineString::new(coords2);
805        assert!(exterior2.is_ok());
806
807        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
808            let poly2 = Polygon::new(ext2, vec![]);
809            assert!(poly2.is_ok());
810
811            if let Ok(p2) = poly2 {
812                let result = overlaps(&p1, &p2);
813                assert!(result.is_ok());
814                if let Ok(do_overlap) = result {
815                    assert!(!do_overlap, "Contained polygons should not overlap");
816                }
817            }
818        }
819    }
820
821    #[test]
822    fn test_overlaps_points() {
823        let p1 = Point::new(1.0, 2.0);
824        let p2 = Point::new(1.0, 2.0);
825
826        let result = overlaps(&p1, &p2);
827        assert!(result.is_ok());
828        if let Ok(do_overlap) = result {
829            assert!(!do_overlap, "Points should not overlap");
830        }
831    }
832
833    #[test]
834    fn test_crosses_polygons() {
835        // Per OGC DE-9IM, Polygon/Polygon crosses is undefined and must return false.
836        // Use overlaps() for partial polygon intersection instead.
837        let poly1 = create_square();
838        assert!(poly1.is_ok());
839
840        let coords2 = vec![
841            Coordinate::new_2d(-1.0, 2.0),
842            Coordinate::new_2d(5.0, 2.0),
843            Coordinate::new_2d(5.0, 3.0),
844            Coordinate::new_2d(-1.0, 3.0),
845            Coordinate::new_2d(-1.0, 2.0),
846        ];
847        let exterior2 = LineString::new(coords2);
848        assert!(exterior2.is_ok());
849
850        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
851            let poly2 = Polygon::new(ext2, vec![]);
852            assert!(poly2.is_ok());
853
854            if let Ok(p2) = poly2 {
855                let result = crosses(&p1, &p2);
856                assert!(result.is_ok());
857                if let Ok(do_cross) = result {
858                    assert!(
859                        !do_cross,
860                        "Polygon/Polygon crosses is undefined per OGC, must return false"
861                    );
862                }
863            }
864        }
865    }
866
867    #[test]
868    fn test_crosses_polygons_disjoint() {
869        // Two polygons that don't cross (disjoint)
870        let poly1 = create_square();
871        assert!(poly1.is_ok());
872
873        let coords2 = vec![
874            Coordinate::new_2d(10.0, 10.0),
875            Coordinate::new_2d(14.0, 10.0),
876            Coordinate::new_2d(14.0, 14.0),
877            Coordinate::new_2d(10.0, 14.0),
878            Coordinate::new_2d(10.0, 10.0),
879        ];
880        let exterior2 = LineString::new(coords2);
881        assert!(exterior2.is_ok());
882
883        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
884            let poly2 = Polygon::new(ext2, vec![]);
885            assert!(poly2.is_ok());
886
887            if let Ok(p2) = poly2 {
888                let result = crosses(&p1, &p2);
889                assert!(result.is_ok());
890                if let Ok(do_cross) = result {
891                    assert!(!do_cross, "Disjoint polygons should not cross");
892                }
893            }
894        }
895    }
896
897    #[test]
898    fn test_crosses_points() {
899        let p1 = Point::new(1.0, 2.0);
900        let p2 = Point::new(3.0, 4.0);
901
902        let result = crosses(&p1, &p2);
903        assert!(result.is_ok());
904        if let Ok(do_cross) = result {
905            assert!(!do_cross, "Points should not cross");
906        }
907    }
908
909    #[test]
910    fn test_touches_adjacent_polygons() {
911        // Two polygons that share a boundary
912        let poly1 = create_square();
913        assert!(poly1.is_ok());
914
915        let coords2 = vec![
916            Coordinate::new_2d(4.0, 0.0),
917            Coordinate::new_2d(8.0, 0.0),
918            Coordinate::new_2d(8.0, 4.0),
919            Coordinate::new_2d(4.0, 4.0),
920            Coordinate::new_2d(4.0, 0.0),
921        ];
922        let exterior2 = LineString::new(coords2);
923        assert!(exterior2.is_ok());
924
925        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
926            let poly2 = Polygon::new(ext2, vec![]);
927            assert!(poly2.is_ok());
928
929            if let Ok(p2) = poly2 {
930                let result = touches(&p1, &p2);
931                assert!(result.is_ok());
932                if let Ok(do_touch) = result {
933                    assert!(do_touch, "Adjacent polygons should touch");
934                }
935            }
936        }
937    }
938
939    #[test]
940    fn test_within_polygon() {
941        // Small polygon within larger polygon
942        let poly1 = create_square();
943        assert!(poly1.is_ok());
944
945        let coords2 = vec![
946            Coordinate::new_2d(1.0, 1.0),
947            Coordinate::new_2d(3.0, 1.0),
948            Coordinate::new_2d(3.0, 3.0),
949            Coordinate::new_2d(1.0, 3.0),
950            Coordinate::new_2d(1.0, 1.0),
951        ];
952        let exterior2 = LineString::new(coords2);
953        assert!(exterior2.is_ok());
954
955        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
956            let poly2 = Polygon::new(ext2, vec![]);
957            assert!(poly2.is_ok());
958
959            if let Ok(p2) = poly2 {
960                let result = within(&p2, &p1);
961                assert!(result.is_ok());
962                if let Ok(is_within) = result {
963                    assert!(is_within, "Small polygon should be within larger polygon");
964                }
965            }
966        }
967    }
968
969    #[test]
970    fn test_contains_polygon() {
971        // Large polygon contains smaller polygon
972        let poly1 = create_square();
973        assert!(poly1.is_ok());
974
975        let coords2 = vec![
976            Coordinate::new_2d(1.0, 1.0),
977            Coordinate::new_2d(3.0, 1.0),
978            Coordinate::new_2d(3.0, 3.0),
979            Coordinate::new_2d(1.0, 3.0),
980            Coordinate::new_2d(1.0, 1.0),
981        ];
982        let exterior2 = LineString::new(coords2);
983        assert!(exterior2.is_ok());
984
985        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
986            let poly2 = Polygon::new(ext2, vec![]);
987            assert!(poly2.is_ok());
988
989            if let Ok(p2) = poly2 {
990                let result = contains(&p1, &p2);
991                assert!(result.is_ok());
992                if let Ok(does_contain) = result {
993                    assert!(does_contain, "Large polygon should contain smaller polygon");
994                }
995            }
996        }
997    }
998
999    #[test]
1000    fn test_disjoint_polygons_separated() {
1001        let poly1 = create_square();
1002        assert!(poly1.is_ok());
1003
1004        let coords2 = vec![
1005            Coordinate::new_2d(10.0, 10.0),
1006            Coordinate::new_2d(14.0, 10.0),
1007            Coordinate::new_2d(14.0, 14.0),
1008            Coordinate::new_2d(10.0, 14.0),
1009            Coordinate::new_2d(10.0, 10.0),
1010        ];
1011        let exterior2 = LineString::new(coords2);
1012        assert!(exterior2.is_ok());
1013
1014        if let (Ok(p1), Ok(ext2)) = (poly1, exterior2) {
1015            let poly2 = Polygon::new(ext2, vec![]);
1016            assert!(poly2.is_ok());
1017
1018            if let Ok(p2) = poly2 {
1019                let result = disjoint(&p1, &p2);
1020                assert!(result.is_ok());
1021                if let Ok(are_disjoint) = result {
1022                    assert!(are_disjoint, "Separated polygons should be disjoint");
1023                }
1024            }
1025        }
1026    }
1027}