use crate::error::{AlgorithmError, Result};
use crate::vector::topology::{OverlayOptions, OverlayType, overlay_polygon};
use oxigdal_core::vector::{Coordinate, LineString, MultiPolygon, Polygon};
#[derive(Debug, Clone)]
pub struct EraseOptions {
pub tolerance: f64,
pub buffer_erase_geom: bool,
pub buffer_distance: f64,
pub min_area: f64,
pub remove_slivers: bool,
pub sliver_threshold: f64,
}
impl Default for EraseOptions {
fn default() -> Self {
Self {
tolerance: 1e-10,
buffer_erase_geom: false,
buffer_distance: 0.0,
min_area: 0.0,
remove_slivers: false,
sliver_threshold: 0.1,
}
}
}
pub fn erase_polygon(
target_poly: &Polygon,
erase_poly: &Polygon,
options: &EraseOptions,
) -> Result<Vec<Polygon>> {
let overlay_options = OverlayOptions {
tolerance: options.tolerance,
preserve_topology: true,
snap_to_grid: false,
grid_size: 1e-6,
simplify_result: false,
simplify_tolerance: 1e-8,
};
let erase_geom = if options.buffer_erase_geom && options.buffer_distance.abs() > 1e-10 {
buffer_erase_polygon(erase_poly, options.buffer_distance)?
} else {
erase_poly.clone()
};
let mut result = overlay_polygon(
target_poly,
&erase_geom,
OverlayType::Difference,
&overlay_options,
)?;
result = filter_results(result, options)?;
Ok(result)
}
fn buffer_erase_polygon(polygon: &Polygon, distance: f64) -> Result<Polygon> {
use crate::vector::buffer::{BufferOptions, buffer_polygon};
let buffer_options = BufferOptions::default();
buffer_polygon(polygon, distance, &buffer_options)
}
fn filter_results(mut polygons: Vec<Polygon>, options: &EraseOptions) -> Result<Vec<Polygon>> {
if options.min_area > 0.0 {
polygons.retain(|poly| {
if let Ok(area) = compute_polygon_area(poly) {
area >= options.min_area
} else {
false
}
});
}
if options.remove_slivers {
polygons.retain(|poly| !is_sliver(poly, options.sliver_threshold));
}
Ok(polygons)
}
fn compute_polygon_area(polygon: &Polygon) -> Result<f64> {
use crate::vector::area::{AreaMethod, area_polygon};
area_polygon(polygon, AreaMethod::Planar)
}
fn is_sliver(polygon: &Polygon, threshold: f64) -> bool {
let area = match compute_polygon_area(polygon) {
Ok(a) => a,
Err(_) => return false,
};
let perimeter = compute_polygon_perimeter(polygon);
if perimeter < 1e-10 {
return false;
}
let compactness = (4.0 * std::f64::consts::PI * area) / (perimeter * perimeter);
compactness < threshold
}
fn compute_polygon_perimeter(polygon: &Polygon) -> f64 {
let mut perimeter = compute_linestring_length(&polygon.exterior);
for interior in &polygon.interiors {
perimeter += compute_linestring_length(interior);
}
perimeter
}
fn compute_linestring_length(linestring: &LineString) -> f64 {
let coords = &linestring.coords;
let mut length = 0.0;
for i in 0..coords.len().saturating_sub(1) {
let dx = coords[i + 1].x - coords[i].x;
let dy = coords[i + 1].y - coords[i].y;
length += (dx * dx + dy * dy).sqrt();
}
length
}
pub fn erase_geometries(
target_poly: &Polygon,
erase_polys: &[Polygon],
options: &EraseOptions,
) -> Result<Vec<Polygon>> {
let mut current_targets = vec![target_poly.clone()];
for erase_poly in erase_polys {
let mut new_targets = Vec::new();
for target in ¤t_targets {
let erased = erase_polygon(target, erase_poly, options)?;
new_targets.extend(erased);
}
current_targets = new_targets;
if current_targets.is_empty() {
break;
}
}
Ok(current_targets)
}
pub fn erase_multipolygon(
target_multi: &MultiPolygon,
erase_multi: &MultiPolygon,
options: &EraseOptions,
) -> Result<Vec<Polygon>> {
let mut result_polygons = Vec::new();
for target_poly in &target_multi.polygons {
let erase_polys: Vec<Polygon> = erase_multi.polygons.to_vec();
let erased = erase_geometries(target_poly, &erase_polys, options)?;
result_polygons.extend(erased);
}
Ok(result_polygons)
}
pub fn erase_polygon_batch(
pairs: &[(Polygon, Polygon)],
options: &EraseOptions,
) -> Result<Vec<Vec<Polygon>>> {
pairs
.iter()
.map(|(target, erase)| erase_polygon(target, erase, options))
.collect()
}
pub fn erase_with_buffer(
target_poly: &Polygon,
erase_poly: &Polygon,
buffer_distance: f64,
tolerance: f64,
) -> Result<Vec<Polygon>> {
let options = EraseOptions {
tolerance,
buffer_erase_geom: true,
buffer_distance,
min_area: 0.0,
remove_slivers: false,
sliver_threshold: 0.1,
};
erase_polygon(target_poly, erase_poly, &options)
}
fn create_polygon_with_hole(
outer_coords: Vec<Coordinate>,
inner_coords: Vec<Coordinate>,
) -> Result<Polygon> {
let exterior = LineString::new(outer_coords)
.map_err(|e| AlgorithmError::InvalidGeometry(format!("Invalid exterior: {}", e)))?;
let interior = LineString::new(inner_coords)
.map_err(|e| AlgorithmError::InvalidGeometry(format!("Invalid interior: {}", e)))?;
Polygon::new(exterior, vec![interior])
.map_err(|e| AlgorithmError::InvalidGeometry(format!("Invalid polygon: {}", e)))
}
#[cfg(test)]
mod tests {
use super::*;
fn create_square(x: f64, y: f64, size: f64) -> 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).expect("Failed to create linestring");
Polygon::new(exterior, vec![]).expect("Failed to create polygon")
}
#[test]
fn test_erase_basic() {
let target = create_square(0.0, 0.0, 10.0);
let erase = create_square(2.0, 2.0, 3.0);
let result = erase_polygon(&target, &erase, &EraseOptions::default());
assert!(result.is_ok());
}
#[test]
fn test_erase_no_overlap() {
let target = create_square(0.0, 0.0, 5.0);
let erase = create_square(10.0, 10.0, 5.0);
let result = erase_polygon(&target, &erase, &EraseOptions::default());
assert!(result.is_ok());
let polygons = result.expect("Erase failed");
assert_eq!(polygons.len(), 1); }
#[test]
fn test_erase_complete_overlap() {
let target = create_square(0.0, 0.0, 5.0);
let erase = create_square(-1.0, -1.0, 10.0);
let result = erase_polygon(&target, &erase, &EraseOptions::default());
assert!(result.is_ok());
let polygons = result.expect("Erase failed");
assert!(
polygons.is_empty()
|| polygons
.iter()
.all(|p| { compute_polygon_area(p).is_ok_and(|a| a < 1e-6) })
);
}
#[test]
fn test_erase_multiple() {
let target = create_square(0.0, 0.0, 10.0);
let erase1 = create_square(1.0, 1.0, 2.0);
let erase2 = create_square(5.0, 5.0, 2.0);
let erase_polys = vec![erase1, erase2];
let result = erase_geometries(&target, &erase_polys, &EraseOptions::default());
assert!(result.is_ok());
}
#[test]
fn test_erase_with_min_area() {
let target = create_square(0.0, 0.0, 10.0);
let erase = create_square(2.0, 2.0, 3.0);
let options = EraseOptions {
min_area: 50.0, ..Default::default()
};
let result = erase_polygon(&target, &erase, &options);
assert!(result.is_ok());
let polygons = result.expect("Erase failed");
for poly in &polygons {
let area = compute_polygon_area(poly).expect("Failed to compute area");
assert!(area >= 50.0);
}
}
#[test]
fn test_is_sliver() {
let sliver = create_square(0.0, 0.0, 10.0); let is_sliver_result = is_sliver(&sliver, 0.5);
assert!(!is_sliver_result);
}
#[test]
fn test_compute_polygon_perimeter() {
let square = create_square(0.0, 0.0, 10.0);
let perimeter = compute_polygon_perimeter(&square);
assert!((perimeter - 40.0).abs() < 1e-6);
}
#[test]
fn test_erase_batch() {
let target1 = create_square(0.0, 0.0, 10.0);
let erase1 = create_square(2.0, 2.0, 3.0);
let target2 = create_square(20.0, 20.0, 10.0);
let erase2 = create_square(22.0, 22.0, 3.0);
let pairs = vec![(target1, erase1), (target2, erase2)];
let result = erase_polygon_batch(&pairs, &EraseOptions::default());
assert!(result.is_ok());
let results = result.expect("Batch erase failed");
assert_eq!(results.len(), 2);
}
#[test]
fn test_erase_with_buffer() {
let target = create_square(0.0, 0.0, 10.0);
let erase = create_square(4.0, 4.0, 2.0);
let result = erase_with_buffer(&target, &erase, 1.0, 1e-10);
assert!(result.is_ok());
}
#[test]
fn test_compute_linestring_length() {
let coords = vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(3.0, 0.0),
Coordinate::new_2d(3.0, 4.0),
];
let linestring = LineString::new(coords).expect("Failed to create linestring");
let length = compute_linestring_length(&linestring);
assert!((length - 7.0).abs() < 1e-6);
}
}