use alloc::vec::Vec;
use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_model::{MultiPolygon, Polygon};
use geometry_tag::SameAs;
use geometry_trait::PointMut;
use crate::operation::{OverlayError, union_poly};
use crate::relate::overlaps;
pub fn merge_polygons<P>(
polygons: Vec<Polygon<P>>,
) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
where
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
let mut work = polygons;
while let Some((i, j)) = first_overlapping_pair(&work) {
let b = work.remove(j); let a = work.remove(i);
let unioned = union_poly(&a, &b)?;
for pg in unioned.0 {
work.push(pg);
}
}
Ok(MultiPolygon(work))
}
pub fn merge_multipolygon<P>(
mp: MultiPolygon<Polygon<P>>,
) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
where
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
merge_polygons(mp.0)
}
fn first_overlapping_pair<P>(polygons: &[Polygon<P>]) -> Option<(usize, usize)>
where
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
for i in 0..polygons.len() {
for j in (i + 1)..polygons.len() {
if overlaps(&polygons[i], &polygons[j]).unwrap_or(false) {
return Some((i, j));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::merge_polygons;
use geometry_algorithm::ring_area;
use geometry_cs::Cartesian;
use geometry_model::{MultiPolygon, Point2D, Polygon, polygon};
use geometry_trait::{MultiPolygon as _, Polygon as _};
type P = Point2D<f64, Cartesian>;
fn square(x: f64, y: f64, s: f64) -> Polygon<P> {
polygon![[(x, y), (x + s, y), (x + s, y + s), (x, y + s), (x, y)]]
}
fn total_area(mp: &MultiPolygon<Polygon<P>>) -> f64 {
mp.polygons().map(|pg| ring_area(pg.exterior()).abs()).sum()
}
fn close(a: f64, b: f64) -> bool {
(a - b).abs() <= 1e-5 * a.abs().max(b.abs()).max(1.0)
}
#[test]
fn overlapping_pair_merges_to_one() {
let a = square(0.0, 0.0, 2.0);
let b = square(1.0, 1.0, 2.0);
let merged = merge_polygons(vec![a, b]).unwrap();
assert_eq!(merged.polygons().count(), 1);
assert!(
close(total_area(&merged), 7.0),
"area {}",
total_area(&merged)
);
}
#[test]
fn disjoint_polygons_stay_separate() {
let a = square(0.0, 0.0, 1.0);
let b = square(5.0, 5.0, 1.0);
let merged = merge_polygons(vec![a, b]).unwrap();
assert_eq!(merged.polygons().count(), 2);
}
#[test]
fn mixed_overlap_and_disjoint() {
let a = square(0.0, 0.0, 2.0);
let b = square(1.0, 1.0, 2.0); let far = square(9.0, 9.0, 1.0); let merged = merge_polygons(vec![a, b, far]).unwrap();
assert_eq!(merged.polygons().count(), 2);
}
#[test]
fn empty_input_empty_output() {
let merged = merge_polygons::<P>(vec![]).unwrap();
assert_eq!(merged.polygons().count(), 0);
}
#[test]
fn touching_only_pair_does_not_abort_the_merge() {
let a = square(0.0, 0.0, 2.0);
let b = square(1.0, 1.0, 2.0); let c = square(10.0, 0.0, 2.0);
let d = square(12.0, 0.0, 2.0); let merged = merge_polygons(vec![a, b, c, d]).unwrap();
assert_eq!(merged.polygons().count(), 3);
}
}