use geometry_model::{
Box, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Polygon, Ring, Segment,
};
use geometry_trait::{Point as PointTrait, PointMut};
#[must_use]
pub fn convert<Src, Dst>(src: &Src) -> Dst
where
Src: Convert<Dst>,
{
src.convert()
}
#[doc(hidden)]
pub trait Convert<Dst> {
fn convert(&self) -> Dst;
}
impl<P, const CW: bool, const CL: bool> Convert<Polygon<P, CW, CL>> for Box<P>
where
P: PointMut + Default,
{
fn convert(&self) -> Polygon<P, CW, CL> {
let min_x = self.min().get::<0>();
let min_y = self.min().get::<1>();
let max_x = self.max().get::<0>();
let max_y = self.max().get::<1>();
let corner = |x: P::Scalar, y: P::Scalar| {
let mut p = P::default();
p.set::<0>(x);
p.set::<1>(y);
p
};
let mut ring = if CW {
alloc::vec![
corner(min_x, min_y),
corner(min_x, max_y),
corner(max_x, max_y),
corner(max_x, min_y),
]
} else {
alloc::vec![
corner(min_x, min_y),
corner(max_x, min_y),
corner(max_x, max_y),
corner(min_x, max_y),
]
};
if CL {
ring.push(corner(min_x, min_y));
}
Polygon::new(Ring::<P, CW, CL>::from_vec(ring))
}
}
impl<P, const CW: bool, const CL: bool> Convert<Polygon<P, CW, CL>> for Ring<P, CW, CL>
where
P: PointTrait + Copy,
{
fn convert(&self) -> Polygon<P, CW, CL> {
Polygon::new(Ring::from_vec(self.0.clone()))
}
}
impl<P, const CW: bool, const CL: bool> Convert<Linestring<P>> for Ring<P, CW, CL>
where
P: PointTrait + Copy,
{
fn convert(&self) -> Linestring<P> {
Linestring(self.0.clone())
}
}
impl<P> Convert<Linestring<P>> for Segment<P>
where
P: PointTrait + Copy,
{
fn convert(&self) -> Linestring<P> {
Linestring(alloc::vec![*self.start(), *self.end()])
}
}
impl<P> Convert<MultiPoint<P>> for P
where
P: PointTrait + Copy,
{
fn convert(&self) -> MultiPoint<P> {
MultiPoint(alloc::vec![*self])
}
}
impl<P> Convert<MultiLinestring<Linestring<P>>> for Linestring<P>
where
P: PointTrait + Copy,
{
fn convert(&self) -> MultiLinestring<Linestring<P>> {
MultiLinestring(alloc::vec![self.clone()])
}
}
impl<P, const CW: bool, const CL: bool> Convert<MultiPolygon<Polygon<P, CW, CL>>>
for Polygon<P, CW, CL>
where
P: PointTrait + Copy,
{
fn convert(&self) -> MultiPolygon<Polygon<P, CW, CL>> {
MultiPolygon(alloc::vec![self.clone()])
}
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
reason = "Converted corner coordinates are exact literals."
)]
mod tests {
use super::convert;
use geometry_cs::Cartesian;
use geometry_model::{Box, Linestring, MultiPoint, Point2D, Polygon, Segment};
use geometry_trait::{Point as _, Polygon as _, Ring as _};
type Pt = Point2D<f64, Cartesian>;
#[test]
fn box_to_polygon_five_points_and_corners() {
let b: Box<Pt> = Box::from_corners(Pt::new(0., 0.), Pt::new(4., 3.));
let pg: Polygon<Pt> = convert(&b);
let pts: alloc::vec::Vec<(f64, f64)> = pg
.exterior()
.points()
.map(|p| (p.get::<0>(), p.get::<1>()))
.collect();
assert_eq!(pts.len(), 5);
assert_eq!(pts[0], (0., 0.));
assert_eq!(pts[4], (0., 0.));
assert!(pts.contains(&(0., 3.)));
assert!(pts.contains(&(4., 3.)));
assert!(pts.contains(&(4., 0.)));
}
#[test]
fn segment_to_linestring_endpoints() {
let s = Segment::new(Pt::new(0., 0.), Pt::new(3., 4.));
let ls: Linestring<Pt> = convert(&s);
assert_eq!(ls.0.len(), 2);
assert_eq!(ls.0[0].get::<0>(), 0.);
assert_eq!(ls.0[1].get::<0>(), 3.);
}
#[test]
fn point_to_multi_point_single_member() {
let mp: MultiPoint<Pt> = convert(&Pt::new(1., 2.));
assert_eq!(mp.0.len(), 1);
assert_eq!(mp.0[0].get::<0>(), 1.);
}
#[test]
fn ring_to_polygon_becomes_hole_free_exterior() {
use geometry_model::Ring;
let ring: Ring<Pt> = Ring::from_vec(vec![
Pt::new(0., 0.),
Pt::new(1., 0.),
Pt::new(1., 1.),
Pt::new(0., 0.),
]);
let pg: Polygon<Pt> = convert(&ring);
assert_eq!(pg.exterior().points().count(), 4);
assert_eq!(pg.interiors().count(), 0);
let first = pg.exterior().points().next().unwrap();
assert_eq!((first.get::<0>(), first.get::<1>()), (0., 0.));
}
#[test]
fn ring_to_linestring_copies_points() {
use geometry_model::Ring;
let ring: Ring<Pt> =
Ring::from_vec(vec![Pt::new(2., 3.), Pt::new(4., 5.), Pt::new(2., 3.)]);
let ls: Linestring<Pt> = convert(&ring);
assert_eq!(ls.0.len(), 3);
assert_eq!((ls.0[1].get::<0>(), ls.0[1].get::<1>()), (4., 5.));
}
#[test]
fn linestring_to_multi_linestring_single_member() {
use geometry_model::MultiLinestring;
let ls: Linestring<Pt> = Linestring(vec![Pt::new(0., 0.), Pt::new(1., 1.)]);
let mls: MultiLinestring<Linestring<Pt>> = convert(&ls);
assert_eq!(mls.0.len(), 1);
assert_eq!(mls.0[0].0.len(), 2);
}
#[test]
fn polygon_to_multi_polygon_single_member() {
use geometry_model::{MultiPolygon, Ring};
let pg: Polygon<Pt> = Polygon::new(Ring::from_vec(vec![
Pt::new(0., 0.),
Pt::new(1., 0.),
Pt::new(1., 1.),
Pt::new(0., 0.),
]));
let mpg: MultiPolygon<Polygon<Pt>> = convert(&pg);
assert_eq!(mpg.0.len(), 1);
assert_eq!(mpg.0[0].exterior().points().count(), 4);
}
#[test]
fn box_to_polygon_is_wound_clockwise() {
let b: Box<Pt> = Box::from_corners(Pt::new(0., 0.), Pt::new(4., 3.));
let pg: Polygon<Pt> = convert(&b);
let pts: alloc::vec::Vec<(f64, f64)> = pg
.exterior()
.points()
.map(|p| (p.get::<0>(), p.get::<1>()))
.collect();
assert_eq!(
pts,
alloc::vec![(0., 0.), (0., 3.), (4., 3.), (4., 0.), (0., 0.)]
);
assert_eq!(crate::area::ring_area(pg.exterior()), 12.0);
}
#[test]
fn box_to_ccw_polygon_is_wound_counter_clockwise() {
let b: Box<Pt> = Box::from_corners(Pt::new(0., 0.), Pt::new(4., 3.));
let pg: Polygon<Pt, false, true> = convert(&b);
let pts: alloc::vec::Vec<(f64, f64)> = pg
.exterior()
.points()
.map(|p| (p.get::<0>(), p.get::<1>()))
.collect();
assert_eq!(
pts,
alloc::vec![(0., 0.), (4., 0.), (4., 3.), (0., 3.), (0., 0.)]
);
assert_eq!(crate::area::ring_area(pg.exterior()), 12.0);
}
#[test]
fn box_to_open_polygon_has_four_stored_points() {
let b: Box<Pt> = Box::from_corners(Pt::new(0., 0.), Pt::new(4., 3.));
let pg: Polygon<Pt, true, false> = convert(&b);
assert_eq!(pg.exterior().points().count(), 4);
assert_eq!(crate::area::ring_area(pg.exterior()), 12.0);
}
#[test]
fn three_d_box_to_polygon_uses_the_first_two_dimensions() {
use geometry_model::Point3D;
type P3 = Point3D<f64, Cartesian>;
let b: Box<P3> = Box::from_corners(P3::new(0., 0., 0.), P3::new(4., 3., 1.));
let pg: Polygon<P3> = convert(&b);
assert_eq!(pg.exterior().points().count(), 5);
assert!(pg.exterior().points().all(|p| p.get::<2>() == 0.0));
assert_eq!(crate::area::ring_area(pg.exterior()), 12.0);
}
}