use crate::error::{AlgorithmError, Result};
use crate::vector::pool::{PoolGuard, get_pooled_polygon};
use oxigdal_core::vector::{Coordinate, LineString, Polygon};
#[cfg(feature = "std")]
use std::vec::Vec;
#[derive(Debug, Clone, PartialEq)]
pub enum SegmentIntersection {
None,
Point(Coordinate),
Overlap(Coordinate, Coordinate),
}
pub fn intersect_segment_segment(
p1: &Coordinate,
p2: &Coordinate,
p3: &Coordinate,
p4: &Coordinate,
) -> SegmentIntersection {
let d1x = p2.x - p1.x;
let d1y = p2.y - p1.y;
let d2x = p4.x - p3.x;
let d2y = p4.y - p3.y;
let cross = d1x * d2y - d1y * d2x;
let dx = p3.x - p1.x;
let dy = p3.y - p1.y;
if cross.abs() < f64::EPSILON {
let cross_start = dx * d1y - dy * d1x;
if cross_start.abs() < f64::EPSILON {
check_collinear_overlap(p1, p2, p3, p4)
} else {
SegmentIntersection::None
}
} else {
let t = (dx * d2y - dy * d2x) / cross;
let u = (dx * d1y - dy * d1x) / cross;
if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
let x = p1.x + t * d1x;
let y = p1.y + t * d1y;
SegmentIntersection::Point(Coordinate::new_2d(x, y))
} else {
SegmentIntersection::None
}
}
}
fn check_collinear_overlap(
p1: &Coordinate,
p2: &Coordinate,
p3: &Coordinate,
p4: &Coordinate,
) -> SegmentIntersection {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
let len_sq = dx * dx + dy * dy;
if len_sq < f64::EPSILON {
return SegmentIntersection::None;
}
let t3 = ((p3.x - p1.x) * dx + (p3.y - p1.y) * dy) / len_sq;
let t4 = ((p4.x - p1.x) * dx + (p4.y - p1.y) * dy) / len_sq;
let (t_min, t_max) = if t3 <= t4 { (t3, t4) } else { (t4, t3) };
let overlap_min = t_min.max(0.0);
let overlap_max = t_max.min(1.0);
if overlap_min <= overlap_max {
let c1 = Coordinate::new_2d(p1.x + overlap_min * dx, p1.y + overlap_min * dy);
let c2 = Coordinate::new_2d(p1.x + overlap_max * dx, p1.y + overlap_max * dy);
if (overlap_max - overlap_min).abs() < f64::EPSILON {
SegmentIntersection::Point(c1)
} else {
SegmentIntersection::Overlap(c1, c2)
}
} else {
SegmentIntersection::None
}
}
pub fn intersect_linestrings(line1: &LineString, line2: &LineString) -> Result<Vec<Coordinate>> {
if line1.coords.len() < 2 {
return Err(AlgorithmError::InsufficientData {
operation: "intersect_linestrings",
message: "line1 must have at least 2 coordinates".to_string(),
});
}
if line2.coords.len() < 2 {
return Err(AlgorithmError::InsufficientData {
operation: "intersect_linestrings",
message: "line2 must have at least 2 coordinates".to_string(),
});
}
let mut intersections = Vec::new();
for i in 0..(line1.coords.len() - 1) {
for j in 0..(line2.coords.len() - 1) {
let p1 = &line1.coords[i];
let p2 = &line1.coords[i + 1];
let p3 = &line2.coords[j];
let p4 = &line2.coords[j + 1];
match intersect_segment_segment(p1, p2, p3, p4) {
SegmentIntersection::Point(pt) => {
if !intersections.iter().any(|c: &Coordinate| {
(c.x - pt.x).abs() < f64::EPSILON && (c.y - pt.y).abs() < f64::EPSILON
}) {
intersections.push(pt);
}
}
SegmentIntersection::Overlap(c1, c2) => {
if !intersections.iter().any(|c: &Coordinate| {
(c.x - c1.x).abs() < f64::EPSILON && (c.y - c1.y).abs() < f64::EPSILON
}) {
intersections.push(c1);
}
if !intersections.iter().any(|c: &Coordinate| {
(c.x - c2.x).abs() < f64::EPSILON && (c.y - c2.y).abs() < f64::EPSILON
}) {
intersections.push(c2);
}
}
SegmentIntersection::None => {}
}
}
}
Ok(intersections)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EventType {
Start,
End,
Intersection,
}
#[derive(Debug, Clone)]
struct SweepEvent {
coord: Coordinate,
event_type: EventType,
segment_idx: Option<usize>,
}
pub fn intersect_linestrings_sweep(
line1: &LineString,
line2: &LineString,
) -> Result<Vec<Coordinate>> {
if line1.coords.len() < 2 {
return Err(AlgorithmError::InsufficientData {
operation: "intersect_linestrings_sweep",
message: "line1 must have at least 2 coordinates".to_string(),
});
}
if line2.coords.len() < 2 {
return Err(AlgorithmError::InsufficientData {
operation: "intersect_linestrings_sweep",
message: "line2 must have at least 2 coordinates".to_string(),
});
}
intersect_linestrings(line1, line2)
}
pub fn intersect_polygons(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
if poly1.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "intersect_polygons",
message: "poly1 exterior must have at least 4 coordinates".to_string(),
});
}
if poly2.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "intersect_polygons",
message: "poly2 exterior must have at least 4 coordinates".to_string(),
});
}
let bounds1 = poly1.bounds();
let bounds2 = poly2.bounds();
if let (Some((min_x1, min_y1, max_x1, max_y1)), Some((min_x2, min_y2, max_x2, max_y2))) =
(bounds1, bounds2)
{
if max_x1 < min_x2 || max_x2 < min_x1 || max_y1 < min_y2 || max_y2 < min_y1 {
return Ok(vec![]);
}
}
let intersection_points = intersect_linestrings(&poly1.exterior, &poly2.exterior)?;
if intersection_points.is_empty() {
if point_in_polygon(&poly1.exterior.coords[0], poly2)? {
return Ok(vec![poly1.clone()]);
}
if point_in_polygon(&poly2.exterior.coords[0], poly1)? {
return Ok(vec![poly2.clone()]);
}
return Ok(vec![]);
}
crate::vector::clipping::clip_polygons(
poly1,
poly2,
crate::vector::clipping::ClipOperation::Intersection,
)
}
pub fn point_in_polygon(point: &Coordinate, polygon: &Polygon) -> Result<bool> {
if polygon.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "point_in_polygon",
message: "polygon exterior must have at least 4 coordinates".to_string(),
});
}
let inside = point_in_ring(point, &polygon.exterior.coords);
if !inside {
return Ok(false);
}
for hole in &polygon.interiors {
if point_in_ring(point, &hole.coords) {
return Ok(false);
}
}
Ok(true)
}
fn point_in_ring(point: &Coordinate, ring: &[Coordinate]) -> bool {
let mut inside = false;
let n = ring.len();
let mut j = n - 1;
for i in 0..n {
let xi = ring[i].x;
let yi = ring[i].y;
let xj = ring[j].x;
let yj = ring[j].y;
let intersect = ((yi > point.y) != (yj > point.y))
&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
if intersect {
inside = !inside;
}
j = i;
}
inside
}
#[allow(dead_code)]
fn orientation(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate) -> f64 {
(p2.y - p1.y) * (p3.x - p2.x) - (p2.x - p1.x) * (p3.y - p2.y)
}
#[allow(dead_code)]
fn on_segment(p: &Coordinate, q: &Coordinate, r: &Coordinate) -> bool {
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)
}
pub fn intersect_polygons_pooled(
poly1: &Polygon,
poly2: &Polygon,
) -> Result<PoolGuard<'static, Polygon>> {
let results = intersect_polygons(poly1, poly2)?;
if let Some(result) = results.first() {
let mut poly = get_pooled_polygon();
poly.exterior = result.exterior.clone();
poly.interiors = result.interiors.clone();
Ok(poly)
} else {
Err(AlgorithmError::InsufficientData {
operation: "intersect_polygons_pooled",
message: "intersection resulted in no polygons".to_string(),
})
}
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_segment_intersection_point() {
let p1 = Coordinate::new_2d(0.0, 0.0);
let p2 = Coordinate::new_2d(10.0, 10.0);
let p3 = Coordinate::new_2d(0.0, 10.0);
let p4 = Coordinate::new_2d(10.0, 0.0);
let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
match result {
SegmentIntersection::Point(pt) => {
assert_relative_eq!(pt.x, 5.0, epsilon = 1e-10);
assert_relative_eq!(pt.y, 5.0, epsilon = 1e-10);
}
_ => panic!("Expected point intersection"),
}
}
#[test]
fn test_segment_intersection_none() {
let p1 = Coordinate::new_2d(0.0, 0.0);
let p2 = Coordinate::new_2d(10.0, 0.0);
let p3 = Coordinate::new_2d(0.0, 5.0);
let p4 = Coordinate::new_2d(10.0, 5.0);
let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
assert_eq!(result, SegmentIntersection::None);
}
#[test]
fn test_segment_intersection_collinear_overlap() {
let p1 = Coordinate::new_2d(0.0, 0.0);
let p2 = Coordinate::new_2d(10.0, 0.0);
let p3 = Coordinate::new_2d(5.0, 0.0);
let p4 = Coordinate::new_2d(15.0, 0.0);
let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
match result {
SegmentIntersection::Overlap(c1, c2) => {
assert_relative_eq!(c1.x, 5.0, epsilon = 1e-10);
assert_relative_eq!(c1.y, 0.0, epsilon = 1e-10);
assert_relative_eq!(c2.x, 10.0, epsilon = 1e-10);
assert_relative_eq!(c2.y, 0.0, epsilon = 1e-10);
}
_ => panic!("Expected overlap intersection"),
}
}
#[test]
fn test_segment_intersection_endpoint() {
let p1 = Coordinate::new_2d(0.0, 0.0);
let p2 = Coordinate::new_2d(10.0, 0.0);
let p3 = Coordinate::new_2d(10.0, 0.0);
let p4 = Coordinate::new_2d(10.0, 10.0);
let result = intersect_segment_segment(&p1, &p2, &p3, &p4);
match result {
SegmentIntersection::Point(pt) => {
assert_relative_eq!(pt.x, 10.0, epsilon = 1e-10);
assert_relative_eq!(pt.y, 0.0, epsilon = 1e-10);
}
_ => panic!("Expected point intersection at endpoint"),
}
}
#[test]
fn test_intersect_linestrings_basic() {
let coords1 = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(10.0, 10.0)];
let line1 = LineString::new(coords1);
assert!(line1.is_ok());
let coords2 = vec![Coordinate::new_2d(0.0, 10.0), Coordinate::new_2d(10.0, 0.0)];
let line2 = LineString::new(coords2);
assert!(line2.is_ok());
if let (Ok(ls1), Ok(ls2)) = (line1, line2) {
let result = intersect_linestrings(&ls1, &ls2);
assert!(result.is_ok());
if let Ok(points) = result {
assert_eq!(points.len(), 1);
assert_relative_eq!(points[0].x, 5.0, epsilon = 1e-10);
assert_relative_eq!(points[0].y, 5.0, epsilon = 1e-10);
}
}
}
#[test]
fn test_intersect_linestrings_multiple() {
let coords1 = vec![Coordinate::new_2d(0.0, 5.0), Coordinate::new_2d(10.0, 5.0)];
let line1 = LineString::new(coords1);
assert!(line1.is_ok());
let coords2 = vec![
Coordinate::new_2d(2.0, 0.0),
Coordinate::new_2d(2.0, 10.0),
Coordinate::new_2d(8.0, 10.0),
Coordinate::new_2d(8.0, 0.0),
];
let line2 = LineString::new(coords2);
assert!(line2.is_ok());
if let (Ok(ls1), Ok(ls2)) = (line1, line2) {
let result = intersect_linestrings(&ls1, &ls2);
assert!(result.is_ok());
if let Ok(points) = result {
assert_eq!(points.len(), 2);
}
}
}
#[test]
fn test_point_in_polygon_inside() {
let exterior_coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(10.0, 0.0),
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(0.0, 10.0),
Coordinate::new_2d(0.0, 0.0),
];
let exterior = LineString::new(exterior_coords);
assert!(exterior.is_ok());
if let Ok(ext) = exterior {
let polygon = Polygon::new(ext, vec![]);
assert!(polygon.is_ok());
if let Ok(poly) = polygon {
let point = Coordinate::new_2d(5.0, 5.0);
let result = point_in_polygon(&point, &poly);
assert!(result.is_ok());
if let Ok(inside) = result {
assert!(inside);
}
}
}
}
#[test]
fn test_point_in_polygon_outside() {
let exterior_coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(10.0, 0.0),
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(0.0, 10.0),
Coordinate::new_2d(0.0, 0.0),
];
let exterior = LineString::new(exterior_coords);
assert!(exterior.is_ok());
if let Ok(ext) = exterior {
let polygon = Polygon::new(ext, vec![]);
assert!(polygon.is_ok());
if let Ok(poly) = polygon {
let point = Coordinate::new_2d(15.0, 15.0);
let result = point_in_polygon(&point, &poly);
assert!(result.is_ok());
if let Ok(inside) = result {
assert!(!inside);
}
}
}
}
#[test]
fn test_intersect_polygons_disjoint() {
let coords1 = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(5.0, 0.0),
Coordinate::new_2d(5.0, 5.0),
Coordinate::new_2d(0.0, 5.0),
Coordinate::new_2d(0.0, 0.0),
];
let ext1 = LineString::new(coords1);
assert!(ext1.is_ok());
let coords2 = vec![
Coordinate::new_2d(10.0, 10.0),
Coordinate::new_2d(15.0, 10.0),
Coordinate::new_2d(15.0, 15.0),
Coordinate::new_2d(10.0, 15.0),
Coordinate::new_2d(10.0, 10.0),
];
let ext2 = LineString::new(coords2);
assert!(ext2.is_ok());
if let (Ok(e1), Ok(e2)) = (ext1, ext2) {
let poly1 = Polygon::new(e1, vec![]);
let poly2 = Polygon::new(e2, vec![]);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = intersect_polygons(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 0); }
}
}
}
}