pub(crate) mod i_overlay_integration;
#[cfg(test)]
mod tests;
use i_overlay_integration::BoolOpsCoord;
pub use i_overlay_integration::BoolOpsNum;
use i_overlay_integration::convert::{multi_polygon_from_shapes, ring_to_shape_path};
use crate::geometry::{LineString, MultiLineString, MultiPolygon, Polygon};
use crate::winding_order::{Winding, WindingOrder};
pub use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::clip::FloatClip;
use i_overlay::float::overlay::FloatOverlay;
use i_overlay::float::overlay::OverlayOptions;
use i_overlay::string::clip::ClipRule;
pub trait BooleanOps {
type Scalar: BoolOpsNum;
fn rings(&self) -> impl Iterator<Item = &LineString<Self::Scalar>>;
fn boolean_op(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
op: OpType,
) -> MultiPolygon<Self::Scalar> {
self.boolean_op_with_fill_rule(other, op, FillRule::EvenOdd)
}
fn boolean_op_with_fill_rule(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
op: OpType,
fill_rule: FillRule,
) -> MultiPolygon<Self::Scalar> {
let subject = self.rings().map(ring_to_shape_path).collect::<Vec<_>>();
let clip = other.rings().map(ring_to_shape_path).collect::<Vec<_>>();
let shapes = FloatOverlay::with_subj_and_clip_custom(
&subject,
&clip,
OverlayOptions::ogc(),
Default::default(),
)
.overlay(op.into(), fill_rule);
multi_polygon_from_shapes(shapes)
}
fn intersection(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
) -> MultiPolygon<Self::Scalar> {
self.boolean_op(other, OpType::Intersection)
}
fn intersection_with_fill_rule(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
fill_rule: FillRule,
) -> MultiPolygon<Self::Scalar> {
self.boolean_op_with_fill_rule(other, OpType::Intersection, fill_rule)
}
fn union(&self, other: &impl BooleanOps<Scalar = Self::Scalar>) -> MultiPolygon<Self::Scalar> {
self.boolean_op(other, OpType::Union)
}
fn union_with_fill_rule(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
fill_rule: FillRule,
) -> MultiPolygon<Self::Scalar> {
self.boolean_op_with_fill_rule(other, OpType::Union, fill_rule)
}
fn xor(&self, other: &impl BooleanOps<Scalar = Self::Scalar>) -> MultiPolygon<Self::Scalar> {
self.boolean_op(other, OpType::Xor)
}
fn xor_with_fill_rule(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
fill_rule: FillRule,
) -> MultiPolygon<Self::Scalar> {
self.boolean_op_with_fill_rule(other, OpType::Xor, fill_rule)
}
fn difference(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
) -> MultiPolygon<Self::Scalar> {
self.boolean_op(other, OpType::Difference)
}
fn difference_with_fill_rule(
&self,
other: &impl BooleanOps<Scalar = Self::Scalar>,
fill_rule: FillRule,
) -> MultiPolygon<Self::Scalar> {
self.boolean_op_with_fill_rule(other, OpType::Difference, fill_rule)
}
fn clip(
&self,
multi_line_string: &MultiLineString<Self::Scalar>,
invert: bool,
) -> MultiLineString<Self::Scalar> {
self.clip_with_fill_rule(multi_line_string, invert, FillRule::EvenOdd)
}
fn clip_with_fill_rule(
&self,
multi_line_string: &MultiLineString<Self::Scalar>,
invert: bool,
fill_rule: FillRule,
) -> MultiLineString<Self::Scalar> {
let subject: Vec<Vec<_>> = multi_line_string
.iter()
.map(|line_string| line_string.coords().map(|c| BoolOpsCoord(*c)).collect())
.collect();
let clip = self.rings().map(ring_to_shape_path).collect::<Vec<_>>();
let clip_rule = ClipRule {
invert,
boundary_included: true,
};
let paths = subject.clip_by(&clip, fill_rule, clip_rule);
i_overlay_integration::convert::multi_line_string_from_paths(paths)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum OpType {
Intersection,
Union,
Difference,
Xor,
}
pub fn unary_union<'a, B: BooleanOps + 'a>(
boppables: impl IntoIterator<Item = &'a B>,
) -> MultiPolygon<B::Scalar> {
let mut winding_order: Option<WindingOrder> = None;
let subject = boppables
.into_iter()
.flat_map(|boppable| {
let rings = boppable.rings();
rings
.map(|ring| {
if winding_order.is_none() {
winding_order = ring.winding_order();
}
ring_to_shape_path(ring)
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let fill_rule = if winding_order == Some(WindingOrder::CounterClockwise) {
FillRule::Positive
} else {
FillRule::Negative
};
let shapes =
FloatOverlay::with_subj_custom(&subject, OverlayOptions::ogc(), Default::default())
.overlay(OverlayRule::Subject, fill_rule);
multi_polygon_from_shapes(shapes)
}
impl<T: BoolOpsNum> BooleanOps for Polygon<T> {
type Scalar = T;
fn rings(&self) -> impl Iterator<Item = &LineString<Self::Scalar>> {
std::iter::once(self.exterior()).chain(self.interiors())
}
}
impl<T: BoolOpsNum> BooleanOps for MultiPolygon<T> {
type Scalar = T;
fn rings(&self) -> impl Iterator<Item = &LineString<Self::Scalar>> {
self.iter().flat_map(BooleanOps::rings)
}
}