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