geo 0.23.1

Geospatial primitives and algorithms
Documentation
use geo_types::{MultiLineString, MultiPolygon};
use std::fmt::Debug;

use super::*;
use crate::{sweep::LineOrPoint, GeoFloat, OpType};

pub trait Spec<T: GeoFloat> {
    type Region: Copy + Debug;
    type Output;

    fn infinity(&self) -> Self::Region;
    fn cross(&self, prev_region: Self::Region, idx: usize) -> Self::Region;
    fn output(&mut self, regions: [Self::Region; 2], geom: LineOrPoint<T>, idx: usize);
    fn finish(self) -> Self::Output;
}

pub struct BoolOp<T: GeoFloat> {
    ty: OpType,
    assembly: RegionAssembly<T>,
}
impl<T: GeoFloat> From<OpType> for BoolOp<T> {
    fn from(ty: OpType) -> Self {
        Self {
            ty,
            assembly: RegionAssembly::default(),
        }
    }
}
impl<T: GeoFloat> Spec<T> for BoolOp<T> {
    type Region = Region;
    type Output = MultiPolygon<T>;

    fn infinity(&self) -> Self::Region {
        Region {
            is_first: false,
            is_second: matches!(self.ty, OpType::Difference),
        }
    }

    fn cross(&self, mut prev_region: Self::Region, idx: usize) -> Self::Region {
        prev_region.cross(idx == 0);
        prev_region
    }

    fn output(&mut self, regions: [Self::Region; 2], geom: LineOrPoint<T>, _idx: usize) {
        if regions[0].is_ty(self.ty) ^ regions[1].is_ty(self.ty) {
            self.assembly.add_edge(geom)
        }
    }

    fn finish(self) -> Self::Output {
        self.assembly.finish()
    }
}

pub struct ClipOp<T: GeoFloat> {
    invert: bool,
    assembly: LineAssembly<T>,
}

impl<T: GeoFloat> ClipOp<T> {
    pub fn new(invert: bool) -> Self {
        Self {
            invert,
            assembly: Default::default(),
        }
    }
}

impl<T: GeoFloat> Spec<T> for ClipOp<T> {
    type Region = Region;
    type Output = MultiLineString<T>;

    fn infinity(&self) -> Self::Region {
        Region {
            is_first: false,
            is_second: false,
        }
    }

    fn cross(&self, mut prev_region: Self::Region, idx: usize) -> Self::Region {
        if idx == 0 {
            prev_region.cross(true);
        }
        prev_region
    }

    fn output(&mut self, regions: [Self::Region; 2], geom: LineOrPoint<T>, idx: usize) {
        if idx > 0 && (regions[0].is_first && regions[1].is_first) != self.invert {
            self.assembly.add_edge(geom, idx);
        }
    }

    fn finish(self) -> Self::Output {
        MultiLineString::new(self.assembly.finish())
    }
}

#[derive(Clone, Copy)]
pub struct Region {
    is_first: bool,
    is_second: bool,
}
impl std::fmt::Debug for Region {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "[{f}{s}]",
            f = if self.is_first { "A" } else { "" },
            s = if self.is_second { "B" } else { "" },
        )
    }
}

impl Region {
    fn cross(&mut self, first: bool) {
        if first {
            self.is_first = !self.is_first;
        } else {
            self.is_second = !self.is_second;
        }
    }
    fn is_ty(&self, ty: OpType) -> bool {
        match ty {
            OpType::Intersection | OpType::Difference => self.is_first && self.is_second,
            OpType::Union => self.is_first || self.is_second,
            OpType::Xor => self.is_first ^ self.is_second,
        }
    }
}