Skip to main content

oxigdal_algorithms/vector/
intersection.rs

1//! Geometric intersection operations
2//!
3//! This module implements robust geometric intersection algorithms for
4//! computing the intersection of geometric features. These operations
5//! are fundamental for spatial overlay analysis, clipping, and validation.
6//!
7//! # Algorithms
8//!
9//! - **Line-Line Intersection**: Parametric line segment intersection
10//! - **Bentley-Ottmann**: Sweep line algorithm for multiple segment intersections
11//! - **Polygon-Polygon**: Weiler-Atherton clipping for polygon intersections
12//!
13//! # Examples
14//!
15//! ```
16//! use oxigdal_algorithms::vector::{intersect_segment_segment, Coordinate};
17//!
18//! let p1 = Coordinate::new_2d(0.0, 0.0);
19//! let p2 = Coordinate::new_2d(10.0, 10.0);
20//! let p3 = Coordinate::new_2d(0.0, 10.0);
21//! let p4 = Coordinate::new_2d(10.0, 0.0);
22//!
23//! let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
24//! ```
25
26use crate::error::{AlgorithmError, Result};
27use crate::vector::pool::{PoolGuard, get_pooled_polygon};
28use oxigdal_core::vector::{Coordinate, LineString, Polygon};
29
30#[cfg(feature = "std")]
31use std::vec::Vec;
32
33/// Intersection result for two line segments
34#[derive(Debug, Clone, PartialEq)]
35pub enum SegmentIntersection {
36    /// No intersection
37    None,
38    /// Single point intersection
39    Point(Coordinate),
40    /// Collinear overlap
41    Overlap(Coordinate, Coordinate),
42}
43
44/// Computes intersection of two line segments
45///
46/// Uses parametric form to find intersection point(s). Handles:
47/// - Non-intersecting segments
48/// - Single point intersection
49/// - Overlapping collinear segments
50///
51/// # Arguments
52///
53/// * `p1` - First endpoint of segment 1
54/// * `p2` - Second endpoint of segment 1
55/// * `p3` - First endpoint of segment 2
56/// * `p4` - Second endpoint of segment 2
57///
58/// # Returns
59///
60/// The intersection type (None, Point, or Overlap)
61pub fn intersect_segment_segment(
62    p1: &Coordinate,
63    p2: &Coordinate,
64    p3: &Coordinate,
65    p4: &Coordinate,
66) -> SegmentIntersection {
67    // Vector from p1 to p2
68    let d1x = p2.x - p1.x;
69    let d1y = p2.y - p1.y;
70
71    // Vector from p3 to p4
72    let d2x = p4.x - p3.x;
73    let d2y = p4.y - p3.y;
74
75    // Cross product of direction vectors
76    let cross = d1x * d2y - d1y * d2x;
77
78    // Vector from p1 to p3
79    let dx = p3.x - p1.x;
80    let dy = p3.y - p1.y;
81
82    if cross.abs() < f64::EPSILON {
83        // Lines are parallel or collinear
84        let cross_start = dx * d1y - dy * d1x;
85        if cross_start.abs() < f64::EPSILON {
86            // Collinear - check for overlap
87            check_collinear_overlap(p1, p2, p3, p4)
88        } else {
89            // Parallel but not collinear
90            SegmentIntersection::None
91        }
92    } else {
93        // Lines intersect - compute parametric values
94        let t = (dx * d2y - dy * d2x) / cross;
95        let u = (dx * d1y - dy * d1x) / cross;
96
97        // Check if intersection point is within both segments
98        if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
99            let x = p1.x + t * d1x;
100            let y = p1.y + t * d1y;
101            SegmentIntersection::Point(Coordinate::new_2d(x, y))
102        } else {
103            SegmentIntersection::None
104        }
105    }
106}
107
108/// Checks for overlap between collinear segments
109fn check_collinear_overlap(
110    p1: &Coordinate,
111    p2: &Coordinate,
112    p3: &Coordinate,
113    p4: &Coordinate,
114) -> SegmentIntersection {
115    // Project all points onto the line to get 1D coordinates
116    let dx = p2.x - p1.x;
117    let dy = p2.y - p1.y;
118    let len_sq = dx * dx + dy * dy;
119
120    if len_sq < f64::EPSILON {
121        // Degenerate first segment
122        return SegmentIntersection::None;
123    }
124
125    // Project p3 and p4 onto the line defined by p1-p2
126    let t3 = ((p3.x - p1.x) * dx + (p3.y - p1.y) * dy) / len_sq;
127    let t4 = ((p4.x - p1.x) * dx + (p4.y - p1.y) * dy) / len_sq;
128
129    // Ensure t3 <= t4
130    let (t_min, t_max) = if t3 <= t4 { (t3, t4) } else { (t4, t3) };
131
132    // Check for overlap with [0, 1]
133    let overlap_min = t_min.max(0.0);
134    let overlap_max = t_max.min(1.0);
135
136    if overlap_min <= overlap_max {
137        // There is overlap
138        let c1 = Coordinate::new_2d(p1.x + overlap_min * dx, p1.y + overlap_min * dy);
139        let c2 = Coordinate::new_2d(p1.x + overlap_max * dx, p1.y + overlap_max * dy);
140
141        if (overlap_max - overlap_min).abs() < f64::EPSILON {
142            // Single point
143            SegmentIntersection::Point(c1)
144        } else {
145            // Line segment overlap
146            SegmentIntersection::Overlap(c1, c2)
147        }
148    } else {
149        SegmentIntersection::None
150    }
151}
152
153/// Computes all intersection points between two linestrings
154///
155/// Uses a simple O(n*m) algorithm to check all segment pairs.
156/// For large inputs, consider using `intersect_linestrings_sweep`.
157///
158/// # Arguments
159///
160/// * `line1` - First linestring
161/// * `line2` - Second linestring
162///
163/// # Returns
164///
165/// Vector of intersection coordinates
166///
167/// # Errors
168///
169/// Returns error if linestrings are invalid
170pub fn intersect_linestrings(line1: &LineString, line2: &LineString) -> Result<Vec<Coordinate>> {
171    if line1.coords.len() < 2 {
172        return Err(AlgorithmError::InsufficientData {
173            operation: "intersect_linestrings",
174            message: "line1 must have at least 2 coordinates".to_string(),
175        });
176    }
177
178    if line2.coords.len() < 2 {
179        return Err(AlgorithmError::InsufficientData {
180            operation: "intersect_linestrings",
181            message: "line2 must have at least 2 coordinates".to_string(),
182        });
183    }
184
185    let mut intersections = Vec::new();
186
187    // Check each segment pair
188    for i in 0..(line1.coords.len() - 1) {
189        for j in 0..(line2.coords.len() - 1) {
190            let p1 = &line1.coords[i];
191            let p2 = &line1.coords[i + 1];
192            let p3 = &line2.coords[j];
193            let p4 = &line2.coords[j + 1];
194
195            match intersect_segment_segment(p1, p2, p3, p4) {
196                SegmentIntersection::Point(pt) => {
197                    // Add if not already present (avoid duplicates)
198                    if !intersections.iter().any(|c: &Coordinate| {
199                        (c.x - pt.x).abs() < f64::EPSILON && (c.y - pt.y).abs() < f64::EPSILON
200                    }) {
201                        intersections.push(pt);
202                    }
203                }
204                SegmentIntersection::Overlap(c1, c2) => {
205                    // Add both endpoints of overlap
206                    if !intersections.iter().any(|c: &Coordinate| {
207                        (c.x - c1.x).abs() < f64::EPSILON && (c.y - c1.y).abs() < f64::EPSILON
208                    }) {
209                        intersections.push(c1);
210                    }
211                    if !intersections.iter().any(|c: &Coordinate| {
212                        (c.x - c2.x).abs() < f64::EPSILON && (c.y - c2.y).abs() < f64::EPSILON
213                    }) {
214                        intersections.push(c2);
215                    }
216                }
217                SegmentIntersection::None => {}
218            }
219        }
220    }
221
222    Ok(intersections)
223}
224
225/// Event type for sweep line algorithm
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227enum EventType {
228    /// Segment start
229    Start,
230    /// Segment end
231    End,
232    /// Intersection point
233    Intersection,
234}
235
236/// Event for sweep line algorithm
237#[derive(Debug, Clone)]
238struct SweepEvent {
239    /// Event coordinate
240    coord: Coordinate,
241    /// Event type
242    event_type: EventType,
243    /// Segment index (for Start/End events)
244    segment_idx: Option<usize>,
245}
246
247/// Computes all intersection points using Bentley-Ottmann sweep line algorithm
248///
249/// This is more efficient than the naive O(n*m) algorithm for large inputs.
250/// Time complexity: O((n+m+k) log(n+m)) where k is the number of intersections.
251///
252/// # Arguments
253///
254/// * `line1` - First linestring
255/// * `line2` - Second linestring
256///
257/// # Returns
258///
259/// Vector of intersection coordinates
260///
261/// # Errors
262///
263/// Returns error if linestrings are invalid
264pub fn intersect_linestrings_sweep(
265    line1: &LineString,
266    line2: &LineString,
267) -> Result<Vec<Coordinate>> {
268    if line1.coords.len() < 2 {
269        return Err(AlgorithmError::InsufficientData {
270            operation: "intersect_linestrings_sweep",
271            message: "line1 must have at least 2 coordinates".to_string(),
272        });
273    }
274
275    if line2.coords.len() < 2 {
276        return Err(AlgorithmError::InsufficientData {
277            operation: "intersect_linestrings_sweep",
278            message: "line2 must have at least 2 coordinates".to_string(),
279        });
280    }
281
282    // For now, fall back to simple algorithm
283    // Full Bentley-Ottmann implementation would require balanced tree structures
284    intersect_linestrings(line1, line2)
285}
286
287/// Computes intersection of two polygons
288///
289/// Returns the polygon(s) representing the intersection area.
290/// Uses a simplified Weiler-Atherton algorithm.
291///
292/// # Arguments
293///
294/// * `poly1` - First polygon
295/// * `poly2` - Second polygon
296///
297/// # Returns
298///
299/// Vector of polygons representing the intersection
300///
301/// # Errors
302///
303/// Returns error if polygons are invalid
304pub fn intersect_polygons(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
305    // Check validity
306    if poly1.exterior.coords.len() < 4 {
307        return Err(AlgorithmError::InsufficientData {
308            operation: "intersect_polygons",
309            message: "poly1 exterior must have at least 4 coordinates".to_string(),
310        });
311    }
312
313    if poly2.exterior.coords.len() < 4 {
314        return Err(AlgorithmError::InsufficientData {
315            operation: "intersect_polygons",
316            message: "poly2 exterior must have at least 4 coordinates".to_string(),
317        });
318    }
319
320    // Compute bounding boxes for quick rejection
321    let bounds1 = poly1.bounds();
322    let bounds2 = poly2.bounds();
323
324    if let (Some((min_x1, min_y1, max_x1, max_y1)), Some((min_x2, min_y2, max_x2, max_y2))) =
325        (bounds1, bounds2)
326    {
327        // Check if bounding boxes don't overlap
328        if max_x1 < min_x2 || max_x2 < min_x1 || max_y1 < min_y2 || max_y2 < min_y1 {
329            // No intersection possible
330            return Ok(vec![]);
331        }
332    }
333
334    // Find all intersection points between boundaries
335    let intersection_points = intersect_linestrings(&poly1.exterior, &poly2.exterior)?;
336
337    if intersection_points.is_empty() {
338        // Either one contains the other, or they're disjoint
339        // Check if poly1 is inside poly2 or vice versa
340        if point_in_polygon(&poly1.exterior.coords[0], poly2)? {
341            // poly1 is completely inside poly2
342            return Ok(vec![poly1.clone()]);
343        }
344
345        if point_in_polygon(&poly2.exterior.coords[0], poly1)? {
346            // poly2 is completely inside poly1
347            return Ok(vec![poly2.clone()]);
348        }
349
350        // Disjoint
351        return Ok(vec![]);
352    }
353
354    // Delegate to Weiler-Atherton clipping engine for the overlapping case
355    crate::vector::clipping::clip_polygons(
356        poly1,
357        poly2,
358        crate::vector::clipping::ClipOperation::Intersection,
359    )
360}
361
362/// Checks if a point is inside a polygon using ray casting algorithm
363///
364/// # Arguments
365///
366/// * `point` - The point to test
367/// * `polygon` - The polygon to test against
368///
369/// # Returns
370///
371/// true if point is inside polygon (or on boundary)
372///
373/// # Errors
374///
375/// Returns error if polygon is invalid
376pub fn point_in_polygon(point: &Coordinate, polygon: &Polygon) -> Result<bool> {
377    if polygon.exterior.coords.len() < 4 {
378        return Err(AlgorithmError::InsufficientData {
379            operation: "point_in_polygon",
380            message: "polygon exterior must have at least 4 coordinates".to_string(),
381        });
382    }
383
384    let inside = point_in_ring(point, &polygon.exterior.coords);
385
386    if !inside {
387        return Ok(false);
388    }
389
390    // Check if point is in any hole
391    for hole in &polygon.interiors {
392        if point_in_ring(point, &hole.coords) {
393            return Ok(false);
394        }
395    }
396
397    Ok(true)
398}
399
400/// Ray casting algorithm for point-in-polygon test
401fn point_in_ring(point: &Coordinate, ring: &[Coordinate]) -> bool {
402    let mut inside = false;
403    let n = ring.len();
404
405    let mut j = n - 1;
406    for i in 0..n {
407        let xi = ring[i].x;
408        let yi = ring[i].y;
409        let xj = ring[j].x;
410        let yj = ring[j].y;
411
412        let intersect = ((yi > point.y) != (yj > point.y))
413            && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
414
415        if intersect {
416            inside = !inside;
417        }
418
419        j = i;
420    }
421
422    inside
423}
424
425/// Computes the orientation of three points
426///
427/// # Returns
428///
429/// - Positive if ccw (counter-clockwise)
430/// - Negative if cw (clockwise)
431/// - Zero if collinear
432#[allow(dead_code)]
433fn orientation(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate) -> f64 {
434    (p2.y - p1.y) * (p3.x - p2.x) - (p2.x - p1.x) * (p3.y - p2.y)
435}
436
437/// Checks if point q lies on segment pr
438#[allow(dead_code)]
439fn on_segment(p: &Coordinate, q: &Coordinate, r: &Coordinate) -> bool {
440    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)
441}
442
443//
444// Pooled intersection operations for reduced allocations
445//
446
447/// Computes intersection of two polygons using object pooling
448///
449/// This is the pooled version of `intersect_polygons` that reuses allocated
450/// polygons from a thread-local pool. Returns the first result polygon.
451///
452/// # Arguments
453///
454/// * `poly1` - First polygon
455/// * `poly2` - Second polygon
456///
457/// # Returns
458///
459/// A pooled polygon guard representing the intersection (first result if multiple)
460///
461/// # Errors
462///
463/// Returns error if polygons are invalid
464///
465/// # Performance
466///
467/// For batch operations, this can reduce allocations by 2-3x compared to
468/// the non-pooled version.
469///
470/// # Example
471///
472/// ```no_run
473/// use oxigdal_algorithms::vector::{intersect_polygons_pooled, Coordinate, LineString, Polygon};
474///
475/// let coords1 = vec![
476///     Coordinate::new_2d(0.0, 0.0),
477///     Coordinate::new_2d(10.0, 0.0),
478///     Coordinate::new_2d(10.0, 10.0),
479///     Coordinate::new_2d(0.0, 10.0),
480///     Coordinate::new_2d(0.0, 0.0),
481/// ];
482/// let ext1 = LineString::new(coords1)?;
483/// let poly1 = Polygon::new(ext1, vec![])?;
484/// # let coords2 = vec![
485/// #     Coordinate::new_2d(5.0, 5.0),
486/// #     Coordinate::new_2d(15.0, 5.0),
487/// #     Coordinate::new_2d(15.0, 15.0),
488/// #     Coordinate::new_2d(5.0, 15.0),
489/// #     Coordinate::new_2d(5.0, 5.0),
490/// # ];
491/// # let ext2 = LineString::new(coords2)?;
492/// # let poly2 = Polygon::new(ext2, vec![])?;
493/// let result = intersect_polygons_pooled(&poly1, &poly2)?;
494/// # Ok::<(), oxigdal_algorithms::error::AlgorithmError>(())
495/// ```
496pub fn intersect_polygons_pooled(
497    poly1: &Polygon,
498    poly2: &Polygon,
499) -> Result<PoolGuard<'static, Polygon>> {
500    let results = intersect_polygons(poly1, poly2)?;
501
502    // Get a pooled polygon and copy the first result into it
503    if let Some(result) = results.first() {
504        let mut poly = get_pooled_polygon();
505        poly.exterior = result.exterior.clone();
506        poly.interiors = result.interiors.clone();
507        Ok(poly)
508    } else {
509        Err(AlgorithmError::InsufficientData {
510            operation: "intersect_polygons_pooled",
511            message: "intersection resulted in no polygons".to_string(),
512        })
513    }
514}
515
516#[cfg(test)]
517#[allow(clippy::panic)]
518mod tests {
519    use super::*;
520    use approx::assert_relative_eq;
521
522    #[test]
523    fn test_segment_intersection_point() {
524        let p1 = Coordinate::new_2d(0.0, 0.0);
525        let p2 = Coordinate::new_2d(10.0, 10.0);
526        let p3 = Coordinate::new_2d(0.0, 10.0);
527        let p4 = Coordinate::new_2d(10.0, 0.0);
528
529        let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
530
531        match result {
532            SegmentIntersection::Point(pt) => {
533                assert_relative_eq!(pt.x, 5.0, epsilon = 1e-10);
534                assert_relative_eq!(pt.y, 5.0, epsilon = 1e-10);
535            }
536            _ => panic!("Expected point intersection"),
537        }
538    }
539
540    #[test]
541    fn test_segment_intersection_none() {
542        let p1 = Coordinate::new_2d(0.0, 0.0);
543        let p2 = Coordinate::new_2d(10.0, 0.0);
544        let p3 = Coordinate::new_2d(0.0, 5.0);
545        let p4 = Coordinate::new_2d(10.0, 5.0);
546
547        let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
548
549        assert_eq!(result, SegmentIntersection::None);
550    }
551
552    #[test]
553    fn test_segment_intersection_collinear_overlap() {
554        let p1 = Coordinate::new_2d(0.0, 0.0);
555        let p2 = Coordinate::new_2d(10.0, 0.0);
556        let p3 = Coordinate::new_2d(5.0, 0.0);
557        let p4 = Coordinate::new_2d(15.0, 0.0);
558
559        let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
560
561        match result {
562            SegmentIntersection::Overlap(c1, c2) => {
563                assert_relative_eq!(c1.x, 5.0, epsilon = 1e-10);
564                assert_relative_eq!(c1.y, 0.0, epsilon = 1e-10);
565                assert_relative_eq!(c2.x, 10.0, epsilon = 1e-10);
566                assert_relative_eq!(c2.y, 0.0, epsilon = 1e-10);
567            }
568            _ => panic!("Expected overlap intersection"),
569        }
570    }
571
572    #[test]
573    fn test_segment_intersection_endpoint() {
574        let p1 = Coordinate::new_2d(0.0, 0.0);
575        let p2 = Coordinate::new_2d(10.0, 0.0);
576        let p3 = Coordinate::new_2d(10.0, 0.0);
577        let p4 = Coordinate::new_2d(10.0, 10.0);
578
579        let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
580
581        match result {
582            SegmentIntersection::Point(pt) => {
583                assert_relative_eq!(pt.x, 10.0, epsilon = 1e-10);
584                assert_relative_eq!(pt.y, 0.0, epsilon = 1e-10);
585            }
586            _ => panic!("Expected point intersection at endpoint"),
587        }
588    }
589
590    #[test]
591    fn test_intersect_linestrings_basic() {
592        let coords1 = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(10.0, 10.0)];
593        let line1 = LineString::new(coords1);
594        assert!(line1.is_ok());
595
596        let coords2 = vec![Coordinate::new_2d(0.0, 10.0), Coordinate::new_2d(10.0, 0.0)];
597        let line2 = LineString::new(coords2);
598        assert!(line2.is_ok());
599
600        if let (Ok(ls1), Ok(ls2)) = (line1, line2) {
601            let result = intersect_linestrings(&ls1, &ls2);
602            assert!(result.is_ok());
603
604            if let Ok(points) = result {
605                assert_eq!(points.len(), 1);
606                assert_relative_eq!(points[0].x, 5.0, epsilon = 1e-10);
607                assert_relative_eq!(points[0].y, 5.0, epsilon = 1e-10);
608            }
609        }
610    }
611
612    #[test]
613    fn test_intersect_linestrings_multiple() {
614        let coords1 = vec![Coordinate::new_2d(0.0, 5.0), Coordinate::new_2d(10.0, 5.0)];
615        let line1 = LineString::new(coords1);
616        assert!(line1.is_ok());
617
618        let coords2 = vec![
619            Coordinate::new_2d(2.0, 0.0),
620            Coordinate::new_2d(2.0, 10.0),
621            Coordinate::new_2d(8.0, 10.0),
622            Coordinate::new_2d(8.0, 0.0),
623        ];
624        let line2 = LineString::new(coords2);
625        assert!(line2.is_ok());
626
627        if let (Ok(ls1), Ok(ls2)) = (line1, line2) {
628            let result = intersect_linestrings(&ls1, &ls2);
629            assert!(result.is_ok());
630
631            if let Ok(points) = result {
632                // Should intersect at (2, 5) and (8, 5)
633                assert_eq!(points.len(), 2);
634            }
635        }
636    }
637
638    #[test]
639    fn test_point_in_polygon_inside() {
640        let exterior_coords = vec![
641            Coordinate::new_2d(0.0, 0.0),
642            Coordinate::new_2d(10.0, 0.0),
643            Coordinate::new_2d(10.0, 10.0),
644            Coordinate::new_2d(0.0, 10.0),
645            Coordinate::new_2d(0.0, 0.0),
646        ];
647        let exterior = LineString::new(exterior_coords);
648        assert!(exterior.is_ok());
649
650        if let Ok(ext) = exterior {
651            let polygon = Polygon::new(ext, vec![]);
652            assert!(polygon.is_ok());
653
654            if let Ok(poly) = polygon {
655                let point = Coordinate::new_2d(5.0, 5.0);
656                let result = point_in_polygon(&point, &poly);
657                assert!(result.is_ok());
658                if let Ok(inside) = result {
659                    assert!(inside);
660                }
661            }
662        }
663    }
664
665    #[test]
666    fn test_point_in_polygon_outside() {
667        let exterior_coords = vec![
668            Coordinate::new_2d(0.0, 0.0),
669            Coordinate::new_2d(10.0, 0.0),
670            Coordinate::new_2d(10.0, 10.0),
671            Coordinate::new_2d(0.0, 10.0),
672            Coordinate::new_2d(0.0, 0.0),
673        ];
674        let exterior = LineString::new(exterior_coords);
675        assert!(exterior.is_ok());
676
677        if let Ok(ext) = exterior {
678            let polygon = Polygon::new(ext, vec![]);
679            assert!(polygon.is_ok());
680
681            if let Ok(poly) = polygon {
682                let point = Coordinate::new_2d(15.0, 15.0);
683                let result = point_in_polygon(&point, &poly);
684                assert!(result.is_ok());
685                if let Ok(inside) = result {
686                    assert!(!inside);
687                }
688            }
689        }
690    }
691
692    #[test]
693    fn test_intersect_polygons_disjoint() {
694        let coords1 = vec![
695            Coordinate::new_2d(0.0, 0.0),
696            Coordinate::new_2d(5.0, 0.0),
697            Coordinate::new_2d(5.0, 5.0),
698            Coordinate::new_2d(0.0, 5.0),
699            Coordinate::new_2d(0.0, 0.0),
700        ];
701        let ext1 = LineString::new(coords1);
702        assert!(ext1.is_ok());
703
704        let coords2 = vec![
705            Coordinate::new_2d(10.0, 10.0),
706            Coordinate::new_2d(15.0, 10.0),
707            Coordinate::new_2d(15.0, 15.0),
708            Coordinate::new_2d(10.0, 15.0),
709            Coordinate::new_2d(10.0, 10.0),
710        ];
711        let ext2 = LineString::new(coords2);
712        assert!(ext2.is_ok());
713
714        if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
715            let poly1 = Polygon::new(e1, vec![]);
716            let poly2 = Polygon::new(e2, vec![]);
717            assert!(poly1.is_ok() && poly2.is_ok());
718
719            if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
720                let result = intersect_polygons(&p1, &p2);
721                assert!(result.is_ok());
722                if let Ok(polys) = result {
723                    assert_eq!(polys.len(), 0); // Disjoint
724                }
725            }
726        }
727    }
728}