use crate::error::{AlgorithmError, Result};
use crate::vector::intersection::{intersect_linestrings, point_in_polygon};
use crate::vector::pool::{PoolGuard, get_pooled_polygon};
use oxigdal_core::vector::{Coordinate, Polygon};
#[cfg(feature = "std")]
use std::vec::Vec;
pub fn union_polygon(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
if poly1.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "union_polygon",
message: "poly1 exterior must have at least 4 coordinates".to_string(),
});
}
if poly2.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "union_polygon",
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![poly1.clone(), poly2.clone()]);
}
}
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![poly2.clone()]);
}
if point_in_polygon(&poly2.exterior.coords[0], poly1)? {
return Ok(vec![poly1.clone()]);
}
return Ok(vec![poly1.clone(), poly2.clone()]);
}
crate::vector::clipping::clip_polygons(
poly1,
poly2,
crate::vector::clipping::ClipOperation::Union,
)
}
pub fn union_polygons(polygons: &[Polygon]) -> Result<Vec<Polygon>> {
cascaded_union(polygons)
}
pub fn cascaded_union(polygons: &[Polygon]) -> Result<Vec<Polygon>> {
if polygons.is_empty() {
return Ok(vec![]);
}
if polygons.len() == 1 {
return Ok(vec![polygons[0].clone()]);
}
for (i, poly) in polygons.iter().enumerate() {
if poly.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "cascaded_union",
message: format!("polygon {} exterior must have at least 4 coordinates", i),
});
}
}
let mut current = polygons.to_vec();
let max_iterations = (polygons.len() as f64).log2().ceil() as usize + 1;
let mut iteration = 0;
while current.len() > 1 && iteration < max_iterations {
let mut next_level = Vec::new();
let initial_count = current.len();
let mut i = 0;
while i < current.len() {
if i + 1 < current.len() {
let union_result = union_polygon(¤t[i], ¤t[i + 1])?;
next_level.extend(union_result);
i += 2;
} else {
next_level.push(current[i].clone());
i += 1;
}
}
current = next_level;
iteration += 1;
if current.len() >= initial_count {
break;
}
}
Ok(current)
}
pub fn merge_polygons(polygons: &[Polygon], tolerance: f64) -> Result<Vec<Polygon>> {
if polygons.is_empty() {
return Ok(vec![]);
}
if tolerance < 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "tolerance",
message: "tolerance must be non-negative".to_string(),
});
}
let mut merged = Vec::new();
let mut used = vec![false; polygons.len()];
for i in 0..polygons.len() {
if used[i] {
continue;
}
let mut group = vec![polygons[i].clone()];
used[i] = true;
for j in (i + 1)..polygons.len() {
if used[j] {
continue;
}
let mut is_close = false;
for poly in &group {
if polygons_are_close(poly, &polygons[j], tolerance) {
is_close = true;
break;
}
}
if is_close {
group.push(polygons[j].clone());
used[j] = true;
}
}
let union_result = cascaded_union(&group)?;
merged.extend(union_result);
}
Ok(merged)
}
fn polygons_are_close(poly1: &Polygon, poly2: &Polygon, tolerance: f64) -> bool {
if let (Some(b1), Some(b2)) = (poly1.bounds(), poly2.bounds()) {
let (min_x1, min_y1, max_x1, max_y1) = b1;
let (min_x2, min_y2, max_x2, max_y2) = b2;
let x_gap = if max_x1 < min_x2 {
min_x2 - max_x1
} else if max_x2 < min_x1 {
min_x1 - max_x2
} else {
0.0
};
let y_gap = if max_y1 < min_y2 {
min_y2 - max_y1
} else if max_y2 < min_y1 {
min_y1 - max_y2
} else {
0.0
};
x_gap <= tolerance && y_gap <= tolerance
} else {
false
}
}
fn compute_polygon_area(polygon: &Polygon) -> f64 {
let mut area = ring_area(&polygon.exterior.coords);
for hole in &polygon.interiors {
area -= ring_area(&hole.coords);
}
area.abs()
}
fn ring_area(coords: &[Coordinate]) -> f64 {
if coords.len() < 3 {
return 0.0;
}
let mut area = 0.0;
let n = coords.len();
for i in 0..n {
let j = (i + 1) % n;
area += coords[i].x * coords[j].y;
area -= coords[j].x * coords[i].y;
}
area / 2.0
}
pub fn convex_hull(points: &[Coordinate]) -> Result<Vec<Coordinate>> {
if points.len() < 3 {
return Err(AlgorithmError::InsufficientData {
operation: "convex_hull",
message: "need at least 3 points for convex hull".to_string(),
});
}
let mut lowest_idx = 0;
for i in 1..points.len() {
if points[i].y < points[lowest_idx].y
|| (points[i].y == points[lowest_idx].y && points[i].x < points[lowest_idx].x)
{
lowest_idx = i;
}
}
let pivot = points[lowest_idx];
let mut sorted_points: Vec<(usize, Coordinate, f64)> = points
.iter()
.enumerate()
.filter(|(i, _)| *i != lowest_idx)
.map(|(i, p)| {
let angle = (p.y - pivot.y).atan2(p.x - pivot.x);
(i, *p, angle)
})
.collect();
sorted_points.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal));
let mut hull = vec![pivot];
for (_, point, _) in sorted_points {
while hull.len() >= 2 {
let n = hull.len();
let cross = cross_product(&hull[n - 2], &hull[n - 1], &point);
if cross <= 0.0 {
hull.pop();
} else {
break;
}
}
hull.push(point);
}
Ok(hull)
}
fn cross_product(o: &Coordinate, a: &Coordinate, b: &Coordinate) -> f64 {
(a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)
}
pub fn union_polygon_pooled(
poly1: &Polygon,
poly2: &Polygon,
) -> Result<PoolGuard<'static, Polygon>> {
let results = union_polygon(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: "union_polygon_pooled",
message: "union resulted in no polygons".to_string(),
})
}
}
pub fn union_polygons_pooled(polygons: &[Polygon]) -> Result<PoolGuard<'static, Polygon>> {
let results = union_polygons(polygons)?;
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: "union_polygons_pooled",
message: "union resulted in no polygons".to_string(),
})
}
}
pub fn cascaded_union_pooled(polygons: &[Polygon]) -> Result<PoolGuard<'static, Polygon>> {
union_polygons_pooled(polygons)
}
pub fn convex_hull_pooled(points: &[Coordinate]) -> Result<PoolGuard<'static, Polygon>> {
use oxigdal_core::vector::LineString;
let hull_coords = convex_hull(points)?;
let mut poly = get_pooled_polygon();
poly.exterior.coords.clear();
poly.exterior.coords.extend(hull_coords);
if let (Some(&first), Some(&last)) = (poly.exterior.coords.first(), poly.exterior.coords.last())
{
if first.x != last.x || first.y != last.y {
poly.exterior.coords.push(first);
}
}
while poly.exterior.len() < 4 {
if let Some(&first) = poly.exterior.coords.first() {
poly.exterior.coords.push(first);
}
}
Ok(poly)
}
#[cfg(test)]
mod tests {
use super::*;
use oxigdal_core::vector::LineString;
fn create_square(x: f64, y: f64, size: f64) -> Result<Polygon> {
let coords = vec![
Coordinate::new_2d(x, y),
Coordinate::new_2d(x + size, y),
Coordinate::new_2d(x + size, y + size),
Coordinate::new_2d(x, y + size),
Coordinate::new_2d(x, y),
];
let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
}
#[test]
fn test_union_polygon_disjoint() {
let poly1 = create_square(0.0, 0.0, 5.0);
let poly2 = create_square(10.0, 10.0, 5.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = union_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 2); }
}
}
#[test]
fn test_union_polygon_contained() {
let poly1 = create_square(0.0, 0.0, 10.0);
let poly2 = create_square(2.0, 2.0, 3.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = union_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1); }
}
}
#[test]
fn test_union_polygon_overlapping() {
let poly1 = create_square(0.0, 0.0, 5.0);
let poly2 = create_square(3.0, 0.0, 5.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = union_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert!(!polys.is_empty());
}
}
}
#[test]
fn test_cascaded_union_empty() {
let result = cascaded_union(&[]);
assert!(result.is_ok());
if let Ok(polys) = result {
assert!(polys.is_empty());
}
}
#[test]
fn test_cascaded_union_single() {
let poly = create_square(0.0, 0.0, 5.0);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = cascaded_union(std::slice::from_ref(&p));
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
}
}
}
#[test]
fn test_cascaded_union_multiple() {
let poly1 = create_square(0.0, 0.0, 5.0);
let poly2 = create_square(10.0, 0.0, 5.0);
let poly3 = create_square(20.0, 0.0, 5.0);
assert!(poly1.is_ok() && poly2.is_ok() && poly3.is_ok());
if let (Ok(p1), Ok(p2), Ok(p3)) = (poly1, poly2, poly3) {
let result = cascaded_union(&[p1, p2, p3]);
assert!(result.is_ok());
if let Ok(polys) = result {
assert!(!polys.is_empty());
}
}
}
#[test]
fn test_merge_polygons() {
let poly1 = create_square(0.0, 0.0, 5.0);
let poly2 = create_square(10.0, 0.0, 5.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = merge_polygons(&[p1, p2], 1.0);
assert!(result.is_ok());
}
}
#[test]
fn test_compute_polygon_area() {
let poly = create_square(0.0, 0.0, 10.0);
assert!(poly.is_ok());
if let Ok(p) = poly {
let area = compute_polygon_area(&p);
assert!((area - 100.0).abs() < 1e-10);
}
}
#[test]
fn test_ring_area() {
let 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 area = ring_area(&coords);
assert!((area.abs() - 100.0).abs() < 1e-10);
}
#[test]
fn test_convex_hull_triangle() {
let points = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(4.0, 0.0),
Coordinate::new_2d(2.0, 3.0),
];
let result = convex_hull(&points);
assert!(result.is_ok());
if let Ok(hull) = result {
assert_eq!(hull.len(), 3);
}
}
#[test]
fn test_convex_hull_square_with_interior() {
let points = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(4.0, 0.0),
Coordinate::new_2d(4.0, 4.0),
Coordinate::new_2d(0.0, 4.0),
Coordinate::new_2d(2.0, 2.0), ];
let result = convex_hull(&points);
assert!(result.is_ok());
if let Ok(hull) = result {
assert_eq!(hull.len(), 4); }
}
#[test]
fn test_convex_hull_insufficient_points() {
let points = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(1.0, 1.0)];
let result = convex_hull(&points);
assert!(result.is_err());
}
#[test]
fn test_polygons_are_close() {
let poly1 = create_square(0.0, 0.0, 5.0);
let poly2 = create_square(5.5, 0.0, 5.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
assert!(polygons_are_close(&p1, &p2, 1.0)); assert!(!polygons_are_close(&p1, &p2, 0.1)); }
}
#[test]
fn test_cascaded_union_many_disjoint_no_infinite_loop() {
let mut polygons = Vec::new();
for i in 0..10 {
let x = (i as f64) * 20.0; if let Ok(poly) = create_square(x, 0.0, 5.0) {
polygons.push(poly);
}
}
let start = std::time::Instant::now();
let result = cascaded_union(&polygons);
let elapsed = start.elapsed();
assert!(
elapsed.as_secs() < 1,
"cascaded_union took too long: {elapsed:?}"
);
assert!(result.is_ok());
if let Ok(results) = result {
assert_eq!(results.len(), 10);
}
}
}