use core::fmt;
use std::io::{self, Write};
use geo_types::MultiPoint;
use crate::{
BoundingBox, COORD_SIZE_IN_BYTES, Coords, InputRelation, OutputRelation, RelationBetweenShapes,
Zerometry, Zoint, Zollection, Zolygon, ZultiPolygons, zine::Zine, zulti_lines::ZultiLines,
};
#[derive(Clone, Copy)]
pub struct ZultiPoints<'a> {
bounding_box: &'a BoundingBox,
coords: &'a Coords,
}
impl<'a> ZultiPoints<'a> {
pub fn new(bounding_box: &'a BoundingBox, coords: &'a Coords) -> Self {
Self {
bounding_box,
coords,
}
}
#[inline]
pub unsafe fn from_bytes(data: &'a [u8]) -> Self {
let bounding_box = unsafe { BoundingBox::from_bytes(&data[0..COORD_SIZE_IN_BYTES * 2]) };
let coords = unsafe { Coords::from_bytes(&data[COORD_SIZE_IN_BYTES * 2..]) };
Self::new(bounding_box, coords)
}
pub fn write_from_geometry(
writer: &mut impl Write,
geometry: &MultiPoint<f64>,
) -> Result<(), io::Error> {
BoundingBox::write_from_geometry(writer, geometry.iter().copied())?;
for point in geometry.iter() {
writer.write_all(&point.x().to_ne_bytes())?;
writer.write_all(&point.y().to_ne_bytes())?;
}
Ok(())
}
#[inline]
pub fn bounding_box(&self) -> &'a BoundingBox {
self.bounding_box
}
#[inline]
pub fn len(&self) -> usize {
self.coords.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub(crate) fn coords(&self) -> &'a Coords {
self.coords
}
#[inline]
pub fn get(&self, index: usize) -> Option<Zoint<'a>> {
if index > self.coords.len() {
None
} else {
let coord = &self.coords()[index];
Some(Zoint::new(coord))
}
}
#[inline]
pub fn points(&'a self) -> impl Iterator<Item = Zoint<'a>> {
(0..self.len()).map(move |index| self.get(index).unwrap())
}
pub fn to_geo(&self) -> geo_types::MultiPoint<f64> {
geo_types::MultiPoint::new(
self.coords
.iter()
.map(|coord| geo_types::Point::new(coord.lng(), coord.lat()))
.collect(),
)
}
}
impl<'a> fmt::Debug for ZultiPoints<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZultiPoints")
.field("bounding_box", &self.bounding_box)
.field(
"points",
&self.coords.iter().map(Zoint::new).collect::<Vec<_>>(),
)
.finish()
}
}
impl<'a> RelationBetweenShapes<ZultiPoints<'a>> for ZultiPoints<'a> {
fn relation(&self, _other: &ZultiPoints<'a>, relation: InputRelation) -> OutputRelation {
relation.to_false().make_disjoint_if_set()
}
}
impl<'a> RelationBetweenShapes<Zoint<'a>> for ZultiPoints<'a> {
fn relation(&self, _other: &Zoint<'a>, relation: InputRelation) -> OutputRelation {
relation.to_false().make_disjoint_if_set()
}
}
impl<'a> RelationBetweenShapes<Zine<'a>> for ZultiPoints<'a> {
fn relation(&self, _other: &Zine<'a>, relation: InputRelation) -> OutputRelation {
relation.to_false().make_disjoint_if_set()
}
}
impl<'a> RelationBetweenShapes<ZultiLines<'a>> for ZultiPoints<'a> {
fn relation(&self, _other: &ZultiLines<'a>, relation: InputRelation) -> OutputRelation {
relation.to_false().make_disjoint_if_set()
}
}
impl<'a> RelationBetweenShapes<Zolygon<'a>> for ZultiPoints<'a> {
fn relation(&self, other: &Zolygon<'a>, relation: InputRelation) -> OutputRelation {
other
.relation(self, relation.swap_contains_relation())
.swap_contains_relation()
}
}
impl<'a> RelationBetweenShapes<ZultiPolygons<'a>> for ZultiPoints<'a> {
fn relation(&self, other: &ZultiPolygons<'a>, relation: InputRelation) -> OutputRelation {
other
.relation(self, relation.swap_contains_relation())
.swap_contains_relation()
}
}
impl<'a> RelationBetweenShapes<Zollection<'a>> for ZultiPoints<'a> {
fn relation(&self, other: &Zollection<'a>, relation: InputRelation) -> OutputRelation {
other
.relation(self, relation.swap_contains_relation())
.swap_contains_relation()
}
}
impl<'a> RelationBetweenShapes<Zerometry<'a>> for ZultiPoints<'a> {
fn relation(&self, other: &Zerometry<'a>, relation: InputRelation) -> OutputRelation {
other
.relation(self, relation.swap_contains_relation())
.swap_contains_relation()
}
}
impl<'a> PartialEq<MultiPoint<f64>> for ZultiPoints<'a> {
fn eq(&self, other: &MultiPoint<f64>) -> bool {
self.coords
.iter()
.zip(other.iter())
.all(|(a, b)| a.lng() == b.x() && a.lat() == b.y())
}
}
#[cfg(test)]
mod tests {
use bytemuck::cast_slice;
use geo_types::Point;
use insta::assert_compact_debug_snapshot;
use super::*;
#[test]
fn test_zulti_points_binary_format() {
let mut buffer = Vec::new();
ZultiPoints::write_from_geometry(
&mut buffer,
&MultiPoint::from(vec![Point::new(1.0, 2.0), Point::new(3.0, 4.0)]),
)
.unwrap();
let input: &[f64] = cast_slice(&buffer);
assert_compact_debug_snapshot!(input, @"[1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0]");
let zulti_points = unsafe { ZultiPoints::from_bytes(&buffer) };
assert_compact_debug_snapshot!(zulti_points.bounding_box(), @"BoundingBox { bottom_left: Coord { x: 1.0, y: 2.0 }, top_right: Coord { x: 3.0, y: 4.0 } }");
assert_compact_debug_snapshot!(zulti_points.coords(), @"[Coord { x: 1.0, y: 2.0 }, Coord { x: 3.0, y: 4.0 }]");
}
proptest::proptest! {
#[test]
fn test_zulti_points_round_trip(points: Vec<(f64, f64)>) {
let multi_point = MultiPoint::from(points);
let mut buffer = Vec::new();
ZultiPoints::write_from_geometry(&mut buffer, &multi_point).unwrap();
let zulti_points = unsafe { ZultiPoints::from_bytes(&buffer) };
assert_eq!(zulti_points, multi_point);
}
}
}