Skip to main content

rtz_core/geo/
shared.rs

1//! Shared functionality for geo operations.
2
3use core::str;
4use std::{
5    borrow::Cow,
6    collections::HashMap,
7    fmt::{Display, Formatter},
8    ops::Deref,
9};
10
11use chashmap::CHashMap;
12use geo::{Coord, Geometry, Intersects, Rect, SimplifyVw};
13// These types are named only in the `self-contained` codec helpers; `simplify_geometry` uses them
14// via `Geometry::` variants, which don't need the imports. Gating them keeps the default build warning-free.
15#[cfg(feature = "self-contained")]
16use geo::{LineString, MultiPolygon, Polygon};
17use geojson::{Feature, FeatureCollection, GeoJson};
18use rayon::prelude::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
19use serde_json::{Map, Value};
20use std::path::Path;
21
22#[cfg(feature = "self-contained")]
23use bincode::{
24    config::Configuration,
25    de::{read::BorrowReader, BorrowDecoder, Decoder},
26    enc::Encoder,
27    error::{DecodeError, EncodeError},
28    BorrowDecode, Decode, Encode,
29};
30
31use crate::base::types::Float;
32
33// Types.
34
35/// An index into the global static cache.
36pub type Id = u32;
37/// A rounded integer.
38pub type RoundDegree = i16;
39/// A rounded longitude and latitude.
40pub type RoundLngLat = (RoundDegree, RoundDegree);
41/// An `(id, Feature)` pair.
42pub type IdFeaturePair = (usize, geojson::Feature);
43
44// Concrete helpers.
45
46/// A concrete collection of concrete values.
47#[derive(Debug)]
48#[cfg_attr(feature = "self-contained", derive(Encode, Decode))]
49pub struct ConcreteVec<T>(Vec<T>)
50where
51    T: 'static;
52
53impl<T> Deref for ConcreteVec<T> {
54    type Target = Vec<T>;
55
56    fn deref(&self) -> &Self::Target {
57        &self.0
58    }
59}
60
61impl<T> From<geojson::FeatureCollection> for ConcreteVec<T>
62where
63    T: From<IdFeaturePair> + Send,
64{
65    fn from(value: geojson::FeatureCollection) -> ConcreteVec<T> {
66        let values = value.features.into_par_iter().enumerate().map(T::from).collect::<Vec<T>>();
67
68        ConcreteVec(values)
69    }
70}
71
72impl<T> IntoIterator for ConcreteVec<T> {
73    type IntoIter = std::vec::IntoIter<T>;
74    type Item = T;
75
76    fn into_iter(self) -> Self::IntoIter {
77        self.0.into_iter()
78    }
79}
80
81impl<'a, T> IntoIterator for &'a ConcreteVec<T> {
82    type IntoIter = std::slice::Iter<'a, T>;
83    type Item = &'a T;
84
85    fn into_iter(self) -> Self::IntoIter {
86        self.0.iter()
87    }
88}
89
90// Cow helpers.
91
92/// A wrapper for `Cow<'static, str>` to make encoding and decoding easier.
93#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
94pub struct EncodableString(pub Cow<'static, str>);
95
96impl AsRef<str> for EncodableString {
97    fn as_ref(&self) -> &str {
98        self.0.as_ref()
99    }
100}
101
102impl Deref for EncodableString {
103    type Target = Cow<'static, str>;
104
105    fn deref(&self) -> &Self::Target {
106        &self.0
107    }
108}
109
110impl Display for EncodableString {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        write!(f, "{}", self.0)
113    }
114}
115
116#[cfg(feature = "self-contained")]
117impl Encode for EncodableString {
118    fn encode<E>(&self, encoder: &mut E) -> Result<(), EncodeError>
119    where
120        E: Encoder,
121    {
122        let data = pad_string_alignment(self);
123
124        data.encode(encoder)
125    }
126}
127
128#[cfg(feature = "self-contained")]
129impl<Context> Decode<Context> for EncodableString {
130    fn decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
131    where
132        D: Decoder,
133    {
134        let data = Vec::decode(decoder)?;
135
136        // Now, we can limit the slice to trim the null padding.
137        unpad_string_alignment(&data).map(ToString::to_string).map(Cow::Owned).map(EncodableString)
138    }
139}
140
141#[cfg(feature = "self-contained")]
142impl<'de, Context> BorrowDecode<'de, Context> for EncodableString {
143    fn borrow_decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
144    where
145        D: BorrowDecoder<'de>,
146    {
147        let length = usize::decode(decoder)?;
148        let slice = decoder.borrow_reader().take_bytes(length * std::mem::size_of::<u8>())?;
149
150        // SAFETY: We know this slice is built into the binary, and it has a static lifetime.
151        let slice = unsafe { std::mem::transmute::<&'_ [u8], &'static [u8]>(slice) };
152
153        // Now, we can limit the slice to trim the null padding.
154        unpad_string_alignment(slice).map(Cow::Borrowed).map(EncodableString)
155    }
156}
157
158/// A wrapper for `Option<Cow<'static, str>>` to make encoding and decoding easier.
159#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
160pub struct EncodableOptionString(pub Option<Cow<'static, str>>);
161
162impl EncodableOptionString {
163    /// Converts from `EncodableOptionString`` to `Option<&Cow<'static, str>>``.
164    pub fn as_ref(&self) -> Option<&Cow<'static, str>> {
165        self.0.as_ref()
166    }
167}
168
169impl Deref for EncodableOptionString {
170    type Target = Option<Cow<'static, str>>;
171
172    fn deref(&self) -> &Self::Target {
173        &self.0
174    }
175}
176
177impl Display for EncodableOptionString {
178    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
179        match self.as_ref() {
180            None => write!(f, "None"),
181            Some(cow) => write!(f, "{}", cow),
182        }
183    }
184}
185
186#[cfg(feature = "self-contained")]
187impl Encode for EncodableOptionString {
188    fn encode<E>(&self, encoder: &mut E) -> Result<(), EncodeError>
189    where
190        E: Encoder,
191    {
192        match self.as_ref() {
193            None => 0usize.encode(encoder),
194            Some(cow) => {
195                // Descriminant.
196                1usize.encode(encoder)?;
197
198                // Padded data.
199                let data = pad_string_alignment(cow);
200
201                data.encode(encoder)
202            }
203        }
204    }
205}
206
207#[cfg(feature = "self-contained")]
208impl<Context> Decode<Context> for EncodableOptionString {
209    fn decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
210    where
211        D: Decoder,
212    {
213        let variant = usize::decode(decoder)?;
214
215        let result = match variant {
216            0 => EncodableOptionString(None),
217            1 => {
218                let es = EncodableString::decode(decoder)?;
219
220                EncodableOptionString(Some(es.0))
221            }
222            _ => panic!("Unsupported variant."),
223        };
224
225        Ok(result)
226    }
227}
228
229#[cfg(feature = "self-contained")]
230impl<'de, Context> BorrowDecode<'de, Context> for EncodableOptionString {
231    fn borrow_decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
232    where
233        D: BorrowDecoder<'de>,
234    {
235        let variant = usize::decode(decoder)?;
236
237        let result = match variant {
238            0 => EncodableOptionString(None),
239            1 => {
240                let es = EncodableString::borrow_decode(decoder)?;
241
242                EncodableOptionString(Some(es.0))
243            }
244            _ => panic!("Unsupported variant."),
245        };
246
247        Ok(result)
248    }
249}
250
251// Traits.
252
253/// A trait for types that have a [`Geometry`].
254///
255/// Helps abstract away this property so the helper methods can be generalized.
256pub trait HasGeometry {
257    /// Get the `id` of the [`HasGeometry`].
258    fn id(&self) -> usize;
259    /// Get the [`Geometry`] of the [`HasGeometry`].
260    fn geometry(&self) -> &Geometry<Float>;
261}
262
263/// A trait for types that have properties.
264pub trait HasProperties {
265    /// Get the properties of the [`HasProperties`].
266    fn properties(&self) -> Map<String, Value>;
267}
268
269/// A trait that allows types to be converted to GeoJSON.
270pub trait ToGeoJsonFeature {
271    /// Convert the type to GeoJSON.
272    fn to_feature(&self) -> geojson::Feature;
273}
274
275impl<T> ToGeoJsonFeature for T
276where
277    T: HasGeometry + HasProperties,
278{
279    fn to_feature(&self) -> geojson::Feature {
280        let geometry = self.geometry();
281        let properties = self.properties();
282
283        geojson::Feature {
284            properties: Some(properties),
285            geometry: Some(geojson::Geometry::from(geometry)),
286            ..geojson::Feature::default()
287        }
288    }
289}
290
291/// A trait that allows for iterator types to be converted to GeoJSON.
292pub trait ToGeoJsonFeatureCollection {
293    /// Convert the type to GeoJSON.
294    fn to_feature_collection(&self) -> geojson::FeatureCollection;
295}
296
297/// Implementation specifically for [`ConcreteVec`].
298impl<L, D, T> ToGeoJsonFeatureCollection for &L
299where
300    L: Deref<Target = D>,
301    D: Deref<Target = [T]>,
302    T: ToGeoJsonFeature + 'static,
303{
304    fn to_feature_collection(&self) -> geojson::FeatureCollection {
305        let features = self.iter().map(|x| x.to_feature()).collect();
306
307        geojson::FeatureCollection {
308            features,
309            bbox: None,
310            foreign_members: None,
311        }
312    }
313}
314
315/// A trait to convert to GeoJSON.
316pub trait ToGeoJson {
317    /// Convert the type to GeoJSON.
318    fn to_geojson(&self) -> GeoJson;
319}
320
321impl<T> ToGeoJson for T
322where
323    T: ToGeoJsonFeatureCollection,
324{
325    fn to_geojson(&self) -> GeoJson {
326        GeoJson::FeatureCollection(self.to_feature_collection())
327    }
328}
329
330// Helper methods.
331
332/// Pads a String before encoding to ensure that the string is aligned to the correct byte boundary.
333#[cfg(feature = "self-contained")]
334pub fn pad_string_alignment(string: impl AsRef<str>) -> Vec<u8> {
335    let alignment = std::mem::align_of::<Float>();
336    let padding = alignment - (string.as_ref().len() % alignment);
337
338    string.as_ref().as_bytes().iter().chain(std::iter::repeat_n(&0u8, padding)).copied().collect::<Vec<u8>>()
339}
340
341/// Unpads a String after decoding to remove any null padding.
342#[cfg(feature = "self-contained")]
343pub fn unpad_string_alignment(data: &[u8]) -> Result<&str, DecodeError> {
344    let terminator = data.iter().position(|&x| x == 0).unwrap_or(data.len());
345    let slice = &data[..terminator];
346
347    let str = str::from_utf8(slice).map_err(|e| DecodeError::Utf8 { inner: e })?;
348
349    Ok(str)
350}
351
352/// Simplifies a [`Geometry`] using the [Visvalingam-Whyatt algorithm](https://bost.ocks.org/mike/simplify/).
353///
354/// For geometries that cannot be simplified, the original geometry is returned.
355pub fn simplify_geometry(geometry: Geometry<Float>, simplification_epsilon: Float) -> Geometry<Float> {
356    #[cfg(not(feature = "unsimplified"))]
357    let geometry = match geometry {
358        Geometry::Polygon(polygon) => {
359            let simplified = polygon.simplify_vw(simplification_epsilon);
360            Geometry::Polygon(simplified)
361        }
362        Geometry::MultiPolygon(multi_polygon) => {
363            let simplified = multi_polygon.simplify_vw(simplification_epsilon);
364            Geometry::MultiPolygon(simplified)
365        }
366        Geometry::LineString(line_string) => {
367            let simplified = line_string.simplify_vw(simplification_epsilon);
368            Geometry::LineString(simplified)
369        }
370        Geometry::MultiLineString(multi_line_string) => {
371            let simplified = multi_line_string.simplify_vw(simplification_epsilon);
372            Geometry::MultiLineString(simplified)
373        }
374        g => g,
375    };
376
377    geometry
378}
379
380/// Get the cache from the timezones.
381pub fn get_lookup_from_geometries<T>(geometries: &ConcreteVec<T>) -> HashMap<RoundLngLat, EncodableIds>
382where
383    T: HasGeometry + Send + Sync,
384{
385    let map = CHashMap::new();
386
387    (-180..180).into_par_iter().for_each(|x| {
388        for y in -90..90 {
389            let xf = x as Float;
390            let yf = y as Float;
391
392            let rect = Rect::new(Coord { x: xf, y: yf }, Coord { x: xf + 1.0, y: yf + 1.0 });
393
394            let mut intersected = Vec::new();
395
396            for g in geometries {
397                if g.geometry().intersects(&rect) {
398                    intersected.push(g.id() as Id);
399                }
400            }
401
402            map.insert((x as RoundDegree, y as RoundDegree), intersected);
403        }
404    });
405
406    let mut cache = HashMap::new();
407    for (key, value) in map.into_iter() {
408        cache.insert(key, EncodableIds(value));
409    }
410
411    cache
412}
413
414/// Generate the bincode representation of the 100km cache.
415///
416/// "100km" is a bit of a misnomer.  This is really 100km _at the equator_, but this
417/// makes it easier to reason about what the caches are doing.
418#[cfg(feature = "self-contained")]
419#[cfg_attr(coverage_nightly, coverage(off))]
420fn generate_lookup_bincode<T>(bincode_input: impl AsRef<Path>, bincode_destination: impl AsRef<Path>)
421where
422    T: HasGeometry + Decode<()> + Send + Sync + 'static,
423{
424    let data = std::fs::read(bincode_input).unwrap();
425    let (timezones, _len): (ConcreteVec<T>, usize) = bincode::decode_from_slice(&data, get_global_bincode_config()).unwrap();
426
427    let cache = get_lookup_from_geometries(&timezones);
428
429    bincode::encode_into_std_write(cache, &mut std::fs::File::create(bincode_destination).unwrap(), get_global_bincode_config()).unwrap();
430}
431
432/// Get the concrete timezones from features.
433pub fn get_items_from_features<T>(features: FeatureCollection) -> ConcreteVec<T>
434where
435    T: HasGeometry + From<IdFeaturePair> + Send,
436{
437    ConcreteVec::from(features)
438}
439
440/// Generate bincode representation of the timezones.
441#[cfg(feature = "self-contained")]
442#[cfg_attr(coverage_nightly, coverage(off))]
443fn generate_item_bincode<T>(geojson_features: FeatureCollection, bincode_destination: impl AsRef<Path>)
444where
445    T: HasGeometry + Encode + From<IdFeaturePair> + Send + 'static,
446{
447    let items: ConcreteVec<T> = get_items_from_features(geojson_features);
448    bincode::encode_into_std_write(items, &mut std::fs::File::create(bincode_destination).unwrap(), get_global_bincode_config()).unwrap();
449}
450
451/// Get the GeoJSON features from the binary assets.
452pub fn get_geojson_features_from_file(geojson_input: impl AsRef<Path>) -> FeatureCollection {
453    let geojson = std::fs::read_to_string(geojson_input).unwrap();
454    FeatureCollection::try_from(geojson.parse::<GeoJson>().unwrap()).unwrap()
455}
456
457/// Get the GeoJSON features from the binary assets.
458pub fn get_geojson_features_from_string(geojson_input: &str) -> FeatureCollection {
459    FeatureCollection::try_from(geojson_input.parse::<GeoJson>().unwrap()).unwrap()
460}
461
462/// Get the GeoJSON feature from the binary assets.
463pub fn get_geojson_feature_from_file(geojson_input: impl AsRef<Path>) -> Feature {
464    let geojson = std::fs::read_to_string(geojson_input).unwrap();
465    Feature::try_from(geojson.parse::<GeoJson>().unwrap()).unwrap()
466}
467
468/// Get the GeoJSON feature from the binary assets.
469pub fn get_geojson_feature_from_string(geojson_input: &str) -> Feature {
470    Feature::try_from(geojson_input.parse::<GeoJson>().unwrap()).unwrap()
471}
472
473/// Generates new bincodes for the timezones and the cache from the GeoJSON.
474#[cfg(feature = "self-contained")]
475#[cfg_attr(coverage_nightly, coverage(off))]
476pub fn generate_bincodes<T>(geojson_features: FeatureCollection, timezone_bincode_destination: impl AsRef<Path>, lookup_bincode_destination: impl AsRef<Path>)
477where
478    T: HasGeometry + Encode + From<IdFeaturePair> + Decode<()> + Send + Sync + 'static,
479{
480    generate_item_bincode::<T>(geojson_features, timezone_bincode_destination.as_ref());
481    generate_lookup_bincode::<T>(timezone_bincode_destination, lookup_bincode_destination);
482}
483
484// Helpers to get GeoJSON features from a source.
485
486/// Trait that supports getting the GeoJSON features from a source.
487pub trait CanGetGeoJsonFeaturesFromSource {
488    /// Get the GeoJSON features from a source.
489    fn get_geojson_features_from_source() -> geojson::FeatureCollection;
490}
491
492// Bincode helpers.
493
494/// Computes the best bincode to be used for the target architecture.
495#[cfg(all(feature = "self-contained", target_endian = "big"))]
496
497pub fn get_global_bincode_config() -> Configuration<bincode::config::BigEndian, bincode::config::Fixint> {
498    bincode::config::legacy().with_big_endian()
499}
500
501/// Computes the best bincode to be used for the target architecture.
502#[cfg(all(feature = "self-contained", target_endian = "little"))]
503pub fn get_global_bincode_config() -> Configuration<bincode::config::LittleEndian, bincode::config::Fixint> {
504    bincode::config::legacy()
505}
506
507// Special encoding / decoding logic for geometries.
508
509/// A wrapped [`Geometry`] that can be encoded and decoded via bincode.
510#[derive(Debug)]
511pub struct EncodableGeometry(pub Geometry<Float>);
512
513// When `owned-decode` is off, geometries are borrow-decoded: their coordinate `Vec`s are
514// reconstructed via `Vec::from_raw_parts` directly over the embedded asset bytes (see
515// `borrow_decode_poly`). Those `Vec`s do not own heap memory, so letting `geo`'s `Vec::drop` run
516// would `dealloc` a pointer into `.rodata` — undefined behavior. We forget the geometry on drop so
517// that never happens; the bytes live in the binary, so there is nothing to free.
518//
519// SAFETY / INVARIANT: this leak-on-drop is correct *only* because, with `owned-decode` off, every
520// geometry originates from `borrow_decode` (static-backed). The single selector that guarantees
521// this is `decode_binary_data` in `rtz/src/geo/shared.rs` — keep the two in lockstep. With
522// `owned-decode` on, geometries own real allocations and must drop normally, so there is no `Drop`.
523#[cfg(not(feature = "owned-decode"))]
524impl Drop for EncodableGeometry {
525    fn drop(&mut self) {
526        let geometry = std::mem::replace(&mut self.0, Geometry::Point(geo::Point::new(0.0, 0.0)));
527        std::mem::forget(geometry);
528    }
529}
530
531#[cfg(feature = "self-contained")]
532fn encode_poly<E>(polygon: &Polygon<Float>, encoder: &mut E) -> Result<(), EncodeError>
533where
534    E: Encoder,
535{
536    let exterior = &polygon.exterior().0;
537
538    // Encode the exterior length.
539    exterior.len().encode(encoder)?;
540
541    // Encode the exterior points.
542    for point in exterior {
543        point.x.encode(encoder)?;
544        point.y.encode(encoder)?;
545    }
546
547    let interiors = polygon.interiors();
548
549    // Encode the number of interiors.
550    interiors.len().encode(encoder)?;
551
552    // Encode the interiors.
553    for interior in interiors {
554        let interior = &interior.0;
555
556        // Encode the interior length.
557        interior.len().encode(encoder)?;
558
559        // Encode the interior points.
560        for point in interior {
561            point.x.encode(encoder)?;
562            point.y.encode(encoder)?;
563        }
564    }
565
566    Ok(())
567}
568
569#[cfg(feature = "self-contained")]
570impl Encode for EncodableGeometry {
571    fn encode<E>(&self, encoder: &mut E) -> Result<(), EncodeError>
572    where
573        E: Encoder,
574    {
575        match &self.0 {
576            Geometry::Polygon(polygon) => {
577                // Encode the variant.
578                0usize.encode(encoder)?;
579
580                encode_poly(polygon, encoder)?;
581            }
582            Geometry::MultiPolygon(multi_polygon) => {
583                // Encode the variant.
584                1usize.encode(encoder)?;
585
586                let polygons = &multi_polygon.0;
587
588                // Encode the number of polygons.
589                polygons.len().encode(encoder)?;
590
591                // Encode the polygons.
592                for polygon in polygons {
593                    encode_poly(polygon, encoder)?;
594                }
595            }
596            _ => panic!("Unsupported geometry variant."),
597        }
598
599        Ok(())
600    }
601}
602
603#[cfg(feature = "self-contained")]
604fn decode_poly<D>(decoder: &mut D) -> Result<Polygon<Float>, DecodeError>
605where
606    D: Decoder,
607{
608    let exterior_len = usize::decode(decoder)?;
609
610    let mut exterior = Vec::with_capacity(exterior_len);
611
612    for _ in 0..exterior_len {
613        let x = Float::decode(decoder)?;
614        let y = Float::decode(decoder)?;
615
616        exterior.push(Coord { x, y });
617    }
618
619    let interior_len = usize::decode(decoder)?;
620
621    let mut interiors = Vec::with_capacity(interior_len);
622
623    for _ in 0..interior_len {
624        let interior_len = usize::decode(decoder)?;
625
626        let mut interior = Vec::with_capacity(interior_len);
627
628        for _ in 0..interior_len {
629            let x = Float::decode(decoder)?;
630            let y = Float::decode(decoder)?;
631
632            interior.push(Coord { x, y });
633        }
634
635        interiors.push(LineString(interior));
636    }
637
638    Ok(Polygon::new(LineString(exterior), interiors))
639}
640
641#[cfg(feature = "self-contained")]
642fn borrow_decode_poly<'de, D>(decoder: &mut D) -> Result<Polygon<Float>, DecodeError>
643where
644    D: BorrowDecoder<'de>,
645{
646    let exterior_len = usize::decode(decoder)?;
647    let exterior_slice = decoder.borrow_reader().take_bytes(exterior_len * std::mem::size_of::<Float>() * 2)?;
648
649    // SAFETY: Perform unholy rites, and summon the devil, lol.
650    // Basically, this is an extreme optimization to prevent loading huge amounts of data into memory that are already
651    // in memory as part of the binary assets.
652    let exterior = unsafe { Vec::from_raw_parts(exterior_slice.as_ptr() as *mut Coord<Float>, exterior_len, exterior_len) };
653
654    let interior_len = usize::decode(decoder)?;
655
656    let mut interiors = Vec::with_capacity(interior_len);
657
658    for _ in 0..interior_len {
659        let interior_len = usize::decode(decoder)?;
660        let interior_slice = decoder.borrow_reader().take_bytes(interior_len * std::mem::size_of::<Float>() * 2)?;
661
662        // SAFETY: Perform unholy rites again: see above.
663        let interior = unsafe { Vec::from_raw_parts(interior_slice.as_ptr() as *mut Coord<Float>, interior_len, interior_len) };
664
665        interiors.push(LineString(interior));
666    }
667
668    Ok(Polygon::new(LineString(exterior), interiors))
669}
670
671#[cfg(feature = "self-contained")]
672impl<Context> Decode<Context> for EncodableGeometry {
673    fn decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
674    where
675        D: Decoder,
676    {
677        let variant = usize::decode(decoder)?;
678
679        let geometry = match variant {
680            0 => {
681                let polygon = decode_poly(decoder)?;
682
683                Geometry::Polygon(polygon)
684            }
685            1 => {
686                let polygon_len = usize::decode(decoder)?;
687
688                let mut polygons = Vec::with_capacity(polygon_len);
689
690                for _ in 0..polygon_len {
691                    let polygon = decode_poly(decoder)?;
692
693                    polygons.push(polygon);
694                }
695
696                Geometry::MultiPolygon(MultiPolygon::new(polygons))
697            }
698            _ => panic!("Unsupported geometry variant."),
699        };
700
701        Ok(EncodableGeometry(geometry))
702    }
703}
704
705#[cfg(feature = "self-contained")]
706impl<'de, Context> BorrowDecode<'de, Context> for EncodableGeometry {
707    fn borrow_decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
708    where
709        D: BorrowDecoder<'de>,
710    {
711        let variant = usize::decode(decoder)?;
712
713        let geometry = match variant {
714            0 => {
715                let polygon = borrow_decode_poly(decoder)?;
716
717                Geometry::Polygon(polygon)
718            }
719            1 => {
720                let polygon_len = usize::decode(decoder)?;
721
722                let mut polygons = Vec::with_capacity(polygon_len);
723
724                for _ in 0..polygon_len {
725                    let polygon = borrow_decode_poly(decoder)?;
726
727                    polygons.push(polygon);
728                }
729
730                Geometry::MultiPolygon(MultiPolygon::new(polygons))
731            }
732            _ => panic!("Unsupported geometry variant."),
733        };
734
735        Ok(EncodableGeometry(geometry))
736    }
737}
738
739/// A wrapped ['Vec`] that can be encoded and decoded via bincode.
740#[derive(Debug)]
741pub struct EncodableIds(pub Vec<Id>);
742
743impl Deref for EncodableIds {
744    type Target = Vec<Id>;
745
746    fn deref(&self) -> &Self::Target {
747        &self.0
748    }
749}
750
751impl AsRef<[Id]> for EncodableIds {
752    fn as_ref(&self) -> &[Id] {
753        &self.0
754    }
755}
756
757#[cfg(feature = "self-contained")]
758impl Encode for EncodableIds {
759    fn encode<E>(&self, encoder: &mut E) -> Result<(), EncodeError>
760    where
761        E: Encoder,
762    {
763        // Encode the exterior length.
764        self.0.len().encode(encoder)?;
765
766        // Encode the exterior points.
767        for x in &self.0 {
768            x.encode(encoder)?;
769        }
770
771        Ok(())
772    }
773}
774
775#[cfg(feature = "self-contained")]
776impl<Context> Decode<Context> for EncodableIds {
777    fn decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
778    where
779        D: Decoder,
780    {
781        let len = usize::decode(decoder)?;
782
783        let mut vec = Vec::with_capacity(len);
784
785        for _ in 0..len {
786            let x = Id::decode(decoder)?;
787
788            vec.push(x);
789        }
790
791        Ok(EncodableIds(vec))
792    }
793}
794
795#[cfg(feature = "self-contained")]
796impl<'de, Context> BorrowDecode<'de, Context> for EncodableIds {
797    fn borrow_decode<D>(decoder: &mut D) -> Result<Self, DecodeError>
798    where
799        D: BorrowDecoder<'de>,
800    {
801        let len = usize::decode(decoder)?;
802        let slice = decoder.borrow_reader().take_bytes(len * std::mem::size_of::<Id>())?;
803
804        // SAFETY: Perform unholy rites again: see above.
805        let vec = unsafe { Vec::from_raw_parts(slice.as_ptr() as *mut Id, len, len) };
806
807        Ok(EncodableIds(vec))
808    }
809}
810
811#[cfg(all(test, feature = "self-contained"))]
812mod codec_tests {
813    use super::*;
814    use crate::base::types::Float;
815    use geo::{Coord, Geometry, LineString, MultiPolygon, Polygon};
816    use std::borrow::Cow;
817
818    fn roundtrip_string(s: &str) {
819        let cfg = get_global_bincode_config();
820        let original = EncodableString(Cow::Owned(s.to_string()));
821        let bytes = bincode::encode_to_vec(&original, cfg).unwrap();
822        let (decoded, _len): (EncodableString, usize) = bincode::decode_from_slice(&bytes, cfg).unwrap();
823        assert_eq!(decoded, original, "string roundtrip failed for {s:?}");
824    }
825
826    #[test]
827    fn string_roundtrips_ascii_empty_nonascii_and_alignment() {
828        roundtrip_string(""); // empty
829        roundtrip_string("America/Los_Angeles");
830        roundtrip_string("مصر"); // non-ASCII UTF-8 through pad/unpad
831        roundtrip_string("abc"); // len 3 -> 1 pad byte
832        roundtrip_string("abcd"); // len 4 -> full extra 4 pad bytes (alignment boundary)
833    }
834
835    #[test]
836    fn option_string_roundtrips_none_and_some() {
837        let cfg = get_global_bincode_config();
838        for original in [EncodableOptionString(None), EncodableOptionString(Some(Cow::Owned("x".to_string())))] {
839            let bytes = bincode::encode_to_vec(&original, cfg).unwrap();
840            let (decoded, _len): (EncodableOptionString, usize) = bincode::decode_from_slice(&bytes, cfg).unwrap();
841            assert_eq!(decoded, original);
842        }
843    }
844
845    #[test]
846    fn ids_roundtrip_empty_and_many() {
847        let cfg = get_global_bincode_config();
848        for v in [Vec::<Id>::new(), vec![0u32, 1, 2, 4_000_000]] {
849            let original = EncodableIds(v.clone());
850            let bytes = bincode::encode_to_vec(&original, cfg).unwrap();
851            let (decoded, _len): (EncodableIds, usize) = bincode::decode_from_slice(&bytes, cfg).unwrap();
852            assert_eq!(decoded.0, v);
853        }
854    }
855
856    #[test]
857    fn geometry_roundtrips_polygon_with_interior_and_multipolygon() {
858        let cfg = get_global_bincode_config();
859        let exterior = LineString(vec![
860            Coord { x: 0.0, y: 0.0 },
861            Coord { x: 4.0, y: 0.0 },
862            Coord { x: 4.0, y: 4.0 },
863            Coord { x: 0.0, y: 4.0 },
864            Coord { x: 0.0, y: 0.0 },
865        ]);
866        let interior = LineString(vec![
867            Coord { x: 1.0, y: 1.0 },
868            Coord { x: 2.0, y: 1.0 },
869            Coord { x: 2.0, y: 2.0 },
870            Coord { x: 1.0, y: 1.0 },
871        ]);
872        let poly: Polygon<Float> = Polygon::new(exterior, vec![interior]);
873
874        let original = EncodableGeometry(Geometry::Polygon(poly.clone()));
875        let bytes = bincode::encode_to_vec(&original, cfg).unwrap();
876        let (decoded, _len): (EncodableGeometry, usize) = bincode::decode_from_slice(&bytes, cfg).unwrap();
877        assert_eq!(decoded.0, original.0);
878
879        let multi = EncodableGeometry(Geometry::MultiPolygon(MultiPolygon::new(vec![poly])));
880        let bytes = bincode::encode_to_vec(&multi, cfg).unwrap();
881        let (decoded, _len): (EncodableGeometry, usize) = bincode::decode_from_slice(&bytes, cfg).unwrap();
882        assert_eq!(decoded.0, multi.0);
883    }
884
885    #[test]
886    fn string_borrow_decode_matches_owned() {
887        // The borrowed decode path builds a `Cow::Borrowed` via an internal transmute to
888        // 'static. Exercising it here is sound because the source buffer outlives the decoded
889        // value and `Cow::Borrowed` frees nothing on drop. We deliberately do NOT borrow-decode
890        // the `Vec::from_raw_parts` types (EncodableIds / EncodableGeometry) from a local buffer:
891        // their decoded Vecs would free borrowed memory on drop. Those borrow paths are already
892        // exercised at runtime against the embedded 'static bincodes by the `geo::*` tests.
893        let cfg = get_global_bincode_config();
894        let original = EncodableString(Cow::Owned("America/Los_Angeles".to_string()));
895        let bytes = bincode::encode_to_vec(&original, cfg).unwrap();
896        let (decoded, _len): (EncodableString, usize) = bincode::borrow_decode_from_slice(&bytes, cfg).unwrap();
897        assert_eq!(decoded.as_ref(), original.as_ref());
898    }
899}