zerometry/
zulti_lines.rs

1use std::{fmt, io, mem};
2
3use bytemuck::cast_slice;
4use geo_types::{MultiLineString, Point};
5
6use crate::{
7    BoundingBox, InputRelation, OutputRelation, RelationBetweenShapes, Zerometry, Zoint,
8    Zollection, Zolygon, ZultiPoints, ZultiPolygons, bounding_box::BOUNDING_BOX_SIZE_IN_BYTES,
9    zine::Zine,
10};
11
12#[derive(Clone, Copy)]
13pub struct ZultiLines<'a> {
14    bounding_box: &'a BoundingBox,
15    // In the binary format we store the number of offsets here
16    // If it's 0, it means that the multi lines is empty
17    // If it's odd it means we also inserted one extra offset at the end for padding that should not ends up in the slice
18    offsets: &'a [u32],
19    bytes: &'a [u8],
20}
21
22impl<'a> ZultiLines<'a> {
23    pub fn new(bounding_box: &'a BoundingBox, offsets: &'a [u32], bytes: &'a [u8]) -> Self {
24        Self {
25            bounding_box,
26            offsets,
27            bytes,
28        }
29    }
30
31    pub fn from_bytes(data: &'a [u8]) -> Self {
32        // 1. Retrieve the bounding box
33        let bounding_box = BoundingBox::from_bytes(&data[..BOUNDING_BOX_SIZE_IN_BYTES]);
34        let data = &data[BOUNDING_BOX_SIZE_IN_BYTES..];
35
36        // 2. Then retrieve the offsets
37        // 2.1 Start by getting the number of offsets to retrieve
38        let offsets_count = u32::from_ne_bytes(data[..mem::size_of::<u32>()].try_into().unwrap());
39        let data = &data[mem::size_of::<u32>()..];
40        // 2.2 Then retrieve the offsets
41        let size_of_offsets = offsets_count as usize * mem::size_of::<u32>();
42        let offsets = &data[..size_of_offsets];
43        let offsets: &[u32] = cast_slice(offsets);
44        let data = &data[size_of_offsets..];
45        // 2.3 If we have an even number of offsets, there is one u32 of padding at the end that we must skip before retrieving coords of the lines
46        let data = if offsets_count % 2 == 0 {
47            debug_assert_eq!(data[0..mem::size_of::<u32>()], [0, 0, 0, 0]);
48            &data[mem::size_of::<u32>()..]
49        } else {
50            data
51        };
52        // 3. Finally retrieve the lines
53        let bytes = data;
54
55        Self {
56            bounding_box,
57            offsets,
58            bytes,
59        }
60    }
61
62    pub fn write_from_geometry(
63        writer: &mut Vec<u8>,
64        geometry: &MultiLineString<f64>,
65    ) -> Result<(), io::Error> {
66        BoundingBox::write_from_geometry(
67            writer,
68            geometry
69                .0
70                .iter()
71                .flat_map(|line| line.0.iter())
72                .map(|coord| Point::from((coord.x, coord.y))),
73        )?;
74        // Write the number of offsets to expect
75        writer.extend((geometry.0.len() as u32).to_ne_bytes());
76        let offsets_addr = writer.len();
77        // We must leave an empty space to write the offsets later
78        writer.extend(std::iter::repeat_n(
79            0,
80            geometry.0.len() * mem::size_of::<u32>(),
81        ));
82        if geometry.0.len() % 2 == 0 {
83            // If we have an even number of lines, we must add an extra offset at the end for padding
84            writer.extend(0_u32.to_ne_bytes());
85        }
86        let start = writer.len();
87        let mut offsets = Vec::new();
88        for line in geometry.iter() {
89            offsets.push(writer.len() as u32 - start as u32);
90            Zine::write_from_geometry(writer, line)?;
91        }
92
93        for (i, offset) in offsets.iter().enumerate() {
94            let offset_addr = offsets_addr + i * mem::size_of::<u32>();
95            writer[offset_addr..offset_addr + mem::size_of::<u32>()]
96                .copy_from_slice(&offset.to_ne_bytes());
97        }
98        Ok(())
99    }
100
101    pub fn bounding_box(&self) -> &'a BoundingBox {
102        self.bounding_box
103    }
104
105    pub fn get(&self, index: usize) -> Option<Zine<'a>> {
106        let offset = *self.offsets.get(index)?;
107        let next_offset = *self
108            .offsets
109            .get(index + 1)
110            .unwrap_or(&(self.bytes.len() as u32));
111        let bytes = &self.bytes[offset as usize..next_offset as usize];
112        Some(Zine::from_bytes(bytes))
113    }
114
115    pub fn len(&self) -> usize {
116        self.offsets.len()
117    }
118
119    pub fn is_empty(&self) -> bool {
120        self.len() == 0
121    }
122
123    pub fn lines(&'a self) -> impl Iterator<Item = Zine<'a>> {
124        (0..self.len()).map(move |index| self.get(index).unwrap())
125    }
126
127    pub fn to_geo(&self) -> geo_types::MultiLineString<f64> {
128        geo_types::MultiLineString::new(self.lines().map(|zine| zine.to_geo()).collect())
129    }
130}
131
132impl<'a> fmt::Debug for ZultiLines<'a> {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        struct ZinesDebug<'b, 'a>(&'b ZultiLines<'a>);
135
136        impl<'b, 'a> fmt::Debug for ZinesDebug<'b, 'a> {
137            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138                f.debug_list().entries(self.0.lines()).finish()
139            }
140        }
141
142        f.debug_struct("ZultiLines")
143            .field("bounding_box", &self.bounding_box())
144            .field("zines", &ZinesDebug(self))
145            .finish()
146    }
147}
148
149// points and line have nothing in common
150impl<'a> RelationBetweenShapes<Zoint<'a>> for ZultiLines<'a> {
151    fn relation(&self, _other: &Zoint, relation: InputRelation) -> OutputRelation {
152        relation.to_false().make_disjoint_if_set()
153    }
154}
155
156impl<'a> RelationBetweenShapes<ZultiPoints<'a>> for ZultiLines<'a> {
157    fn relation(&self, _other: &ZultiPoints, relation: InputRelation) -> OutputRelation {
158        relation.to_false().make_disjoint_if_set()
159    }
160}
161
162impl<'a> RelationBetweenShapes<Zine<'a>> for ZultiLines<'a> {
163    fn relation(&self, other: &Zine, relation: InputRelation) -> OutputRelation {
164        if self.is_empty() || other.is_empty() || self.bounding_box().disjoint(other.bounding_box())
165        {
166            return relation.to_false().make_disjoint_if_set();
167        }
168
169        for line in self.lines() {
170            if line.intersects(other) {
171                return relation.to_false().make_intersect_if_set();
172            }
173        }
174
175        relation.to_false().make_disjoint_if_set()
176    }
177}
178
179impl<'a> RelationBetweenShapes<ZultiLines<'a>> for ZultiLines<'a> {
180    fn relation(&self, other: &ZultiLines, relation: InputRelation) -> OutputRelation {
181        if self.is_empty() || other.is_empty() || self.bounding_box().disjoint(other.bounding_box())
182        {
183            return relation.to_false().make_disjoint_if_set();
184        }
185
186        for left in self.lines() {
187            for right in other.lines() {
188                if left.intersects(&right) {
189                    return relation.to_false().make_intersect_if_set();
190                }
191            }
192        }
193
194        relation.to_false().make_disjoint_if_set()
195    }
196}
197
198impl<'a> RelationBetweenShapes<Zolygon<'a>> for ZultiLines<'a> {
199    fn relation(&self, other: &Zolygon, relation: InputRelation) -> OutputRelation {
200        let mut output = relation.to_false();
201
202        if self.is_empty() || other.is_empty() || self.bounding_box().disjoint(other.bounding_box())
203        {
204            return output.make_disjoint_if_set();
205        }
206
207        let mut contained = 0;
208        for line in self.lines() {
209            let r = line.relation(other, relation.strip_strict().strip_disjoint());
210            output |= r;
211            if r.contained.unwrap_or_default() {
212                contained += 1;
213            }
214
215            if output.any_relation() && relation.early_exit {
216                return output;
217            }
218        }
219
220        if contained == self.len() {
221            output = output.make_strict_contains_if_set();
222        }
223
224        if output.any_relation() {
225            output
226        } else {
227            output.make_disjoint_if_set()
228        }
229    }
230}
231
232impl<'a> RelationBetweenShapes<ZultiPolygons<'a>> for ZultiLines<'a> {
233    fn relation(&self, other: &ZultiPolygons, relation: InputRelation) -> OutputRelation {
234        let mut output = relation.to_false();
235
236        if self.is_empty() || other.is_empty() || self.bounding_box().disjoint(other.bounding_box())
237        {
238            return output.make_disjoint_if_set();
239        }
240        let mut contained = 0;
241        for line in self.lines() {
242            for polygon in other.polygons() {
243                let r = line.relation(&polygon, relation.strip_strict().strip_disjoint());
244                output |= r;
245                if r.contained.unwrap_or_default() {
246                    contained += 1;
247                }
248
249                if output.any_relation() && relation.early_exit {
250                    return output;
251                }
252            }
253        }
254
255        if contained == self.len() {
256            output = output.make_strict_contained_if_set();
257        }
258
259        if output.any_relation() {
260            output
261        } else {
262            output.make_disjoint_if_set()
263        }
264    }
265}
266
267impl<'a> RelationBetweenShapes<Zollection<'a>> for ZultiLines<'a> {
268    fn relation(&self, other: &Zollection<'a>, relation: InputRelation) -> OutputRelation {
269        other
270            .relation(self, relation.swap_contains_relation())
271            .swap_contains_relation()
272    }
273}
274
275impl<'a> RelationBetweenShapes<Zerometry<'a>> for ZultiLines<'a> {
276    fn relation(&self, other: &Zerometry<'a>, relation: InputRelation) -> OutputRelation {
277        other
278            .relation(self, relation.swap_contains_relation())
279            .swap_contains_relation()
280    }
281}
282
283impl PartialEq<MultiLineString> for ZultiLines<'_> {
284    fn eq(&self, other: &MultiLineString) -> bool {
285        self.lines()
286            .zip(other.0.iter())
287            .all(|(zine, line)| zine.eq(line))
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use geo::{LineString, MultiPolygon, coord, polygon};
294    use geo_types::MultiLineString;
295    use insta::{assert_compact_debug_snapshot, assert_debug_snapshot, assert_snapshot};
296
297    use super::*;
298
299    #[test]
300    fn test_write_from_geometry_with_even_number_of_elements() {
301        let first_line = LineString::from(vec![
302            Point::from((0.0, 0.0)),
303            Point::from((10.0, 0.0)),
304            Point::from((0.0, 10.0)),
305        ]);
306        let second_line = LineString::from(vec![
307            Point::from((10.0, 10.0)),
308            Point::from((20.0, 0.0)),
309            Point::from((20.0, 10.0)),
310        ]);
311        let geometry = MultiLineString::new(vec![first_line.clone(), second_line.clone()]);
312
313        let mut writer = Vec::new();
314
315        ZultiLines::write_from_geometry(&mut writer, &geometry).unwrap();
316        // Debug everything at once just to make sure it never changes
317        assert_debug_snapshot!(writer);
318        let mut current_offset = 0;
319        let expected_bounding_box: &[f64] =
320            cast_slice(&writer[current_offset..BOUNDING_BOX_SIZE_IN_BYTES]);
321        assert_compact_debug_snapshot!(expected_bounding_box, @"[0.0, 0.0, 20.0, 10.0]");
322        current_offset += BOUNDING_BOX_SIZE_IN_BYTES;
323        let expected_nb_offsets: u32 = u32::from_ne_bytes(
324            writer[current_offset..current_offset + mem::size_of::<u32>()]
325                .try_into()
326                .unwrap(),
327        );
328        assert_snapshot!(expected_nb_offsets, @"2");
329        current_offset += mem::size_of::<u32>();
330        // With 2 elements + the u32 to give us the number of elements we're one u32 off at the end. There should be padding
331        let expected_offsets: &[u32] = cast_slice(
332            &writer[current_offset
333                ..current_offset + mem::size_of::<u32>() * expected_nb_offsets as usize],
334        );
335        assert_compact_debug_snapshot!(expected_offsets, @"[0, 80]");
336        current_offset += mem::size_of::<u32>() * expected_nb_offsets as usize;
337        // Now there should be a one u32 of padding
338        let padding = &writer[current_offset..current_offset + mem::size_of::<u32>()];
339        assert_compact_debug_snapshot!(padding, @"[0, 0, 0, 0]");
340        current_offset += mem::size_of::<u32>();
341        // Now there should be the first zine at the offset 0
342        let first_zine_bytes = &writer[current_offset + expected_offsets[0] as usize
343            ..current_offset + expected_offsets[1] as usize];
344        assert_compact_debug_snapshot!(first_zine_bytes, @"[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64]");
345        let first_zine = Zine::from_bytes(first_zine_bytes);
346        assert_compact_debug_snapshot!(first_zine, @"Zine { bounding_box: BoundingBox { bottom_left: Coord { x: 0.0, y: 0.0 }, top_right: Coord { x: 10.0, y: 10.0 } }, points: [Zoint { lng: 0.0, lat: 0.0 }, Zoint { lng: 10.0, lat: 0.0 }, Zoint { lng: 0.0, lat: 10.0 }] }");
347        assert_eq!(first_zine, first_line);
348        let second_zine_bytes = &writer[current_offset + expected_offsets[1] as usize..];
349        assert_compact_debug_snapshot!(second_zine_bytes, @"[0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 64, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 52, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 64, 0, 0, 0, 0, 0, 0, 36, 64]");
350        let second_zine = Zine::from_bytes(second_zine_bytes);
351        assert_compact_debug_snapshot!(second_zine, @"Zine { bounding_box: BoundingBox { bottom_left: Coord { x: 10.0, y: 0.0 }, top_right: Coord { x: 20.0, y: 10.0 } }, points: [Zoint { lng: 10.0, lat: 10.0 }, Zoint { lng: 20.0, lat: 0.0 }, Zoint { lng: 20.0, lat: 10.0 }] }");
352        assert_eq!(second_zine, second_line);
353
354        // Try to parse the zulti lines
355        let zulti_lines = ZultiLines::from_bytes(&writer);
356        assert_snapshot!(zulti_lines.len(), @"2");
357        assert_compact_debug_snapshot!(zulti_lines.bounding_box(), @"BoundingBox { bottom_left: Coord { x: 0.0, y: 0.0 }, top_right: Coord { x: 20.0, y: 10.0 } }");
358        assert_compact_debug_snapshot!(zulti_lines.offsets, @"[0, 80]");
359        assert_compact_debug_snapshot!(zulti_lines.get(0).unwrap(), @"Zine { bounding_box: BoundingBox { bottom_left: Coord { x: 0.0, y: 0.0 }, top_right: Coord { x: 10.0, y: 10.0 } }, points: [Zoint { lng: 0.0, lat: 0.0 }, Zoint { lng: 10.0, lat: 0.0 }, Zoint { lng: 0.0, lat: 10.0 }] }");
360        assert_compact_debug_snapshot!(zulti_lines.get(1).unwrap(), @"Zine { bounding_box: BoundingBox { bottom_left: Coord { x: 10.0, y: 0.0 }, top_right: Coord { x: 20.0, y: 10.0 } }, points: [Zoint { lng: 10.0, lat: 10.0 }, Zoint { lng: 20.0, lat: 0.0 }, Zoint { lng: 20.0, lat: 10.0 }] }");
361        assert_compact_debug_snapshot!(zulti_lines.get(2), @"None");
362        assert_debug_snapshot!(zulti_lines, @r"
363        ZultiLines {
364            bounding_box: BoundingBox {
365                bottom_left: Coord {
366                    x: 0.0,
367                    y: 0.0,
368                },
369                top_right: Coord {
370                    x: 20.0,
371                    y: 10.0,
372                },
373            },
374            zines: [
375                Zine {
376                    bounding_box: BoundingBox {
377                        bottom_left: Coord {
378                            x: 0.0,
379                            y: 0.0,
380                        },
381                        top_right: Coord {
382                            x: 10.0,
383                            y: 10.0,
384                        },
385                    },
386                    points: [
387                        Zoint {
388                            lng: 0.0,
389                            lat: 0.0,
390                        },
391                        Zoint {
392                            lng: 10.0,
393                            lat: 0.0,
394                        },
395                        Zoint {
396                            lng: 0.0,
397                            lat: 10.0,
398                        },
399                    ],
400                },
401                Zine {
402                    bounding_box: BoundingBox {
403                        bottom_left: Coord {
404                            x: 10.0,
405                            y: 0.0,
406                        },
407                        top_right: Coord {
408                            x: 20.0,
409                            y: 10.0,
410                        },
411                    },
412                    points: [
413                        Zoint {
414                            lng: 10.0,
415                            lat: 10.0,
416                        },
417                        Zoint {
418                            lng: 20.0,
419                            lat: 0.0,
420                        },
421                        Zoint {
422                            lng: 20.0,
423                            lat: 10.0,
424                        },
425                    ],
426                },
427            ],
428        }
429        ");
430    }
431
432    #[test]
433    fn test_write_from_geometry_with_odd_number_of_elements() {
434        let first_line = LineString::from(vec![
435            Point::from((0.0, 0.0)),
436            Point::from((10.0, 0.0)),
437            Point::from((0.0, 10.0)),
438        ]);
439        let geometry = MultiLineString::new(vec![first_line.clone()]);
440
441        let mut writer = Vec::new();
442
443        ZultiLines::write_from_geometry(&mut writer, &geometry).unwrap();
444        // Debug everything at once just to make sure it never changes
445        assert_debug_snapshot!(writer);
446        let mut current_offset = 0;
447        let expected_bounding_box: &[f64] =
448            cast_slice(&writer[current_offset..BOUNDING_BOX_SIZE_IN_BYTES]);
449        assert_compact_debug_snapshot!(expected_bounding_box, @"[0.0, 0.0, 10.0, 10.0]");
450        current_offset += BOUNDING_BOX_SIZE_IN_BYTES;
451        let expected_nb_offsets: u32 = u32::from_ne_bytes(
452            writer[current_offset..current_offset + mem::size_of::<u32>()]
453                .try_into()
454                .unwrap(),
455        );
456        assert_snapshot!(expected_nb_offsets, @"1");
457        current_offset += mem::size_of::<u32>();
458        // With 2 elements + the u32 to give us the number of elements we're one u32 off at the end. There should be padding
459        let expected_offsets: &[u32] = cast_slice(
460            &writer[current_offset
461                ..current_offset + mem::size_of::<u32>() * expected_nb_offsets as usize],
462        );
463        assert_compact_debug_snapshot!(expected_offsets, @"[0]");
464        current_offset += mem::size_of::<u32>() * expected_nb_offsets as usize;
465        // This time we should not have any padding
466        // -
467        // Now there should be the first zine at the offset 0
468        let first_zine_bytes = &writer[current_offset + expected_offsets[0] as usize..];
469        assert_compact_debug_snapshot!(first_zine_bytes, @"[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64]");
470        let first_zine = Zine::from_bytes(first_zine_bytes);
471        assert_compact_debug_snapshot!(first_zine, @"Zine { bounding_box: BoundingBox { bottom_left: Coord { x: 0.0, y: 0.0 }, top_right: Coord { x: 10.0, y: 10.0 } }, points: [Zoint { lng: 0.0, lat: 0.0 }, Zoint { lng: 10.0, lat: 0.0 }, Zoint { lng: 0.0, lat: 10.0 }] }");
472        assert_eq!(first_zine, first_line);
473
474        // Try to parse the zulti lines
475        let zulti_polygon = ZultiLines::from_bytes(&writer);
476        assert_snapshot!(zulti_polygon.len(), @"1");
477        assert_compact_debug_snapshot!(zulti_polygon.bounding_box(), @"BoundingBox { bottom_left: Coord { x: 0.0, y: 0.0 }, top_right: Coord { x: 10.0, y: 10.0 } }");
478        assert_compact_debug_snapshot!(zulti_polygon.offsets, @"[0]");
479        assert_compact_debug_snapshot!(zulti_polygon.get(0).unwrap(), @"Zine { bounding_box: BoundingBox { bottom_left: Coord { x: 0.0, y: 0.0 }, top_right: Coord { x: 10.0, y: 10.0 } }, points: [Zoint { lng: 0.0, lat: 0.0 }, Zoint { lng: 10.0, lat: 0.0 }, Zoint { lng: 0.0, lat: 10.0 }] }");
480        assert_compact_debug_snapshot!(zulti_polygon.get(1), @"None");
481        assert_debug_snapshot!(zulti_polygon, @r"
482        ZultiLines {
483            bounding_box: BoundingBox {
484                bottom_left: Coord {
485                    x: 0.0,
486                    y: 0.0,
487                },
488                top_right: Coord {
489                    x: 10.0,
490                    y: 10.0,
491                },
492            },
493            zines: [
494                Zine {
495                    bounding_box: BoundingBox {
496                        bottom_left: Coord {
497                            x: 0.0,
498                            y: 0.0,
499                        },
500                        top_right: Coord {
501                            x: 10.0,
502                            y: 10.0,
503                        },
504                    },
505                    points: [
506                        Zoint {
507                            lng: 0.0,
508                            lat: 0.0,
509                        },
510                        Zoint {
511                            lng: 10.0,
512                            lat: 0.0,
513                        },
514                        Zoint {
515                            lng: 0.0,
516                            lat: 10.0,
517                        },
518                    ],
519                },
520            ],
521        }
522        ");
523    }
524
525    #[test]
526    fn test_write_from_geometry_with_no_elements() {
527        let geometry = MultiLineString::new(vec![]);
528
529        let mut writer = Vec::new();
530
531        ZultiLines::write_from_geometry(&mut writer, &geometry).unwrap();
532        // Debug everything at once just to make sure it never changes
533        assert_debug_snapshot!(writer);
534        let mut current_offset = 0;
535        let expected_bounding_box: &[f64] =
536            cast_slice(&writer[current_offset..BOUNDING_BOX_SIZE_IN_BYTES]);
537        assert_compact_debug_snapshot!(expected_bounding_box, @"[0.0, 0.0, 0.0, 0.0]");
538        current_offset += BOUNDING_BOX_SIZE_IN_BYTES;
539        let expected_nb_offsets: u32 = u32::from_ne_bytes(
540            writer[current_offset..current_offset + mem::size_of::<u32>()]
541                .try_into()
542                .unwrap(),
543        );
544        assert_snapshot!(expected_nb_offsets, @"0");
545        current_offset += mem::size_of::<u32>();
546        // With 2 elements + the u32 to give us the number of elements we're one u32 off at the end. There should be padding
547        let expected_offsets: &[u32] = cast_slice(
548            &writer[current_offset
549                ..current_offset + mem::size_of::<u32>() * expected_nb_offsets as usize],
550        );
551        assert_compact_debug_snapshot!(expected_offsets, @"[]");
552        current_offset += mem::size_of::<u32>() * expected_nb_offsets as usize;
553        // Now there should be a one u32 of padding
554        let padding = &writer[current_offset..current_offset + mem::size_of::<u32>()];
555        assert_compact_debug_snapshot!(padding, @"[0, 0, 0, 0]");
556
557        // Try to parse the zulti lines
558        let zulti_lines = ZultiLines::from_bytes(&writer);
559        assert_snapshot!(zulti_lines.len(), @"0");
560        assert_compact_debug_snapshot!(zulti_lines.bounding_box(), @"BoundingBox { bottom_left: Coord { x: 0.0, y: 0.0 }, top_right: Coord { x: 0.0, y: 0.0 } }");
561        assert_compact_debug_snapshot!(zulti_lines.offsets, @"[]");
562        assert_compact_debug_snapshot!(zulti_lines.get(0), @"None");
563        assert_debug_snapshot!(zulti_lines, @r"
564        ZultiLines {
565            bounding_box: BoundingBox {
566                bottom_left: Coord {
567                    x: 0.0,
568                    y: 0.0,
569                },
570                top_right: Coord {
571                    x: 0.0,
572                    y: 0.0,
573                },
574            },
575            zines: [],
576        }
577        ");
578    }
579
580    #[test]
581    fn test_multi_lines_and_multipolygon() {
582        let inside_line = LineString::new(vec![
583            coord! { x: 0.4, y: 0.4},
584            coord! { x: 0.6, y: 0.4},
585            coord! { x: 0.6, y: 0.6},
586            coord! { x: 0.4, y: 0.6},
587        ]);
588        let outside_line = LineString::new(vec![
589            coord! { x: -0.4, y: -0.4},
590            coord! { x: -0.6, y: -0.4},
591            coord! { x: -0.6, y: -0.6},
592            coord! { x: -0.4, y: -0.6},
593        ]);
594        let inside = polygon![
595             (x: 0., y: 0.),
596             (x: 1., y: 0.),
597             (x: 1., y: 1.),
598             (x: 0., y: 1.),
599        ];
600        let outside = polygon![
601             (x: 5., y: 5.),
602             (x: 6., y: 5.),
603             (x: 6., y: 6.),
604             (x: 5., y: 6.),
605        ];
606        let intersect = polygon![
607             (x: 0.5, y: 0.5),
608             (x: 0.6, y: 0.5),
609             (x: 0.6, y: 0.6),
610             (x: 0.5, y: 0.6),
611        ];
612        let multi_line_strict_inside =
613            MultiLineString::new(vec![inside_line.clone(), inside_line.clone()]);
614        let multi_line_outside =
615            MultiLineString::new(vec![outside_line.clone(), outside_line.clone()]);
616        let multi_line_inside = MultiLineString::new(vec![outside_line, inside_line]);
617
618        let multi_polygons_inside = MultiPolygon::new(vec![inside.clone()]);
619        let multi_polygons_outside = MultiPolygon::new(vec![outside.clone()]);
620        let multi_polygons_intersect = MultiPolygon::new(vec![intersect.clone()]);
621        let multi_polygons_in_and_out = MultiPolygon::new(vec![inside.clone(), outside.clone()]);
622        let multi_polygons_all =
623            MultiPolygon::new(vec![inside.clone(), outside.clone(), intersect.clone()]);
624
625        let mut buf = Vec::new();
626        ZultiLines::write_from_geometry(&mut buf, &multi_line_strict_inside).unwrap();
627        let multi_line_strict_inside = ZultiLines::from_bytes(&buf);
628
629        let mut buf = Vec::new();
630        ZultiLines::write_from_geometry(&mut buf, &multi_line_outside).unwrap();
631        let multi_line_outside = ZultiLines::from_bytes(&buf);
632
633        let mut buf = Vec::new();
634        ZultiLines::write_from_geometry(&mut buf, &multi_line_inside).unwrap();
635        let multi_line_inside = ZultiLines::from_bytes(&buf);
636
637        let mut buf = Vec::new();
638        ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_inside).unwrap();
639        let multi_polygons_inside = ZultiPolygons::from_bytes(&buf);
640        let mut buf = Vec::new();
641        ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_outside).unwrap();
642        let multi_polygons_outside = ZultiPolygons::from_bytes(&buf);
643        let mut buf = Vec::new();
644        ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_intersect).unwrap();
645        let multi_polygons_intersect = ZultiPolygons::from_bytes(&buf);
646        let mut buf = Vec::new();
647        ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_in_and_out).unwrap();
648        let multi_polygons_in_and_out = ZultiPolygons::from_bytes(&buf);
649        let mut buf = Vec::new();
650        ZultiPolygons::write_from_geometry(&mut buf, &multi_polygons_all).unwrap();
651        let multi_polygons_all = ZultiPolygons::from_bytes(&buf);
652
653        assert_compact_debug_snapshot!(multi_line_strict_inside.all_relation(&multi_polygons_inside), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(true), intersect: Some(false), disjoint: Some(false) }");
654        assert_compact_debug_snapshot!(multi_line_strict_inside.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) }");
655        assert_compact_debug_snapshot!(multi_line_strict_inside.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) }");
656        assert_compact_debug_snapshot!(multi_line_strict_inside.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) }");
657        assert_compact_debug_snapshot!(multi_line_strict_inside.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) }");
658        assert_compact_debug_snapshot!(multi_line_strict_inside.any_relation(&multi_polygons_all), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(false), intersect: Some(false), disjoint: Some(false) }");
659
660        assert_compact_debug_snapshot!(multi_line_outside.all_relation(&multi_polygons_inside), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(false), strict_contained: Some(false), intersect: Some(false), disjoint: Some(true) }");
661        assert_compact_debug_snapshot!(multi_line_outside.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) }");
662        assert_compact_debug_snapshot!(multi_line_outside.all_relation(&multi_polygons_intersect), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(false), strict_contained: Some(false), intersect: Some(false), disjoint: Some(true) }");
663        assert_compact_debug_snapshot!(multi_line_outside.all_relation(&multi_polygons_in_and_out), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(false), strict_contained: Some(false), intersect: Some(false), disjoint: Some(true) }");
664        assert_compact_debug_snapshot!(multi_line_outside.all_relation(&multi_polygons_all), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(false), strict_contained: Some(false), intersect: Some(false), disjoint: Some(true) }");
665        assert_compact_debug_snapshot!(multi_line_outside.any_relation(&multi_polygons_all), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(false), strict_contained: Some(false), intersect: Some(false), disjoint: Some(true) }");
666
667        assert_compact_debug_snapshot!(multi_line_inside.all_relation(&multi_polygons_inside), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(false), intersect: Some(false), disjoint: Some(false) }");
668        assert_compact_debug_snapshot!(multi_line_inside.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) }");
669        assert_compact_debug_snapshot!(multi_line_inside.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) }");
670        assert_compact_debug_snapshot!(multi_line_inside.all_relation(&multi_polygons_in_and_out), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(false), intersect: Some(false), disjoint: Some(false) }");
671        assert_compact_debug_snapshot!(multi_line_inside.all_relation(&multi_polygons_all), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(false), intersect: Some(true), disjoint: Some(false) }");
672        assert_compact_debug_snapshot!(multi_line_inside.any_relation(&multi_polygons_all), @"OutputRelation { contains: Some(false), strict_contains: Some(false), contained: Some(true), strict_contained: Some(false), intersect: Some(false), disjoint: Some(false) }");
673    }
674}