1use core::fmt;
2use std::io::{self, Write};
3
4use geo::{LineString, Point};
5
6use crate::{
7 BoundingBox, COORD_SIZE_IN_BYTES, Coords, InputRelation, OutputRelation, RelationBetweenShapes,
8 Segment, Zerometry, Zoint, Zollection, Zolygon, ZultiPoints, ZultiPolygons,
9 zulti_lines::ZultiLines,
10};
11
12#[derive(Clone, Copy)]
14pub struct Zine<'a> {
15 bounding_box: &'a BoundingBox,
16 coords: &'a Coords,
17}
18
19impl<'a> Zine<'a> {
20 pub fn new(bounding_box: &'a BoundingBox, coords: &'a Coords) -> Self {
23 Self {
24 bounding_box,
25 coords,
26 }
27 }
28
29 #[inline]
32 pub unsafe fn from_bytes(data: &'a [u8]) -> Self {
33 let bounding_box = unsafe { BoundingBox::from_bytes(&data[0..COORD_SIZE_IN_BYTES * 2]) };
34 let coords = unsafe { Coords::from_bytes(&data[COORD_SIZE_IN_BYTES * 2..]) };
35 Self::new(bounding_box, coords)
36 }
37
38 pub fn write_from_geometry(
40 writer: &mut impl Write,
41 geometry: &LineString<f64>,
42 ) -> Result<(), io::Error> {
43 BoundingBox::write_from_geometry(
44 writer,
45 geometry.0.iter().map(|coord| Point::new(coord.x, coord.y)),
46 )?;
47 for point in geometry.0.iter() {
48 writer.write_all(&point.x.to_ne_bytes())?;
49 writer.write_all(&point.y.to_ne_bytes())?;
50 }
51 Ok(())
52 }
53
54 #[inline]
56 pub fn bounding_box(&self) -> &'a BoundingBox {
57 self.bounding_box
58 }
59
60 #[inline]
62 pub fn len(&self) -> usize {
63 self.coords.len()
64 }
65
66 #[inline]
68 pub fn is_empty(&self) -> bool {
69 self.len() == 0
70 }
71
72 #[inline]
74 pub fn coords(&self) -> &'a Coords {
75 self.coords
76 }
77
78 #[inline]
80 pub fn segments(&self) -> impl Iterator<Item = Segment<'a>> {
81 self.coords
82 .consecutive_pairs()
83 .map(|coords| unsafe { Segment::from_slice(coords) })
85 }
86
87 pub fn to_geo(self) -> geo_types::LineString<f64> {
89 geo_types::LineString::new(
90 self.coords
91 .iter()
92 .map(|coord| geo_types::Coord {
93 x: coord.lng(),
94 y: coord.lat(),
95 })
96 .collect(),
97 )
98 }
99}
100
101impl<'a> fmt::Debug for Zine<'a> {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 f.debug_struct("Zine")
104 .field("bounding_box", &self.bounding_box)
105 .field(
106 "points",
107 &self.coords.iter().map(Zoint::new).collect::<Vec<_>>(),
108 )
109 .finish()
110 }
111}
112
113impl<'a> RelationBetweenShapes<Zoint<'a>> for Zine<'a> {
115 fn relation(&self, _other: &Zoint<'a>, relation: InputRelation) -> OutputRelation {
116 relation.to_false().make_disjoint_if_set()
117 }
118}
119
120impl<'a> RelationBetweenShapes<ZultiPoints<'a>> for Zine<'a> {
122 fn relation(&self, _other: &ZultiPoints<'a>, relation: InputRelation) -> OutputRelation {
123 relation.to_false().make_disjoint_if_set()
124 }
125}
126
127impl<'a> RelationBetweenShapes<Zine<'a>> for Zine<'a> {
128 fn relation(&self, other: &Zine<'a>, relation: InputRelation) -> OutputRelation {
129 let relation = relation.to_false();
130 if self.is_empty() || other.is_empty() || self.bounding_box().disjoint(other.bounding_box())
131 {
132 return relation.make_disjoint_if_set();
133 }
134
135 for lhs in self.segments() {
136 for rhs in other.segments() {
137 if lhs.intersects(&rhs) {
138 return relation.make_intersect_if_set();
139 }
140 }
141 }
142
143 relation.make_disjoint_if_set()
144 }
145}
146
147impl<'a> RelationBetweenShapes<ZultiLines<'a>> for Zine<'a> {
148 fn relation(&self, other: &ZultiLines<'a>, relation: InputRelation) -> OutputRelation {
149 other.relation(self, relation)
151 }
152}
153
154impl<'a> RelationBetweenShapes<Zolygon<'a>> for Zine<'a> {
155 fn relation(&self, other: &Zolygon<'a>, relation: InputRelation) -> OutputRelation {
156 if self.is_empty() || other.is_empty() || self.bounding_box().disjoint(other.bounding_box())
157 {
158 return relation.to_false().make_disjoint_if_set();
159 }
160
161 for segment in self.segments() {
164 for other_segment in other.segments() {
165 if segment.intersects(&other_segment) {
166 return relation.to_false().make_intersect_if_set();
167 }
168 }
169 }
170
171 let any = self.coords().iter().next().unwrap();
175 if other.contains(any) {
176 return relation.to_false().make_strict_contained_if_set();
177 }
178
179 relation.to_false().make_disjoint_if_set()
180 }
181}
182
183impl<'a> RelationBetweenShapes<ZultiPolygons<'a>> for Zine<'a> {
184 fn relation(&self, other: &ZultiPolygons<'a>, relation: InputRelation) -> OutputRelation {
185 let mut output = relation.to_false();
186 if self.is_empty() || other.is_empty() || self.bounding_box().disjoint(other.bounding_box())
187 {
188 return output.make_disjoint_if_set();
189 }
190
191 for polygon in other.polygons() {
192 output |= self.relation(&polygon, relation.strip_disjoint());
193
194 if output.any_relation() && relation.early_exit {
195 return output;
196 }
197 }
198
199 if output.any_relation() {
200 output
201 } else {
202 output.make_disjoint_if_set()
203 }
204 }
205}
206
207impl<'a> RelationBetweenShapes<Zollection<'a>> for Zine<'a> {
208 fn relation(&self, other: &Zollection<'a>, relation: InputRelation) -> OutputRelation {
209 other
210 .relation(self, relation.swap_contains_relation())
211 .swap_contains_relation()
212 }
213}
214
215impl<'a> RelationBetweenShapes<Zerometry<'a>> for Zine<'a> {
216 fn relation(&self, other: &Zerometry<'a>, relation: InputRelation) -> OutputRelation {
217 match other {
218 Zerometry::Point(zoint) => self.relation(zoint, relation),
219 Zerometry::MultiPoints(zulti_points) => self.relation(zulti_points, relation),
220 Zerometry::Line(zine) => self.relation(zine, relation),
221 Zerometry::MultiLines(zulti_lines) => self.relation(zulti_lines, relation),
222 Zerometry::Polygon(zolygon) => self.relation(zolygon, relation),
223 Zerometry::MultiPolygon(zulti_polygon) => self.relation(zulti_polygon, relation),
224 Zerometry::Collection(zollection) => self.relation(zollection, relation),
225 }
226 }
227}
228
229impl<'a> PartialEq<LineString<f64>> for Zine<'a> {
230 fn eq(&self, other: &LineString<f64>) -> bool {
231 self.coords
232 .iter()
233 .zip(other.0.iter())
234 .all(|(a, b)| a.lng() == b.x && a.lat() == b.y)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use bytemuck::cast_slice;
241 use geo::{MultiPolygon, coord, polygon};
242 use geo_types::Point;
243 use insta::assert_compact_debug_snapshot;
244
245 use super::*;
246
247 #[test]
248 fn test_zine_binary_format() {
249 let mut buffer = Vec::new();
250 Zine::write_from_geometry(
251 &mut buffer,
252 &LineString::from(vec![Point::new(1.0, 2.0), Point::new(3.0, 4.0)]),
253 )
254 .unwrap();
255 let input: &[f64] = cast_slice(&buffer);
256 assert_compact_debug_snapshot!(input, @"[1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0, 4.0]");
257 let zulti_points = unsafe { Zine::from_bytes(&buffer) };
258 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 } }");
259 assert_compact_debug_snapshot!(zulti_points.coords(), @"[Coord { x: 1.0, y: 2.0 }, Coord { x: 3.0, y: 4.0 }]");
260 }
261
262 #[test]
263 fn test_zine_in_polygon() {
264 let line = LineString::new(vec![
265 coord! { x: 0.4, y: 0.4},
266 coord! { x: 0.6, y: 0.4},
267 coord! { x: 0.6, y: 0.6},
268 coord! { x: 0.4, y: 0.6},
269 ]);
270 let polygon = polygon![
271 (x: 0., y: 0.),
272 (x: 1., y: 0.),
273 (x: 1., y: 1.),
274 (x: 0., y: 1.),
275 ];
276
277 let mut buf = Vec::new();
278 Zine::write_from_geometry(&mut buf, &line).unwrap();
279 let zine = unsafe { Zine::from_bytes(&buf) };
280
281 let mut buf = Vec::new();
282 Zolygon::write_from_geometry(&mut buf, &polygon).unwrap();
283 let zolygon = unsafe { Zolygon::from_bytes(&buf) };
284
285 assert_compact_debug_snapshot!(zine.all_relation(&zolygon), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(true), intersect: Some(false), disjoint: Some(false) }");
286 }
287
288 #[test]
289 fn test_zine_and_multipolygon() {
290 let line = LineString::new(vec![
291 coord! { x: 0.4, y: 0.4},
292 coord! { x: 0.6, y: 0.4},
293 coord! { x: 0.6, y: 0.6},
294 coord! { x: 0.4, y: 0.6},
295 ]);
296 let inside = polygon![
297 (x: 0., y: 0.),
298 (x: 1., y: 0.),
299 (x: 1., y: 1.),
300 (x: 0., y: 1.),
301 ];
302 let outside = polygon![
303 (x: 5., y: 5.),
304 (x: 6., y: 5.),
305 (x: 6., y: 6.),
306 (x: 5., y: 6.),
307 ];
308 let intersect = polygon![
309 (x: 0.5, y: 0.5),
310 (x: 0.6, y: 0.5),
311 (x: 0.6, y: 0.6),
312 (x: 0.5, y: 0.6),
313 ];
314 let multi_polygons_inside = MultiPolygon::new(vec![inside.clone()]);
315 let multi_polygons_outside = MultiPolygon::new(vec![outside.clone()]);
316 let multi_polygons_intersect = MultiPolygon::new(vec![intersect.clone()]);
317 let multi_polygons_in_and_out = MultiPolygon::new(vec![inside.clone(), outside.clone()]);
318 let multi_polygons_all =
319 MultiPolygon::new(vec![inside.clone(), outside.clone(), intersect.clone()]);
320
321 let mut buf = Vec::new();
322 Zine::write_from_geometry(&mut buf, &line).unwrap();
323 let zine = unsafe { Zine::from_bytes(&buf) };
324
325 let mut buf = Vec::new();
326 ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_inside).unwrap();
327 let inside = unsafe { ZultiPolygons::from_bytes(&buf) };
328 assert_compact_debug_snapshot!(zine.all_relation(&inside ), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(true), intersect: Some(false), disjoint: Some(false) }");
329
330 let mut buf = Vec::new();
331 ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_outside).unwrap();
332 let multi_polygons_outside = unsafe { ZultiPolygons::from_bytes(&buf) };
333 assert_compact_debug_snapshot!(zine.all_relation(&multi_polygons_outside), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(false), strict_contained: Some(false), intersect: Some(false), disjoint: Some(true) }");
334
335 let mut buf = Vec::new();
336 ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_intersect).unwrap();
337 let multi_polygons_intersect = unsafe { ZultiPolygons::from_bytes(&buf) };
338 assert_compact_debug_snapshot!(zine.all_relation(&multi_polygons_intersect), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(false), strict_contained: Some(false), intersect: Some(true), disjoint: Some(false) }");
339
340 let mut buf = Vec::new();
341 ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_in_and_out).unwrap();
342 let multi_polygons_in_and_out = unsafe { ZultiPolygons::from_bytes(&buf) };
343 assert_compact_debug_snapshot!(zine.all_relation(&multi_polygons_in_and_out), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(true), intersect: Some(false), disjoint: Some(false) }");
344
345 let mut buf = Vec::new();
346 ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_all).unwrap();
347 let multi_polygons_all = unsafe { ZultiPolygons::from_bytes(&buf) };
348 assert_compact_debug_snapshot!(zine.all_relation(&multi_polygons_all), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(true), intersect: Some(true), disjoint: Some(false) }");
349 assert_compact_debug_snapshot!(zine.any_relation(&multi_polygons_all), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(true), intersect: Some(false), disjoint: Some(false) }");
350 }
351
352 proptest::proptest! {
354 #[test]
355 fn test_zine_points_round_trip(points: Vec<(f64, f64)>) {
356 let multi_point = LineString::from(points);
357 let mut buffer = Vec::new();
358 Zine::write_from_geometry(&mut buffer, &multi_point).unwrap();
359 let zulti_points = unsafe { Zine::from_bytes(&buffer) };
360 assert_eq!(zulti_points, multi_point);
361 }
362 }
363}