use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_model::Box as ModelBox;
use geometry_tag::{
BoxTag, LinestringTag, MultiLinestringTag, MultiPointTag, MultiPolygonTag, PointTag,
PolygonTag, RingTag, SameAs, SegmentTag,
};
use geometry_trait::{
Box as BoxTrait, IndexedAccess, Linestring as LinestringTrait,
MultiLinestring as MultiLinestringTrait, MultiPoint as MultiPointTrait,
MultiPolygon as MultiPolygonTrait, Point as PointTrait, PointMut, Polygon as PolygonTrait,
Ring as RingTrait, Segment as SegmentTrait, fold_dims,
};
pub trait EnvelopeStrategy<G> {
type Output;
fn envelope(&self, g: &G) -> Self::Output;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopePoint;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopeLinestring;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopeRing;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopePolygon;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopeSegment;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopeBox;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopeMultiPoint;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopeMultiLinestring;
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvelopeMultiPolygon;
#[inline]
pub fn seed_from_point<P>(out: &mut ModelBox<P>, p: &P)
where
P: PointMut,
{
fold_dims((), p, |(), p, d| {
match d {
0 => write_both::<P, 0>(out, p),
1 => write_both::<P, 1>(out, p),
2 => write_both::<P, 2>(out, p),
3 => write_both::<P, 3>(out, p),
_ => unreachable!("fold_dims: dimension out of MAX_DIM range"),
}
});
}
#[inline]
fn write_both<P, const D: usize>(out: &mut ModelBox<P>, p: &P)
where
P: PointMut,
{
let v = p.get::<D>();
out.set_indexed::<0, D>(v);
out.set_indexed::<1, D>(v);
}
#[inline]
pub fn grow_from_point<P>(out: &mut ModelBox<P>, p: &P)
where
P: PointMut,
{
fold_dims((), p, |(), p, d| match d {
0 => grow_one::<P, 0>(out, p),
1 => grow_one::<P, 1>(out, p),
2 => grow_one::<P, 2>(out, p),
3 => grow_one::<P, 3>(out, p),
_ => unreachable!("fold_dims: dimension out of MAX_DIM range"),
});
}
#[inline]
fn grow_one<P, const D: usize>(out: &mut ModelBox<P>, p: &P)
where
P: PointMut,
{
let v = p.get::<D>();
let lo = out.get_indexed::<0, D>();
let hi = out.get_indexed::<1, D>();
if v < lo {
out.set_indexed::<0, D>(v);
}
if v > hi {
out.set_indexed::<1, D>(v);
}
}
#[inline]
pub fn envelope_of_points<'a, P, I>(it: I) -> ModelBox<P>
where
P: PointMut + Default + 'a,
P::Scalar: CoordinateScalar,
I: IntoIterator<Item = &'a P>,
{
let mut out = ModelBox::<P>::default();
let mut it = it.into_iter();
if let Some(first) = it.next() {
seed_from_point(&mut out, first);
for p in it {
grow_from_point(&mut out, p);
}
}
out
}
impl<G> EnvelopeStrategy<G> for EnvelopePoint
where
G: PointTrait + PointMut + Default,
<G::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
let mut out = ModelBox::<G>::default();
seed_from_point(&mut out, g);
out
}
}
impl<G> EnvelopeStrategy<G> for EnvelopeLinestring
where
G: LinestringTrait,
G::Point: PointMut + Default,
<<G::Point as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::Point>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
envelope_of_points::<G::Point, _>(g.points())
}
}
impl<G> EnvelopeStrategy<G> for EnvelopeRing
where
G: RingTrait,
G::Point: PointMut + Default,
<<G::Point as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::Point>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
envelope_of_points::<G::Point, _>(g.points())
}
}
impl<G> EnvelopeStrategy<G> for EnvelopePolygon
where
G: PolygonTrait,
G::Point: PointMut + Default,
<<G::Point as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::Point>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
envelope_of_points::<G::Point, _>(g.exterior().points())
}
}
impl<G> EnvelopeStrategy<G> for EnvelopeSegment
where
G: SegmentTrait,
G::Point: PointMut + Default,
<<G::Point as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::Point>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
let mut out = ModelBox::<G::Point>::default();
seed_from_point(&mut out, &geometry_trait::segment_start(g));
grow_from_point(&mut out, &geometry_trait::segment_end(g));
out
}
}
impl<G> EnvelopeStrategy<G> for EnvelopeBox
where
G: BoxTrait,
G::Point: PointMut + Default,
<<G::Point as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::Point>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
let mut out = ModelBox::<G::Point>::default();
seed_from_point(&mut out, &geometry_trait::box_min(g));
grow_from_point(&mut out, &geometry_trait::box_max(g));
out
}
}
impl<G> EnvelopeStrategy<G> for EnvelopeMultiPoint
where
G: MultiPointTrait,
G::ItemPoint: PointMut + Default,
<<G::ItemPoint as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::ItemPoint>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
envelope_of_points::<G::ItemPoint, _>(g.points())
}
}
impl<G> EnvelopeStrategy<G> for EnvelopeMultiLinestring
where
G: MultiLinestringTrait,
G::Point: PointMut + Default,
<<G::Point as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::Point>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
let mut out = ModelBox::<G::Point>::default();
let mut seeded = false;
for ls in g.linestrings() {
for p in ls.points() {
if seeded {
grow_from_point(&mut out, p);
} else {
seed_from_point(&mut out, p);
seeded = true;
}
}
}
out
}
}
impl<G> EnvelopeStrategy<G> for EnvelopeMultiPolygon
where
G: MultiPolygonTrait,
G::Point: PointMut + Default,
<<G::Point as PointTrait>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Output = ModelBox<G::Point>;
#[inline]
fn envelope(&self, g: &G) -> Self::Output {
let mut out = ModelBox::<G::Point>::default();
let mut seeded = false;
for poly in g.polygons() {
for p in poly.exterior().points() {
if seeded {
grow_from_point(&mut out, p);
} else {
seed_from_point(&mut out, p);
seeded = true;
}
}
}
out
}
}
#[doc(hidden)]
pub trait EnvelopeStrategyForKind {
type S: Default;
}
impl EnvelopeStrategyForKind for PointTag {
type S = EnvelopePoint;
}
impl EnvelopeStrategyForKind for LinestringTag {
type S = EnvelopeLinestring;
}
impl EnvelopeStrategyForKind for RingTag {
type S = EnvelopeRing;
}
impl EnvelopeStrategyForKind for PolygonTag {
type S = EnvelopePolygon;
}
impl EnvelopeStrategyForKind for SegmentTag {
type S = EnvelopeSegment;
}
impl EnvelopeStrategyForKind for BoxTag {
type S = EnvelopeBox;
}
impl EnvelopeStrategyForKind for MultiPointTag {
type S = EnvelopeMultiPoint;
}
impl EnvelopeStrategyForKind for MultiLinestringTag {
type S = EnvelopeMultiLinestring;
}
impl EnvelopeStrategyForKind for MultiPolygonTag {
type S = EnvelopeMultiPolygon;
}
#[cfg(test)]
mod tests {
use super::{
EnvelopeBox, EnvelopeLinestring, EnvelopePoint, EnvelopePolygon, EnvelopeSegment,
EnvelopeStrategy,
};
use geometry_cs::Cartesian;
use geometry_model::{Box, Linestring, Point2D, Polygon, Segment, linestring, polygon};
use geometry_trait::IndexedAccess as _;
type P = Point2D<f64, Cartesian>;
fn assert_2d(b: &Box<P>, xmin: f64, xmax: f64, ymin: f64, ymax: f64) {
assert_eq!(b.get_indexed::<0, 0>().to_bits(), xmin.to_bits());
assert_eq!(b.get_indexed::<0, 1>().to_bits(), ymin.to_bits());
assert_eq!(b.get_indexed::<1, 0>().to_bits(), xmax.to_bits());
assert_eq!(b.get_indexed::<1, 1>().to_bits(), ymax.to_bits());
}
#[test]
fn point_envelope_collapses() {
let p = Point2D::<f64, Cartesian>::new(1.0, 1.0);
assert_2d(&EnvelopePoint.envelope(&p), 1.0, 1.0, 1.0, 1.0);
}
#[test]
fn linestring_two_points() {
let ls: Linestring<P> = linestring![(1.0, 1.0), (2.0, 2.0)];
assert_2d(&EnvelopeLinestring.envelope(&ls), 1.0, 2.0, 1.0, 2.0);
}
#[test]
fn polygon_axis_aligned_square() {
let p: Polygon<P> = polygon![[(1.0, 1.0), (1.0, 3.0), (3.0, 3.0), (3.0, 1.0), (1.0, 1.0),]];
assert_2d(&EnvelopePolygon.envelope(&p), 1.0, 3.0, 1.0, 3.0);
}
#[test]
fn box_envelope_is_self() {
let b = Box::from_corners(
Point2D::<f64, Cartesian>::new(1.0, 1.0),
Point2D::<f64, Cartesian>::new(3.0, 3.0),
);
assert_2d(&EnvelopeBox.envelope(&b), 1.0, 3.0, 1.0, 3.0);
}
#[test]
fn ring_non_convex() {
let p: Polygon<P> = polygon![[(4.0, 1.0), (0.0, 7.0), (7.0, 9.0), (4.0, 1.0)]];
assert_2d(&EnvelopePolygon.envelope(&p), 0.0, 7.0, 1.0, 9.0);
}
#[test]
fn segment_envelope() {
let s = Segment::new(
Point2D::<f64, Cartesian>::new(1.0, 1.0),
Point2D::<f64, Cartesian>::new(3.0, 3.0),
);
assert_2d(&EnvelopeSegment.envelope(&s), 1.0, 3.0, 1.0, 3.0);
}
#[test]
fn segment_envelope_crossed() {
let s = Segment::new(
Point2D::<f64, Cartesian>::new(3.0, 1.0),
Point2D::<f64, Cartesian>::new(1.0, 3.0),
);
assert_2d(&EnvelopeSegment.envelope(&s), 1.0, 3.0, 1.0, 3.0);
}
}