zerometry/
zulti_points.rs1use core::fmt;
2use std::io::{self, Write};
3
4use geo_types::MultiPoint;
5
6use crate::{
7 BoundingBox, COORD_SIZE_IN_BYTES, Coords, InputRelation, OutputRelation, RelationBetweenShapes,
8 Zerometry, Zoint, Zollection, Zolygon, ZultiPolygons, zine::Zine, zulti_lines::ZultiLines,
9};
10
11#[derive(Clone, Copy)]
13pub struct ZultiPoints<'a> {
14 bounding_box: &'a BoundingBox,
15 coords: &'a Coords,
16}
17
18impl<'a> ZultiPoints<'a> {
19 pub fn new(bounding_box: &'a BoundingBox, coords: &'a Coords) -> Self {
21 Self {
22 bounding_box,
23 coords,
24 }
25 }
26
27 #[inline]
30 pub unsafe fn from_bytes(data: &'a [u8]) -> Self {
31 let bounding_box = unsafe { BoundingBox::from_bytes(&data[0..COORD_SIZE_IN_BYTES * 2]) };
32 let coords = unsafe { Coords::from_bytes(&data[COORD_SIZE_IN_BYTES * 2..]) };
33 Self::new(bounding_box, coords)
34 }
35
36 pub fn write_from_geometry(
38 writer: &mut impl Write,
39 geometry: &MultiPoint<f64>,
40 ) -> Result<(), io::Error> {
41 BoundingBox::write_from_geometry(writer, geometry.iter().copied())?;
42 for point in geometry.iter() {
43 writer.write_all(&point.x().to_ne_bytes())?;
44 writer.write_all(&point.y().to_ne_bytes())?;
45 }
46 Ok(())
47 }
48
49 #[inline]
51 pub fn bounding_box(&self) -> &'a BoundingBox {
52 self.bounding_box
53 }
54
55 #[inline]
57 pub fn len(&self) -> usize {
58 self.coords.len()
59 }
60
61 #[inline]
63 pub fn is_empty(&self) -> bool {
64 self.len() == 0
65 }
66
67 #[inline]
68 pub(crate) fn coords(&self) -> &'a Coords {
69 self.coords
70 }
71
72 #[inline]
74 pub fn get(&self, index: usize) -> Option<Zoint<'a>> {
75 if index > self.coords.len() {
76 None
77 } else {
78 let coord = &self.coords()[index];
79 Some(Zoint::new(coord))
80 }
81 }
82
83 #[inline]
85 pub fn points(&'a self) -> impl Iterator<Item = Zoint<'a>> {
86 (0..self.len()).map(move |index| self.get(index).unwrap())
87 }
88
89 pub fn to_geo(&self) -> geo_types::MultiPoint<f64> {
91 geo_types::MultiPoint::new(
92 self.coords
93 .iter()
94 .map(|coord| geo_types::Point::new(coord.lng(), coord.lat()))
95 .collect(),
96 )
97 }
98}
99
100impl<'a> fmt::Debug for ZultiPoints<'a> {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("ZultiPoints")
103 .field("bounding_box", &self.bounding_box)
104 .field(
105 "points",
106 &self.coords.iter().map(Zoint::new).collect::<Vec<_>>(),
107 )
108 .finish()
109 }
110}
111
112impl<'a> RelationBetweenShapes<ZultiPoints<'a>> for ZultiPoints<'a> {
114 fn relation(&self, _other: &ZultiPoints<'a>, relation: InputRelation) -> OutputRelation {
115 relation.to_false().make_disjoint_if_set()
116 }
117}
118
119impl<'a> RelationBetweenShapes<Zoint<'a>> for ZultiPoints<'a> {
121 fn relation(&self, _other: &Zoint<'a>, relation: InputRelation) -> OutputRelation {
122 relation.to_false().make_disjoint_if_set()
123 }
124}
125
126impl<'a> RelationBetweenShapes<Zine<'a>> for ZultiPoints<'a> {
128 fn relation(&self, _other: &Zine<'a>, relation: InputRelation) -> OutputRelation {
129 relation.to_false().make_disjoint_if_set()
130 }
131}
132
133impl<'a> RelationBetweenShapes<ZultiLines<'a>> for ZultiPoints<'a> {
135 fn relation(&self, _other: &ZultiLines<'a>, relation: InputRelation) -> OutputRelation {
136 relation.to_false().make_disjoint_if_set()
137 }
138}
139
140impl<'a> RelationBetweenShapes<Zolygon<'a>> for ZultiPoints<'a> {
141 fn relation(&self, other: &Zolygon<'a>, relation: InputRelation) -> OutputRelation {
142 other
143 .relation(self, relation.swap_contains_relation())
144 .swap_contains_relation()
145 }
146}
147
148impl<'a> RelationBetweenShapes<ZultiPolygons<'a>> for ZultiPoints<'a> {
149 fn relation(&self, other: &ZultiPolygons<'a>, relation: InputRelation) -> OutputRelation {
150 other
151 .relation(self, relation.swap_contains_relation())
152 .swap_contains_relation()
153 }
154}
155
156impl<'a> RelationBetweenShapes<Zollection<'a>> for ZultiPoints<'a> {
157 fn relation(&self, other: &Zollection<'a>, relation: InputRelation) -> OutputRelation {
158 other
159 .relation(self, relation.swap_contains_relation())
160 .swap_contains_relation()
161 }
162}
163
164impl<'a> RelationBetweenShapes<Zerometry<'a>> for ZultiPoints<'a> {
165 fn relation(&self, other: &Zerometry<'a>, relation: InputRelation) -> OutputRelation {
166 other
167 .relation(self, relation.swap_contains_relation())
168 .swap_contains_relation()
169 }
170}
171
172impl<'a> PartialEq<MultiPoint<f64>> for ZultiPoints<'a> {
173 fn eq(&self, other: &MultiPoint<f64>) -> bool {
174 self.coords
175 .iter()
176 .zip(other.iter())
177 .all(|(a, b)| a.lng() == b.x() && a.lat() == b.y())
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use bytemuck::cast_slice;
184 use geo_types::Point;
185 use insta::assert_compact_debug_snapshot;
186
187 use super::*;
188
189 #[test]
190 fn test_zulti_points_binary_format() {
191 let mut buffer = Vec::new();
192 ZultiPoints::write_from_geometry(
193 &mut buffer,
194 &MultiPoint::from(vec![Point::new(1.0, 2.0), Point::new(3.0, 4.0)]),
195 )
196 .unwrap();
197 let input: &[f64] = cast_slice(&buffer);
198 assert_compact_debug_snapshot!(input, @"[1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0]");
199 let zulti_points = unsafe { ZultiPoints::from_bytes(&buffer) };
200 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 } }");
201 assert_compact_debug_snapshot!(zulti_points.coords(), @"[Coord { x: 1.0, y: 2.0 }, Coord { x: 3.0, y: 4.0 }]");
202 }
203
204 proptest::proptest! {
206 #[test]
207 fn test_zulti_points_round_trip(points: Vec<(f64, f64)>) {
208 let multi_point = MultiPoint::from(points);
209 let mut buffer = Vec::new();
210 ZultiPoints::write_from_geometry(&mut buffer, &multi_point).unwrap();
211 let zulti_points = unsafe { ZultiPoints::from_bytes(&buffer) };
212 assert_eq!(zulti_points, multi_point);
213 }
214 }
215}