use crate::GeoFloat;
use crate::geometry::Coord;
use i_overlay::i_float::float::compatible::FloatPointCompatible;
use i_overlay::i_float::float::number::FloatNumber;
use num_traits::FromPrimitive;
pub trait BoolOpsNum: GeoFloat + FloatNumber + FromPrimitive {}
impl<T: GeoFloat + FloatNumber + FromPrimitive> BoolOpsNum for T {}
#[derive(Copy, Clone, Debug)]
pub struct BoolOpsCoord<T: BoolOpsNum>(pub(crate) Coord<T>);
impl<T: BoolOpsNum> FloatPointCompatible<T> for BoolOpsCoord<T> {
fn from_xy(x: T, y: T) -> Self {
Self(Coord { x, y })
}
fn x(&self) -> T {
self.0.x
}
fn y(&self) -> T {
self.0.y
}
}
pub(crate) mod convert {
use super::super::OpType;
use super::BoolOpsNum;
use crate::bool_ops::i_overlay_integration::BoolOpsCoord;
use crate::geometry::{LineString, MultiLineString, MultiPolygon, Polygon};
use geo_types::Coord;
use i_overlay::core::overlay_rule::OverlayRule;
pub fn line_string_from_path<T: BoolOpsNum>(path: Vec<BoolOpsCoord<T>>) -> LineString<T> {
let coords = path.into_iter().map(|bops_coord| bops_coord.0).collect();
LineString(coords)
}
pub fn multi_line_string_from_paths<T: BoolOpsNum>(
paths: Vec<Vec<BoolOpsCoord<T>>>,
) -> MultiLineString<T> {
let line_strings = paths.into_iter().map(|p| line_string_from_path(p));
MultiLineString(line_strings.collect())
}
pub fn polygon_from_shape<T: BoolOpsNum>(shape: Vec<Vec<BoolOpsCoord<T>>>) -> Polygon<T> {
let mut rings = shape.into_iter().map(|path| {
let mut line_string = line_string_from_path(path);
line_string.close();
line_string
});
let exterior = rings.next().unwrap_or(LineString::empty());
Polygon::new(exterior, rings.collect())
}
pub fn multi_polygon_from_shapes<T: BoolOpsNum>(
shapes: Vec<Vec<Vec<BoolOpsCoord<T>>>>,
) -> MultiPolygon<T> {
let polygons = shapes.into_iter().map(|s| polygon_from_shape(s));
MultiPolygon(polygons.collect())
}
pub fn ring_to_shape_path<T: BoolOpsNum>(line_string: &LineString<T>) -> Vec<BoolOpsCoord<T>> {
if line_string.0.is_empty() {
return vec![];
}
let coords = &line_string.0[..line_string.0.len() - 1];
coords.iter().copied().map(BoolOpsCoord).collect()
}
pub fn line_string_to_shape_path<T: BoolOpsNum>(
line_string: &LineString<T>,
) -> Vec<BoolOpsCoord<T>> {
line_string.coords().copied().map(BoolOpsCoord).collect()
}
impl From<OpType> for OverlayRule {
fn from(op: OpType) -> Self {
match op {
OpType::Intersection => OverlayRule::Intersect,
OpType::Union => OverlayRule::Union,
OpType::Difference => OverlayRule::Difference,
OpType::Xor => OverlayRule::Xor,
}
}
}
impl<T: BoolOpsNum> From<Coord<T>> for BoolOpsCoord<T> {
fn from(value: Coord<T>) -> Self {
BoolOpsCoord(value)
}
}
}
#[cfg(test)]
mod tests {
use geo_types::polygon;
use crate::algorithm::{BooleanOps, Relate, Winding};
use crate::geometry::{MultiPolygon, Polygon};
use crate::winding_order::WindingOrder;
use crate::wkt;
#[test]
fn test_winding_order() {
let poly1 = polygon!((x: 0.0, y: 0.0), (x: 1.0, y: 0.0), (x: 1.0, y: 1.0));
assert!(matches!(
poly1.exterior().winding_order(),
Some(WindingOrder::CounterClockwise)
));
{
let union = poly1.union(&polygon!());
assert_eq!(union.0.len(), 1);
let union = &union[0];
assert!(matches!(
union.exterior().winding_order(),
Some(WindingOrder::CounterClockwise)
));
}
{
let intersection = poly1.intersection(&poly1);
assert_eq!(intersection.0.len(), 1);
let intersection = &intersection[0];
assert!(matches!(
intersection.exterior().winding_order(),
Some(WindingOrder::CounterClockwise)
));
}
let poly2 = polygon!((x: 0.0, y: 0.0), (x: 1.0, y: 1.0), (x: 0.0, y: 1.0));
assert!(matches!(
poly2.exterior().winding_order(),
Some(WindingOrder::CounterClockwise)
));
{
let union = poly1.union(&poly2);
assert_eq!(union.0.len(), 1);
let union = &union[0];
assert!(union.interiors().is_empty());
assert!(matches!(
union.exterior().winding_order(),
Some(WindingOrder::CounterClockwise)
));
}
}
#[test]
fn two_empty_polygons() {
let p1: Polygon = wkt!(POLYGON EMPTY);
let p2: Polygon = wkt!(POLYGON EMPTY);
assert_eq!(&p1.union(&p2), &wkt!(MULTIPOLYGON EMPTY));
assert_eq!(&p1.intersection(&p2), &wkt!(MULTIPOLYGON EMPTY));
}
#[test]
fn one_empty_polygon() {
let p1: Polygon = wkt!(POLYGON((0.0 0.0,1.0 0.0,1.0 1.0,0.0 1.0,0.0 0.0)));
let p2: Polygon = wkt!(POLYGON EMPTY);
{
let unioned = p1.union(&p2);
let expected = MultiPolygon(vec![p1.clone()]);
let im = unioned.relate(&expected);
assert!(im.is_equal_topo());
}
assert_eq!(&p1.intersection(&p2), &wkt!(MULTIPOLYGON EMPTY));
}
}