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;
const EPSILON: f64 = 1e-10;
pub fn difference_polygon(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
use oxigdal_core::OxiGdalError;
if poly1.exterior.coords.len() < 4 {
return Err(OxiGdalError::invalid_parameter_builder(
"poly1",
format!("exterior must have at least 4 coordinates, got {}", poly1.exterior.coords.len()),
)
.with_parameter("coordinate_count", poly1.exterior.coords.len().to_string())
.with_parameter("min_count", "4")
.with_operation("difference_polygon")
.with_suggestion("A valid polygon requires at least 4 coordinates (first and last must be identical to close the ring)")
.build()
.into());
}
if poly2.exterior.coords.len() < 4 {
return Err(OxiGdalError::invalid_parameter_builder(
"poly2",
format!("exterior must have at least 4 coordinates, got {}", poly2.exterior.coords.len()),
)
.with_parameter("coordinate_count", poly2.exterior.coords.len().to_string())
.with_parameter("min_count", "4")
.with_operation("difference_polygon")
.with_suggestion("A valid polygon requires at least 4 coordinates (first and last must be identical to close the ring)")
.build()
.into());
}
crate::vector::clipping::clip_polygons(
poly1,
poly2,
crate::vector::clipping::ClipOperation::Difference,
)
}
fn point_in_ring(point: &Coordinate, ring: &[Coordinate]) -> bool {
crate::vector::clipping::point_in_ring(point, ring)
}
fn compute_ring_centroid(coords: &[Coordinate]) -> Coordinate {
crate::vector::clipping::compute_ring_centroid(coords)
}
pub fn difference_polygons(base: &[Polygon], subtract: &[Polygon]) -> Result<Vec<Polygon>> {
if base.is_empty() {
return Ok(vec![]);
}
if subtract.is_empty() {
return Ok(base.to_vec());
}
for (i, poly) in base.iter().enumerate() {
if poly.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "difference_polygons",
message: format!(
"base polygon {} exterior must have at least 4 coordinates",
i
),
});
}
}
for (i, poly) in subtract.iter().enumerate() {
if poly.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "difference_polygons",
message: format!(
"subtract polygon {} exterior must have at least 4 coordinates",
i
),
});
}
}
let mut result = base.to_vec();
for sub_poly in subtract {
let mut new_result = Vec::new();
for base_poly in &result {
let diff = difference_polygon(base_poly, sub_poly)?;
new_result.extend(diff);
}
result = new_result;
}
Ok(result)
}
pub fn symmetric_difference(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
let diff1 = difference_polygon(poly1, poly2)?;
let diff2 = difference_polygon(poly2, poly1)?;
let mut result = diff1;
result.extend(diff2);
Ok(result)
}
pub fn clip_to_box(
polygon: &Polygon,
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
) -> Result<Vec<Polygon>> {
if polygon.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "clip_to_box",
message: "polygon exterior must have at least 4 coordinates".to_string(),
});
}
if min_x >= max_x || min_y >= max_y {
return Err(AlgorithmError::InvalidParameter {
parameter: "bounding box",
message: "invalid bounding box dimensions".to_string(),
});
}
if let Some((poly_min_x, poly_min_y, poly_max_x, poly_max_y)) = polygon.bounds() {
if poly_max_x < min_x || poly_min_x > max_x || poly_max_y < min_y || poly_min_y > max_y {
return Ok(vec![]);
}
if poly_min_x >= min_x && poly_max_x <= max_x && poly_min_y >= min_y && poly_max_y <= max_y
{
return Ok(vec![polygon.clone()]);
}
}
let mut clipped_coords = polygon.exterior.coords.clone();
clipped_coords = clip_against_edge(&clipped_coords, min_x, true, false)?; clipped_coords = clip_against_edge(&clipped_coords, max_x, true, true)?; clipped_coords = clip_against_edge(&clipped_coords, min_y, false, false)?; clipped_coords = clip_against_edge(&clipped_coords, max_y, false, true)?;
if clipped_coords.len() < 4 {
return Ok(vec![]);
}
if let (Some(first), Some(last)) = (clipped_coords.first(), clipped_coords.last()) {
if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
clipped_coords.push(*first);
}
}
let clipped_exterior = LineString::new(clipped_coords).map_err(AlgorithmError::Core)?;
let clipped_interiors = clip_interior_rings_to_box(
&polygon.interiors,
min_x,
min_y,
max_x,
max_y,
&clipped_exterior,
)?;
let result = Polygon::new(clipped_exterior, clipped_interiors).map_err(AlgorithmError::Core)?;
Ok(vec![result])
}
fn clip_interior_rings_to_box(
interiors: &[LineString],
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
clipped_exterior: &LineString,
) -> Result<Vec<LineString>> {
let mut result = Vec::new();
for hole in interiors {
if hole.coords.len() < 4 {
continue;
}
let hole_bounds = hole.bounds();
if let Some((hole_min_x, hole_min_y, hole_max_x, hole_max_y)) = hole_bounds {
if hole_max_x < min_x || hole_min_x > max_x || hole_max_y < min_y || hole_min_y > max_y
{
continue;
}
if hole_min_x >= min_x
&& hole_max_x <= max_x
&& hole_min_y >= min_y
&& hole_max_y <= max_y
{
if is_ring_inside_ring(&hole.coords, &clipped_exterior.coords) {
result.push(hole.clone());
}
continue;
}
}
let clipped_hole = clip_ring_to_box(&hole.coords, min_x, min_y, max_x, max_y)?;
if clipped_hole.len() >= 4 {
if is_ring_inside_ring(&clipped_hole, &clipped_exterior.coords) {
let mut closed_hole = clipped_hole;
if let (Some(first), Some(last)) = (closed_hole.first(), closed_hole.last()) {
if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
closed_hole.push(*first);
}
}
if closed_hole.len() >= 4 {
if let Ok(hole_ring) = LineString::new(closed_hole) {
result.push(hole_ring);
}
}
}
}
}
Ok(result)
}
fn clip_ring_to_box(
coords: &[Coordinate],
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
) -> Result<Vec<Coordinate>> {
let mut clipped = coords.to_vec();
clipped = clip_against_edge(&clipped, min_x, true, false)?; clipped = clip_against_edge(&clipped, max_x, true, true)?; clipped = clip_against_edge(&clipped, min_y, false, false)?; clipped = clip_against_edge(&clipped, max_y, false, true)?;
Ok(clipped)
}
fn is_ring_inside_ring(inner: &[Coordinate], outer: &[Coordinate]) -> bool {
if inner.is_empty() || outer.is_empty() {
return false;
}
let centroid = compute_ring_centroid(inner);
point_in_ring(¢roid, outer)
}
pub fn validate_polygon_topology(polygon: &Polygon) -> Result<bool> {
if polygon.exterior.coords.len() < 4 {
return Ok(false);
}
if let (Some(first), Some(last)) = (
polygon.exterior.coords.first(),
polygon.exterior.coords.last(),
) {
if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
return Ok(false);
}
}
for hole in &polygon.interiors {
if hole.coords.len() < 4 {
return Ok(false);
}
if let (Some(first), Some(last)) = (hole.coords.first(), hole.coords.last()) {
if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
return Ok(false);
}
}
if !is_ring_inside_ring(&hole.coords, &polygon.exterior.coords) {
return Ok(false);
}
}
for i in 0..polygon.interiors.len() {
for j in (i + 1)..polygon.interiors.len() {
if rings_overlap(&polygon.interiors[i].coords, &polygon.interiors[j].coords)? {
return Ok(false);
}
}
}
Ok(true)
}
fn rings_overlap(ring1: &[Coordinate], ring2: &[Coordinate]) -> Result<bool> {
for coord in ring1 {
if point_in_ring(coord, ring2) {
return Ok(true);
}
}
for coord in ring2 {
if point_in_ring(coord, ring1) {
return Ok(true);
}
}
if ring1.len() < 2 || ring2.len() < 2 {
return Ok(false);
}
for i in 0..(ring1.len() - 1) {
for j in 0..(ring2.len() - 1) {
if segments_intersect(&ring1[i], &ring1[i + 1], &ring2[j], &ring2[j + 1]) {
return Ok(true);
}
}
}
Ok(false)
}
fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
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;
if cross.abs() < EPSILON {
return false;
}
let dx = p3.x - p1.x;
let dy = p3.y - p1.y;
let t = (dx * d2y - dy * d2x) / cross;
let u = (dx * d1y - dy * d1x) / cross;
t > EPSILON && t < (1.0 - EPSILON) && u > EPSILON && u < (1.0 - EPSILON)
}
pub fn merge_adjacent_holes(holes: &[LineString]) -> Result<Vec<LineString>> {
if holes.is_empty() {
return Ok(vec![]);
}
if holes.len() == 1 {
return Ok(holes.to_vec());
}
let mut result = Vec::new();
let mut merged = vec![false; holes.len()];
for i in 0..holes.len() {
if merged[i] {
continue;
}
let mut current_hole = holes[i].coords.clone();
for j in (i + 1)..holes.len() {
if merged[j] {
continue;
}
if rings_overlap(¤t_hole, &holes[j].coords)? {
if compute_ring_area(&holes[j].coords).abs()
> compute_ring_area(¤t_hole).abs()
{
current_hole = holes[j].coords.clone();
}
merged[j] = true;
}
}
if current_hole.len() >= 4 {
if let Ok(ring) = LineString::new(current_hole) {
result.push(ring);
}
}
}
Ok(result)
}
fn clip_against_edge(
coords: &[Coordinate],
edge_value: f64,
is_vertical: bool,
is_max: bool,
) -> Result<Vec<Coordinate>> {
if coords.is_empty() {
return Ok(vec![]);
}
let mut result = Vec::new();
for i in 0..coords.len() {
let current = &coords[i];
let next = &coords[(i + 1) % coords.len()];
let current_inside = is_inside(current, edge_value, is_vertical, is_max);
let next_inside = is_inside(next, edge_value, is_vertical, is_max);
if current_inside {
result.push(*current);
}
if current_inside != next_inside {
if let Some(intersection) = compute_intersection(current, next, edge_value, is_vertical)
{
result.push(intersection);
}
}
}
Ok(result)
}
fn is_inside(point: &Coordinate, edge_value: f64, is_vertical: bool, is_max: bool) -> bool {
let value = if is_vertical { point.x } else { point.y };
if is_max {
value <= edge_value
} else {
value >= edge_value
}
}
fn compute_intersection(
p1: &Coordinate,
p2: &Coordinate,
edge_value: f64,
is_vertical: bool,
) -> Option<Coordinate> {
if is_vertical {
let dx = p2.x - p1.x;
if dx.abs() < f64::EPSILON {
return None; }
let t = (edge_value - p1.x) / dx;
if (0.0..=1.0).contains(&t) {
let y = p1.y + t * (p2.y - p1.y);
Some(Coordinate::new_2d(edge_value, y))
} else {
None
}
} else {
let dy = p2.y - p1.y;
if dy.abs() < f64::EPSILON {
return None; }
let t = (edge_value - p1.y) / dy;
if (0.0..=1.0).contains(&t) {
let x = p1.x + t * (p2.x - p1.x);
Some(Coordinate::new_2d(x, edge_value))
} else {
None
}
}
}
pub fn erase_small_holes(polygon: &Polygon, min_area: f64) -> Result<Polygon> {
if polygon.exterior.coords.len() < 4 {
return Err(AlgorithmError::InsufficientData {
operation: "erase_small_holes",
message: "polygon exterior must have at least 4 coordinates".to_string(),
});
}
if min_area < 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "min_area",
message: "min_area must be non-negative".to_string(),
});
}
let mut kept_holes = Vec::new();
for hole in &polygon.interiors {
let area = compute_ring_area(&hole.coords).abs();
if area >= min_area {
kept_holes.push(hole.clone());
}
}
Polygon::new(polygon.exterior.clone(), kept_holes).map_err(AlgorithmError::Core)
}
fn compute_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 difference_polygon_pooled(
subject: &Polygon,
clip: &Polygon,
) -> Result<PoolGuard<'static, Polygon>> {
let results = difference_polygon(subject, clip)?;
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: "difference_polygon_pooled",
message: "difference resulted in no polygons".to_string(),
})
}
}
pub fn difference_polygons_pooled(
base: &[Polygon],
subtract: &[Polygon],
) -> Result<PoolGuard<'static, Polygon>> {
let results = difference_polygons(base, subtract)?;
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: "difference_polygons_pooled",
message: "difference resulted in no polygons".to_string(),
})
}
}
pub fn symmetric_difference_pooled(
poly1: &Polygon,
poly2: &Polygon,
) -> Result<Vec<PoolGuard<'static, Polygon>>> {
let results = symmetric_difference(poly1, poly2)?;
let mut pooled_results = Vec::new();
for result in results {
let mut poly = get_pooled_polygon();
poly.exterior = result.exterior;
poly.interiors = result.interiors;
pooled_results.push(poly);
}
Ok(pooled_results)
}
#[cfg(test)]
mod tests {
use super::*;
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_difference_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 = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1); }
}
}
#[test]
fn test_difference_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 = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
assert_eq!(polys[0].interiors.len(), 1);
}
}
}
#[test]
fn test_difference_polygon_completely_subtracted() {
let poly1 = create_square(2.0, 2.0, 3.0);
let poly2 = create_square(0.0, 0.0, 10.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 0);
}
}
}
#[test]
fn test_difference_polygons_multiple() {
let base = vec![create_square(0.0, 0.0, 10.0).ok()];
let subtract = vec![
create_square(2.0, 2.0, 2.0).ok(),
create_square(6.0, 6.0, 2.0).ok(),
];
let base_polys: Vec<_> = base.into_iter().flatten().collect();
let subtract_polys: Vec<_> = subtract.into_iter().flatten().collect();
let result = difference_polygons(&base_polys, &subtract_polys);
assert!(result.is_ok());
}
#[test]
fn test_symmetric_difference() {
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 = symmetric_difference(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert!(!polys.is_empty());
}
}
}
#[test]
fn test_clip_to_box_inside() {
let poly = create_square(2.0, 2.0, 3.0);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
}
}
}
#[test]
fn test_clip_to_box_outside() {
let poly = create_square(15.0, 15.0, 3.0);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 0);
}
}
}
#[test]
fn test_clip_to_box_partial() {
let poly = create_square(5.0, 5.0, 10.0);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
}
}
}
#[test]
fn test_erase_small_holes() {
let exterior_coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(20.0, 0.0),
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(0.0, 20.0),
Coordinate::new_2d(0.0, 0.0),
];
let small_hole_coords = vec![
Coordinate::new_2d(2.0, 2.0),
Coordinate::new_2d(3.0, 2.0),
Coordinate::new_2d(3.0, 3.0),
Coordinate::new_2d(2.0, 3.0),
Coordinate::new_2d(2.0, 2.0),
];
let large_hole_coords = 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 exterior = LineString::new(exterior_coords);
let small_hole = LineString::new(small_hole_coords);
let large_hole = LineString::new(large_hole_coords);
assert!(exterior.is_ok() && small_hole.is_ok() && large_hole.is_ok());
if let (Ok(ext), Ok(sh), Ok(lh)) = (exterior, small_hole, large_hole) {
let polygon = Polygon::new(ext, vec![sh, lh]);
assert!(polygon.is_ok());
if let Ok(poly) = polygon {
let result = erase_small_holes(&poly, 10.0);
assert!(result.is_ok());
if let Ok(cleaned) = result {
assert_eq!(cleaned.interiors.len(), 1);
}
}
}
}
fn create_square_with_hole(
x: f64,
y: f64,
size: f64,
hole_x: f64,
hole_y: f64,
hole_size: f64,
) -> Result<Polygon> {
let exterior_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 hole_coords = vec![
Coordinate::new_2d(hole_x, hole_y),
Coordinate::new_2d(hole_x + hole_size, hole_y),
Coordinate::new_2d(hole_x + hole_size, hole_y + hole_size),
Coordinate::new_2d(hole_x, hole_y + hole_size),
Coordinate::new_2d(hole_x, hole_y),
];
let exterior = LineString::new(exterior_coords).map_err(AlgorithmError::Core)?;
let hole = LineString::new(hole_coords).map_err(AlgorithmError::Core)?;
Polygon::new(exterior, vec![hole]).map_err(AlgorithmError::Core)
}
#[test]
fn test_difference_poly1_with_hole_disjoint() {
let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
let poly2 = create_square(30.0, 30.0, 5.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
assert_eq!(polys[0].interiors.len(), 1);
}
}
}
#[test]
fn test_difference_poly1_with_hole_contained_subtract() {
let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
let poly2 = create_square(12.0, 12.0, 3.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
assert_eq!(polys[0].interiors.len(), 2);
}
}
}
#[test]
fn test_difference_subtract_poly_with_hole() {
let poly1 = create_square(0.0, 0.0, 20.0);
let poly2 = create_square_with_hole(2.0, 2.0, 16.0, 6.0, 6.0, 4.0);
assert!(poly1.is_ok() && poly2.is_ok());
if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
let result = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert!(!polys.is_empty());
}
}
}
#[test]
fn test_difference_poly1_inside_hole_of_poly2() {
let exterior_coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(30.0, 0.0),
Coordinate::new_2d(30.0, 30.0),
Coordinate::new_2d(0.0, 30.0),
Coordinate::new_2d(0.0, 0.0),
];
let hole_coords = vec![
Coordinate::new_2d(5.0, 5.0),
Coordinate::new_2d(25.0, 5.0),
Coordinate::new_2d(25.0, 25.0),
Coordinate::new_2d(5.0, 25.0),
Coordinate::new_2d(5.0, 5.0),
];
let exterior = LineString::new(exterior_coords);
let hole = LineString::new(hole_coords);
assert!(exterior.is_ok() && hole.is_ok());
if let (Ok(ext), Ok(h)) = (exterior, hole) {
let poly2 = Polygon::new(ext, vec![h]);
let poly1 = 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 = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
}
}
}
}
#[test]
fn test_clip_to_box_with_hole_inside() {
let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = clip_to_box(&p, 0.0, 0.0, 25.0, 25.0);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
assert_eq!(polys[0].interiors.len(), 1);
}
}
}
#[test]
fn test_clip_to_box_hole_outside() {
let exterior_coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(20.0, 0.0),
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(0.0, 20.0),
Coordinate::new_2d(0.0, 0.0),
];
let hole_coords = vec![
Coordinate::new_2d(15.0, 15.0),
Coordinate::new_2d(18.0, 15.0),
Coordinate::new_2d(18.0, 18.0),
Coordinate::new_2d(15.0, 18.0),
Coordinate::new_2d(15.0, 15.0),
];
let exterior = LineString::new(exterior_coords);
let hole = LineString::new(hole_coords);
assert!(exterior.is_ok() && hole.is_ok());
if let (Ok(ext), Ok(h)) = (exterior, hole) {
let poly = Polygon::new(ext, vec![h]);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
assert_eq!(polys[0].interiors.len(), 0);
}
}
}
}
#[test]
fn test_clip_to_box_hole_partial() {
let exterior_coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(20.0, 0.0),
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(0.0, 20.0),
Coordinate::new_2d(0.0, 0.0),
];
let hole_coords = vec![
Coordinate::new_2d(8.0, 8.0),
Coordinate::new_2d(12.0, 8.0),
Coordinate::new_2d(12.0, 12.0),
Coordinate::new_2d(8.0, 12.0),
Coordinate::new_2d(8.0, 8.0),
];
let exterior = LineString::new(exterior_coords);
let hole = LineString::new(hole_coords);
assert!(exterior.is_ok() && hole.is_ok());
if let (Ok(ext), Ok(h)) = (exterior, hole) {
let poly = Polygon::new(ext, vec![h]);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
assert!(result.is_ok());
}
}
}
#[test]
fn test_validate_polygon_topology_valid() {
let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
assert!(poly.is_ok());
if let Ok(p) = poly {
let result = validate_polygon_topology(&p);
assert!(result.is_ok());
if let Ok(valid) = result {
assert!(valid);
}
}
}
#[test]
fn test_validate_polygon_topology_hole_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 hole_coords = vec![
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(25.0, 20.0),
Coordinate::new_2d(25.0, 25.0),
Coordinate::new_2d(20.0, 25.0),
Coordinate::new_2d(20.0, 20.0),
];
let exterior = LineString::new(exterior_coords);
let hole = LineString::new(hole_coords);
assert!(exterior.is_ok() && hole.is_ok());
if let (Ok(ext), Ok(h)) = (exterior, hole) {
let poly = Polygon {
exterior: ext,
interiors: vec![h],
};
let result = validate_polygon_topology(&poly);
assert!(result.is_ok());
if let Ok(valid) = result {
assert!(!valid);
}
}
}
#[test]
fn test_merge_adjacent_holes_no_overlap() {
let hole1_coords = vec![
Coordinate::new_2d(2.0, 2.0),
Coordinate::new_2d(4.0, 2.0),
Coordinate::new_2d(4.0, 4.0),
Coordinate::new_2d(2.0, 4.0),
Coordinate::new_2d(2.0, 2.0),
];
let hole2_coords = vec![
Coordinate::new_2d(6.0, 6.0),
Coordinate::new_2d(8.0, 6.0),
Coordinate::new_2d(8.0, 8.0),
Coordinate::new_2d(6.0, 8.0),
Coordinate::new_2d(6.0, 6.0),
];
let hole1 = LineString::new(hole1_coords);
let hole2 = LineString::new(hole2_coords);
assert!(hole1.is_ok() && hole2.is_ok());
if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
let result = merge_adjacent_holes(&[h1, h2]);
assert!(result.is_ok());
if let Ok(merged) = result {
assert_eq!(merged.len(), 2);
}
}
}
#[test]
fn test_merge_adjacent_holes_with_overlap() {
let hole1_coords = vec![
Coordinate::new_2d(2.0, 2.0),
Coordinate::new_2d(6.0, 2.0),
Coordinate::new_2d(6.0, 6.0),
Coordinate::new_2d(2.0, 6.0),
Coordinate::new_2d(2.0, 2.0),
];
let hole2_coords = vec![
Coordinate::new_2d(4.0, 4.0),
Coordinate::new_2d(8.0, 4.0),
Coordinate::new_2d(8.0, 8.0),
Coordinate::new_2d(4.0, 8.0),
Coordinate::new_2d(4.0, 4.0),
];
let hole1 = LineString::new(hole1_coords);
let hole2 = LineString::new(hole2_coords);
assert!(hole1.is_ok() && hole2.is_ok());
if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
let result = merge_adjacent_holes(&[h1, h2]);
assert!(result.is_ok());
if let Ok(merged) = result {
assert_eq!(merged.len(), 1);
}
}
}
#[test]
fn test_point_in_ring() {
let ring = 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 inside = point_in_ring(&Coordinate::new_2d(5.0, 5.0), &ring);
assert!(inside);
let outside = point_in_ring(&Coordinate::new_2d(15.0, 15.0), &ring);
assert!(!outside);
let on_edge = point_in_ring(&Coordinate::new_2d(5.0, 0.0), &ring);
let _ = on_edge;
}
#[test]
fn test_is_ring_inside_ring() {
let outer = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(20.0, 0.0),
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(0.0, 20.0),
Coordinate::new_2d(0.0, 0.0),
];
let inner = vec![
Coordinate::new_2d(5.0, 5.0),
Coordinate::new_2d(15.0, 5.0),
Coordinate::new_2d(15.0, 15.0),
Coordinate::new_2d(5.0, 15.0),
Coordinate::new_2d(5.0, 5.0),
];
let outside = vec![
Coordinate::new_2d(30.0, 30.0),
Coordinate::new_2d(40.0, 30.0),
Coordinate::new_2d(40.0, 40.0),
Coordinate::new_2d(30.0, 40.0),
Coordinate::new_2d(30.0, 30.0),
];
assert!(is_ring_inside_ring(&inner, &outer));
assert!(!is_ring_inside_ring(&outside, &outer));
}
#[test]
fn test_clip_ring_to_box() {
let ring = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(20.0, 0.0),
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(0.0, 20.0),
Coordinate::new_2d(0.0, 0.0),
];
let result = clip_ring_to_box(&ring, 5.0, 5.0, 15.0, 15.0);
assert!(result.is_ok());
if let Ok(clipped) = result {
assert!(clipped.len() >= 4);
}
}
#[test]
fn test_compute_ring_centroid() {
let ring = 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 centroid = compute_ring_centroid(&ring);
assert!((centroid.x - 4.0).abs() < 1.0); assert!((centroid.y - 4.0).abs() < 1.0);
}
#[test]
fn test_difference_preserves_unaffected_holes() {
let exterior_coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(30.0, 0.0),
Coordinate::new_2d(30.0, 30.0),
Coordinate::new_2d(0.0, 30.0),
Coordinate::new_2d(0.0, 0.0),
];
let hole1_coords = vec![
Coordinate::new_2d(2.0, 2.0),
Coordinate::new_2d(5.0, 2.0),
Coordinate::new_2d(5.0, 5.0),
Coordinate::new_2d(2.0, 5.0),
Coordinate::new_2d(2.0, 2.0),
];
let hole2_coords = vec![
Coordinate::new_2d(20.0, 20.0),
Coordinate::new_2d(25.0, 20.0),
Coordinate::new_2d(25.0, 25.0),
Coordinate::new_2d(20.0, 25.0),
Coordinate::new_2d(20.0, 20.0),
];
let exterior = LineString::new(exterior_coords);
let hole1 = LineString::new(hole1_coords);
let hole2 = LineString::new(hole2_coords);
assert!(exterior.is_ok() && hole1.is_ok() && hole2.is_ok());
if let (Ok(ext), Ok(h1), Ok(h2)) = (exterior, hole1, hole2) {
let poly1 = Polygon::new(ext, vec![h1, h2]);
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 = difference_polygon(&p1, &p2);
assert!(result.is_ok());
if let Ok(polys) = result {
assert_eq!(polys.len(), 1);
assert!(polys[0].interiors.len() >= 2);
}
}
}
}
}