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