Skip to main content

mlt_core/convert/mvt/
decode.rs

1//! Decode MVT bytes into [`FeatureCollection`] or row-oriented [`TileLayer`]s.
2
3use std::collections::{BTreeMap, HashMap};
4
5use fast_mvt::{MvtLayer, MvtLayerRef, MvtReaderRef, MvtValue, MvtValueRef};
6use serde_json::Value;
7
8use crate::geojson::{Feature, FeatureCollection};
9use crate::tile::{PropValue, TileFeature, TileLayer};
10use crate::{MltError, MltResult};
11
12/// Parse MVT bytes into a list of layers, each holding its raw features.
13fn read_mvt_layers(data: &[u8]) -> MltResult<Vec<MvtLayer>> {
14    let layers = MvtReaderRef::new(data)?.to_tile()?.layers;
15    if layers.iter().any(|layer| layer.name.is_empty()) {
16        return Err(MltError::MissingLayerName);
17    }
18    Ok(layers)
19}
20
21/// Parse MVT binary data and convert to a [`FeatureCollection`].
22pub fn mvt_to_feature_collection(data: impl AsRef<[u8]>) -> MltResult<FeatureCollection> {
23    let mut features = Vec::new();
24
25    for layer in read_mvt_layers(data.as_ref())? {
26        for feat in layer.features {
27            let mut properties = feat
28                .properties
29                .into_iter()
30                .map(|(k, v)| Ok((k, Value::try_from(v)?)))
31                .collect::<MltResult<BTreeMap<_, _>>>()?;
32            properties.insert("_layer".into(), Value::String(layer.name.clone()));
33            properties.insert("_extent".into(), Value::Number(layer.extent.get().into()));
34            features.push(Feature {
35                geometry: feat.geometry,
36                id: feat.id,
37                properties,
38                ty: "Feature".into(),
39            });
40        }
41    }
42
43    Ok(FeatureCollection {
44        features,
45        ty: "FeatureCollection".into(),
46    })
47}
48
49/// Parse MVT binary data and convert each layer to a row-oriented [`TileLayer`].
50///
51/// Each MVT layer becomes one [`TileLayer`].  Property column types are inferred
52/// from all features in the layer: the first non-null value seen for each column
53/// determines its type, with `I64`+`U64` widened to `I64` and `F32`+`F64` widened
54/// to `F64`; all other type conflicts fall back to `Str`.
55pub fn mvt_to_tile_layers(data: impl AsRef<[u8]>) -> MltResult<Vec<TileLayer>> {
56    MvtReaderRef::new(data.as_ref())?
57        .layers()
58        .map(tile_layer_from_ref)
59        .collect()
60}
61
62/// Build a [`TileLayer`] straight from the borrowed reader.
63fn tile_layer_from_ref(layer: MvtLayerRef<'_>) -> MltResult<TileLayer> {
64    let name = layer.name();
65    if name.is_empty() {
66        return Err(MltError::MissingLayerName);
67    }
68
69    // First pass: collect property names (insertion-ordered) and infer column types.
70    let mut col_names: Vec<String> = Vec::new();
71    let mut col_index: HashMap<&str, usize> = HashMap::new();
72    let mut col_types: Vec<InferredType> = Vec::new();
73    // Each value with its column, so the second pass resolves no keys.
74    let mut values: Vec<(usize, MvtValueRef<'_>)> = Vec::new();
75    let mut feature_ends: Vec<usize> = Vec::with_capacity(layer.feature_count());
76
77    for feat in layer.features() {
78        for prop in feat.properties() {
79            let (key, value) = prop?;
80            let idx = if let Some(&idx) = col_index.get(key) {
81                idx
82            } else {
83                let idx = col_names.len();
84                col_names.push(key.to_string());
85                col_index.insert(key, idx);
86                col_types.push(InferredType::Unknown);
87                idx
88            };
89            // One bounds check rather than one per index expression.
90            let slot = &mut col_types[idx];
91            *slot = slot.merge(InferredType::from_mvt(value));
92            values.push((idx, value));
93        }
94        feature_ends.push(values.len());
95    }
96
97    // Columns that were only ever null fall back to Str.
98    for t in &mut col_types {
99        if *t == InferredType::Unknown {
100            *t = InferredType::Str;
101        }
102    }
103
104    // Second pass: build TileFeature objects.
105    let mut tile_features = Vec::with_capacity(layer.feature_count());
106    let mut start = 0;
107    for (feat, &end) in layer.features().zip(&feature_ends) {
108        // Start every slot with a typed null; fill in present values below.
109        let mut properties: Vec<PropValue> = col_types.iter().map(|t| t.typed_null()).collect();
110        for &(idx, value) in &values[start..end] {
111            if !matches!(value, MvtValueRef::Null) {
112                properties[idx] = col_types[idx].convert(value.into_owned());
113            }
114        }
115        start = end;
116        tile_features.push(TileFeature {
117            id: feat.id(),
118            geometry: feat.geometry()?,
119            properties,
120            #[cfg(feature = "unstable-v2")]
121            m_values: Vec::new(),
122            #[cfg(feature = "unstable-v2")]
123            nested: Vec::new(),
124        });
125    }
126
127    TileLayer::from_parts(name, layer.extent(), col_names, tile_features)
128}
129
130impl TryFrom<MvtLayer> for TileLayer {
131    type Error = MltError;
132
133    fn try_from(layer: MvtLayer) -> Result<Self, Self::Error> {
134        if layer.name.is_empty() {
135            return Err(MltError::MissingLayerName);
136        }
137
138        // First pass: collect property names (insertion-ordered) and infer column types.
139        let mut col_names: Vec<String> = Vec::new();
140        let mut col_index: HashMap<String, usize> = HashMap::new();
141        let mut col_types: Vec<InferredType> = Vec::new();
142
143        for feat in &layer.features {
144            for (key, val) in &feat.properties {
145                let idx = *col_index.entry(key.clone()).or_insert_with(|| {
146                    let i = col_names.len();
147                    col_names.push(key.clone());
148                    col_types.push(InferredType::Unknown);
149                    i
150                });
151                let slot = &mut col_types[idx];
152                *slot = slot.merge(InferredType::from_mvt(as_value_ref(val)));
153            }
154        }
155
156        // Columns that were only ever null fall back to Str.
157        for t in &mut col_types {
158            if *t == InferredType::Unknown {
159                *t = InferredType::Str;
160            }
161        }
162
163        // Second pass: build TileFeature objects.
164        let mut tile_features = Vec::with_capacity(layer.features.len());
165        for feat in layer.features {
166            // Start every slot with a typed null; fill in present values below.
167            let mut properties: Vec<PropValue> = col_types.iter().map(|t| t.typed_null()).collect();
168            for (key, val) in feat.properties {
169                if let Some(&idx) = col_index.get(&key)
170                    && !matches!(val, MvtValue::Null)
171                {
172                    properties[idx] = col_types[idx].convert(val);
173                }
174            }
175            tile_features.push(TileFeature {
176                id: feat.id,
177                geometry: feat.geometry,
178                properties,
179                #[cfg(feature = "unstable-v2")]
180                m_values: Vec::new(),
181                #[cfg(feature = "unstable-v2")]
182                nested: Vec::new(),
183            });
184        }
185
186        Self::from_parts(layer.name, layer.extent.get(), col_names, tile_features)
187    }
188}
189
190/// Borrow an owned [`MvtValue`], so both conversion paths share one inference pass.
191fn as_value_ref(value: &MvtValue) -> MvtValueRef<'_> {
192    match value {
193        MvtValue::String(s) => MvtValueRef::String(s),
194        MvtValue::Float(f) => MvtValueRef::Float(*f),
195        MvtValue::Double(f) => MvtValueRef::Double(*f),
196        MvtValue::Int(i) => MvtValueRef::Int(*i),
197        MvtValue::UInt(u) => MvtValueRef::UInt(*u),
198        MvtValue::SInt(i) => MvtValueRef::SInt(*i),
199        MvtValue::Bool(b) => MvtValueRef::Bool(*b),
200        MvtValue::Null => MvtValueRef::Null,
201    }
202}
203
204/// Column type inferred from MVT property values across all features in a layer.
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206enum InferredType {
207    Unknown,
208    Bool,
209    I64,
210    U64,
211    F32,
212    F64,
213    Str,
214}
215
216impl InferredType {
217    fn from_mvt(val: MvtValueRef<'_>) -> Self {
218        match val {
219            MvtValueRef::Bool(_) => Self::Bool,
220            MvtValueRef::Int(_) | MvtValueRef::SInt(_) => Self::I64,
221            MvtValueRef::UInt(_) => Self::U64,
222            MvtValueRef::Float(_) => Self::F32,
223            MvtValueRef::Double(_) => Self::F64,
224            MvtValueRef::String(_) => Self::Str,
225            MvtValueRef::Null => Self::Unknown,
226        }
227    }
228
229    /// Merge with another type, widening when necessary.
230    fn merge(self, other: Self) -> Self {
231        if self == Self::Unknown {
232            return other;
233        }
234        if other == Self::Unknown || self == other {
235            return self;
236        }
237        if matches!(
238            (self, other),
239            (Self::I64, Self::U64) | (Self::U64, Self::I64)
240        ) {
241            return Self::I64;
242        }
243        if matches!(
244            (self, other),
245            (Self::F32, Self::F64) | (Self::F64, Self::F32)
246        ) {
247            return Self::F64;
248        }
249        Self::Str
250    }
251
252    fn typed_null(self) -> PropValue {
253        match self {
254            Self::Unknown | Self::Str => PropValue::Str(None),
255            Self::Bool => PropValue::Bool(None),
256            Self::I64 => PropValue::I64(None),
257            Self::U64 => PropValue::U64(None),
258            Self::F32 => PropValue::F32(None),
259            Self::F64 => PropValue::F64(None),
260        }
261    }
262
263    /// Convert an owned [`MvtValue`] into a [`PropValue`] matching this column type.
264    fn convert(self, val: MvtValue) -> PropValue {
265        match (self, val) {
266            (_, MvtValue::Null) => self.typed_null(),
267            (Self::Bool, MvtValue::Bool(b)) => PropValue::Bool(Some(b)),
268            (Self::I64, MvtValue::Int(i) | MvtValue::SInt(i)) => PropValue::I64(Some(i)),
269            (Self::I64, MvtValue::UInt(u)) if i64::try_from(u).is_ok() => {
270                // Value must be within 0..i64::MAX
271                #[expect(clippy::cast_possible_wrap, reason = "checked above")]
272                PropValue::I64(Some(u as i64))
273            }
274            (Self::U64, MvtValue::UInt(u)) => PropValue::U64(Some(u)),
275            (Self::F32, MvtValue::Float(f)) => PropValue::F32(Some(f)),
276            (Self::F64, MvtValue::Double(f)) => PropValue::F64(Some(f)),
277            (Self::F64, MvtValue::Float(f)) => PropValue::F64(Some(f64::from(f))),
278            (_, MvtValue::String(s)) => PropValue::Str(Some(s)),
279            // A column that mixes types keeps every value as its text form.
280            (_, MvtValue::Bool(b)) => PropValue::Str(Some(b.to_string())),
281            (_, MvtValue::Int(i) | MvtValue::SInt(i)) => PropValue::Str(Some(i.to_string())),
282            (_, MvtValue::UInt(u)) => PropValue::Str(Some(u.to_string())),
283            (_, MvtValue::Float(f)) => PropValue::Str(Some(f.to_string())),
284            (_, MvtValue::Double(f)) => PropValue::Str(Some(f.to_string())),
285        }
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use std::num::NonZeroU32;
292
293    use rstest::rstest;
294
295    use super::*;
296
297    const POINT_AT_1_1: &[u64] = &[9, 2, 2];
298
299    #[rstest]
300    #[case::key_index_past_the_key_list(vec![5, 0], "MVT error: invalid key index 5")]
301    #[case::value_index_past_the_value_list(vec![0, 9], "MVT error: invalid value index 9")]
302    #[case::odd_tag_count(vec![0], "MVT error: invalid feature tags length: 1")]
303    fn malformed_tags_are_reported(#[case] tags: Vec<u32>, #[case] expected: &str) {
304        assert_eq!(
305            mvt_to_tile_layers(mvt_with_tags(&tags))
306                .expect_err("malformed tags must error")
307                .to_string(),
308            expected
309        );
310    }
311
312    #[rstest]
313    #[case::signed_then_unsigned(
314        vec![MvtValue::SInt(-1), MvtValue::UInt(7)],
315        vec![PropValue::I64(Some(-1)), PropValue::I64(Some(7))],
316    )]
317    #[case::unsigned_then_signed(
318        vec![MvtValue::UInt(7), MvtValue::SInt(-1)],
319        vec![PropValue::I64(Some(7)), PropValue::I64(Some(-1))],
320    )]
321    #[case::int_then_sint(
322        vec![MvtValue::Int(i64::MIN), MvtValue::SInt(3)],
323        vec![PropValue::I64(Some(i64::MIN)), PropValue::I64(Some(3))],
324    )]
325    #[case::unsigned_only(
326        vec![MvtValue::UInt(7), MvtValue::UInt(u64::MAX)],
327        vec![PropValue::U64(Some(7)), PropValue::U64(Some(u64::MAX))],
328    )]
329    #[case::float_only(
330        vec![MvtValue::Float(1.5)],
331        vec![PropValue::F32(Some(1.5))],
332    )]
333    #[case::float_then_double(
334        vec![MvtValue::Float(1.5), MvtValue::Double(2.5)],
335        vec![PropValue::F64(Some(1.5)), PropValue::F64(Some(2.5))],
336    )]
337    #[case::double_then_float(
338        vec![MvtValue::Double(2.5), MvtValue::Float(1.5)],
339        vec![PropValue::F64(Some(2.5)), PropValue::F64(Some(1.5))],
340    )]
341    #[case::bool_only(
342        vec![MvtValue::Bool(true), MvtValue::Bool(false)],
343        vec![PropValue::Bool(Some(true)), PropValue::Bool(Some(false))],
344    )]
345    #[case::string_only(
346        vec![MvtValue::String("x".into())],
347        vec![PropValue::Str(Some("x".into()))],
348    )]
349    #[case::bool_then_int(
350        vec![MvtValue::Bool(true), MvtValue::Int(5)],
351        vec![PropValue::Str(Some("true".into())), PropValue::Str(Some("5".into()))],
352    )]
353    #[case::double_then_string(
354        vec![MvtValue::Double(1.5), MvtValue::String("x".into())],
355        vec![PropValue::Str(Some("1.5".into())), PropValue::Str(Some("x".into()))],
356    )]
357    #[case::null_between_bools(
358        vec![MvtValue::Bool(true), MvtValue::Null, MvtValue::Bool(false)],
359        vec![
360            PropValue::Bool(Some(true)),
361            PropValue::Bool(None),
362            PropValue::Bool(Some(false)),
363        ],
364    )]
365    #[case::null_only(
366        vec![MvtValue::Null],
367        vec![PropValue::Str(None)],
368    )]
369    fn a_column_takes_the_type_that_holds_every_value(
370        #[case] values: Vec<MvtValue>,
371        #[case] expected: Vec<PropValue>,
372    ) {
373        assert_eq!(single_column(&values), expected);
374    }
375
376    #[test]
377    fn an_unsigned_value_past_i64_max_clashes_with_its_signed_column() {
378        let data = mvt_with_values(&[MvtValue::SInt(-1), MvtValue::UInt(u64::MAX)]);
379        assert_eq!(
380            mvt_to_tile_layers(&data)
381                .expect_err("the string fallback leaves the column mixed")
382                .to_string(),
383            "property 0 kind mismatch: expected I64, got Str"
384        );
385    }
386
387    #[test]
388    fn missing_properties_become_typed_nulls() {
389        let messages: Vec<Vec<u8>> = [
390            MvtValue::Int(1),
391            MvtValue::UInt(2),
392            MvtValue::Float(1.5),
393            MvtValue::Double(2.5),
394            MvtValue::Bool(true),
395            MvtValue::String("x".into()),
396        ]
397        .iter()
398        .map(value_message)
399        .collect();
400        let every_key: Vec<u32> = (0..6).flat_map(|i| [i, i]).collect();
401        let data = mvt_tile(&[layer_body(
402            b"l",
403            4096,
404            &[
405                b"i".as_slice(),
406                b"u".as_slice(),
407                b"f".as_slice(),
408                b"d".as_slice(),
409                b"b".as_slice(),
410                b"s".as_slice(),
411            ],
412            &messages,
413            &[(None, &every_key, POINT_AT_1_1), (None, &[], POINT_AT_1_1)],
414        )]);
415
416        let layers = both_paths(&data);
417        let [layer] = &layers[..] else {
418            panic!("expected one layer, got {}", layers.len())
419        };
420        assert_eq!(layer.property_names(), ["i", "u", "f", "d", "b", "s"]);
421        assert_eq!(
422            layer.features()[0].properties(),
423            [
424                PropValue::I64(Some(1)),
425                PropValue::U64(Some(2)),
426                PropValue::F32(Some(1.5)),
427                PropValue::F64(Some(2.5)),
428                PropValue::Bool(Some(true)),
429                PropValue::Str(Some("x".into())),
430            ]
431        );
432        assert_eq!(
433            layer.features()[1].properties(),
434            [
435                PropValue::I64(None),
436                PropValue::U64(None),
437                PropValue::F32(None),
438                PropValue::F64(None),
439                PropValue::Bool(None),
440                PropValue::Str(None),
441            ]
442        );
443    }
444
445    #[test]
446    fn column_order_follows_first_use_not_the_key_list() {
447        let messages: Vec<Vec<u8>> = [
448            MvtValue::String("1".into()),
449            MvtValue::String("2".into()),
450            MvtValue::String("3".into()),
451        ]
452        .iter()
453        .map(value_message)
454        .collect();
455        let data = mvt_tile(&[layer_body(
456            b"l",
457            4096,
458            &[b"b".as_slice(), b"a".as_slice(), b"c".as_slice()],
459            &messages,
460            &[
461                (None, &[2, 2, 0, 0], POINT_AT_1_1),
462                (None, &[1, 1], POINT_AT_1_1),
463            ],
464        )]);
465
466        let layers = both_paths(&data);
467        let [layer] = &layers[..] else {
468            panic!("expected one layer, got {}", layers.len())
469        };
470        assert_eq!(layer.property_names(), ["c", "b", "a"]);
471        assert_eq!(
472            layer.features()[0].properties(),
473            [
474                PropValue::Str(Some("3".into())),
475                PropValue::Str(Some("1".into())),
476                PropValue::Str(None),
477            ]
478        );
479        assert_eq!(
480            layer.features()[1].properties(),
481            [
482                PropValue::Str(None),
483                PropValue::Str(None),
484                PropValue::Str(Some("2".into())),
485            ]
486        );
487    }
488
489    #[test]
490    fn each_layer_keeps_its_own_name_extent_and_columns() {
491        let layers = both_paths(&two_layer_tile());
492        assert_eq!(
493            layers
494                .iter()
495                .map(|layer| (
496                    layer.name(),
497                    layer.extent().get(),
498                    layer.property_names().to_vec()
499                ))
500                .collect::<Vec<_>>(),
501            [
502                ("roads", 4096, vec!["n".to_string()]),
503                ("water", 512, vec!["z".to_string()]),
504            ]
505        );
506        assert_eq!(
507            layers[0].features()[0].properties(),
508            [PropValue::Str(Some("a".into()))]
509        );
510        assert_eq!(
511            layers[1].features()[0].properties(),
512            [PropValue::I64(Some(3))]
513        );
514    }
515
516    #[test]
517    fn a_layer_without_features_has_no_columns() {
518        let data = mvt_tile(&[layer_body(b"l", 4096, &[], &[], &[])]);
519        let layers = both_paths(&data);
520        let [layer] = &layers[..] else {
521            panic!("expected one layer, got {}", layers.len())
522        };
523        let no_names: [&str; 0] = [];
524        assert_eq!(layer.property_names(), no_names);
525        assert_eq!(layer.feature_count(), 0);
526    }
527
528    #[test]
529    fn feature_ids_survive_both_conversions() {
530        let data = mvt_tile(&[layer_body(
531            b"l",
532            4096,
533            &[],
534            &[],
535            &[(Some(7), &[], POINT_AT_1_1), (None, &[], POINT_AT_1_1)],
536        )]);
537        let layers = both_paths(&data);
538        assert_eq!(
539            layers[0]
540                .features()
541                .iter()
542                .map(TileFeature::id)
543                .collect::<Vec<_>>(),
544            [Some(7), None]
545        );
546    }
547
548    #[test]
549    fn a_zero_extent_is_rejected() {
550        let data = mvt_tile(&[layer_body(b"l", 0, &[], &[], &[])]);
551        assert_eq!(
552            mvt_to_tile_layers(&data)
553                .expect_err("zero extent must error")
554                .to_string(),
555            "invalid extent: 0"
556        );
557    }
558
559    #[test]
560    fn a_truncated_geometry_is_reported() {
561        let data = mvt_tile(&[layer_body(b"l", 4096, &[], &[], &[(None, &[], &[9])])]);
562        assert_eq!(
563            mvt_to_tile_layers(&data)
564                .expect_err("truncated geometry must error")
565                .to_string(),
566            "MVT error: invalid geometry command stream"
567        );
568    }
569
570    #[test]
571    fn a_layer_without_a_name_is_rejected() {
572        let unnamed = mvt_layer(b"", &[vec![]], b"k", &[]);
573        assert_eq!(
574            mvt_to_tile_layers(&unnamed)
575                .expect_err("borrowed conversion must error")
576                .to_string(),
577            "MVT error: missing required layer name"
578        );
579        assert_eq!(
580            mvt_to_feature_collection(&unnamed)
581                .expect_err("feature collection must error")
582                .to_string(),
583            "MVT error: missing required layer name"
584        );
585
586        let hand_built = MvtLayer {
587            name: String::new(),
588            extent: NonZeroU32::new(4096).expect("non-zero"),
589            features: Vec::new(),
590        };
591        assert_eq!(
592            TileLayer::try_from(hand_built)
593                .expect_err("owned conversion must error")
594                .to_string(),
595            "missing layer name"
596        );
597    }
598
599    #[test]
600    fn unreadable_tile_bytes_are_reported() {
601        assert!(matches!(
602            mvt_to_tile_layers([0xff_u8]),
603            Err(MltError::Mvt(_))
604        ));
605        assert!(matches!(
606            mvt_to_feature_collection([0xff_u8]),
607            Err(MltError::Mvt(_))
608        ));
609    }
610
611    #[test]
612    fn an_empty_tile_holds_no_layers_and_no_features() {
613        assert_eq!(
614            mvt_to_tile_layers([0_u8; 0]).expect("convert"),
615            Vec::<TileLayer>::new()
616        );
617        insta::assert_snapshot!(
618            serde_json::to_string(&mvt_to_feature_collection([0_u8; 0]).expect("convert"))
619                .expect("serialize"),
620            @r#"{"type":"FeatureCollection","features":[]}"#
621        );
622    }
623
624    #[test]
625    fn every_feature_carries_its_own_layer_name_and_extent() {
626        insta::assert_snapshot!(
627            serde_json::to_string(&mvt_to_feature_collection(two_layer_tile()).expect("convert"))
628                .expect("serialize"),
629            @r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"_extent":4096,"_layer":"roads","n":"a"},"geometry":{"type":"Point","coordinates":[1,1]}},{"type":"Feature","properties":{"_extent":512,"_layer":"water","z":3},"geometry":{"type":"Point","coordinates":[1,1]}}]}"#
630        );
631    }
632
633    #[test]
634    fn a_non_finite_double_cannot_become_geojson() {
635        assert_eq!(
636            mvt_to_feature_collection(mvt_with_values(&[MvtValue::Double(f64::NAN)]))
637                .expect_err("NaN must error")
638                .to_string(),
639            "MVT JSON value error: non-finite float cannot be represented as JSON"
640        );
641    }
642
643    /// Convert a one-column tile through both paths and return that column's value per feature.
644    fn single_column(values: &[MvtValue]) -> Vec<PropValue> {
645        let data = mvt_with_values(values);
646        let layers = both_paths(&data);
647        let [layer] = &layers[..] else {
648            panic!("expected one layer, got {}", layers.len())
649        };
650        assert_eq!(layer.property_names(), ["k"]);
651        layer
652            .features()
653            .iter()
654            .map(|feat| feat.properties()[0].clone())
655            .collect()
656    }
657
658    /// Convert through the borrowed and the owned path, asserting they agree.
659    fn both_paths(data: &[u8]) -> Vec<TileLayer> {
660        let from_ref = mvt_to_tile_layers(data).expect("borrowed conversion");
661        let owned: Vec<TileLayer> = MvtReaderRef::new(data)
662            .expect("read")
663            .to_tile()
664            .expect("to_tile")
665            .layers
666            .into_iter()
667            .map(TileLayer::try_from)
668            .collect::<MltResult<_>>()
669            .expect("owned conversion");
670        assert_eq!(from_ref, owned);
671        from_ref
672    }
673
674    /// Two layers of one point feature each, differing in name, extent, key and value type.
675    fn two_layer_tile() -> Vec<u8> {
676        mvt_tile(&[
677            layer_body(
678                b"roads",
679                4096,
680                &[b"n".as_slice()],
681                &[value_message(&MvtValue::String("a".into()))],
682                &[(None, &[0, 0], POINT_AT_1_1)],
683            ),
684            layer_body(
685                b"water",
686                512,
687                &[b"z".as_slice()],
688                &[value_message(&MvtValue::Int(3))],
689                &[(None, &[0, 0], POINT_AT_1_1)],
690            ),
691        ])
692    }
693
694    fn field(number: u32, wire: u32) -> u8 {
695        u8::try_from((number << 3) | wire).expect("small field number")
696    }
697
698    fn varint(mut value: u64, out: &mut Vec<u8>) {
699        loop {
700            let byte = u8::try_from(value & 0x7f).expect("masked");
701            value >>= 7;
702            if value == 0 {
703                out.push(byte);
704                return;
705            }
706            out.push(byte | 0x80);
707        }
708    }
709
710    fn packed(number: u32, values: &[u64], out: &mut Vec<u8>) {
711        let mut body = Vec::new();
712        for value in values {
713            varint(*value, &mut body);
714        }
715        out.push(field(number, 2));
716        varint(u64::try_from(body.len()).expect("small"), out);
717        out.extend(&body);
718    }
719
720    fn bytes(number: u32, body: &[u8], out: &mut Vec<u8>) {
721        out.push(field(number, 2));
722        varint(u64::try_from(body.len()).expect("small"), out);
723        out.extend(body);
724    }
725
726    /// One `Value` message, its field number picking the type the reader infers.
727    fn value_message(value: &MvtValue) -> Vec<u8> {
728        let mut out = Vec::new();
729        match value {
730            MvtValue::String(s) => bytes(1, s.as_bytes(), &mut out),
731            MvtValue::Float(f) => {
732                out.push(field(2, 5));
733                out.extend(f.to_le_bytes());
734            }
735            MvtValue::Double(f) => {
736                out.push(field(3, 1));
737                out.extend(f.to_le_bytes());
738            }
739            MvtValue::Int(i) => {
740                out.push(field(4, 0));
741                varint(i.cast_unsigned(), &mut out);
742            }
743            MvtValue::UInt(u) => {
744                out.push(field(5, 0));
745                varint(*u, &mut out);
746            }
747            MvtValue::SInt(i) => {
748                out.push(field(6, 0));
749                varint(zigzag::ZigZag::encode(*i), &mut out);
750            }
751            MvtValue::Bool(b) => {
752                out.push(field(7, 0));
753                varint(u64::from(*b), &mut out);
754            }
755            // A `Value` with no field set is what the reader reports as `Null`.
756            MvtValue::Null => {}
757        }
758        out
759    }
760
761    /// Tile of one layer holding one point feature per `values` entry, all under key `k`.
762    fn mvt_with_values(values: &[MvtValue]) -> Vec<u8> {
763        let tags: Vec<Vec<u32>> = (0..values.len())
764            .map(|i| vec![0, u32::try_from(i).expect("small")])
765            .collect();
766        let messages: Vec<Vec<u8>> = values.iter().map(value_message).collect();
767        mvt_layer(b"l", &tags, b"k", &messages)
768    }
769
770    /// Minimal hand-written MVT tile with one point feature carrying `tags`.
771    fn mvt_with_tags(tags: &[u32]) -> Vec<u8> {
772        mvt_layer(b"l", &[tags.to_vec()], b"k", &[])
773    }
774
775    /// Tile of one default-extent layer with a single key and one point feature per tag list.
776    fn mvt_layer(
777        name: &[u8],
778        feature_tags: &[Vec<u32>],
779        key: &[u8],
780        values: &[Vec<u8>],
781    ) -> Vec<u8> {
782        let features: Vec<(Option<u64>, &[u32], &[u64])> = feature_tags
783            .iter()
784            .map(|tags| (None, tags.as_slice(), POINT_AT_1_1))
785            .collect();
786        mvt_tile(&[layer_body(name, 4096, &[key], values, &features)])
787    }
788
789    fn mvt_tile(layers: &[Vec<u8>]) -> Vec<u8> {
790        let mut tile = Vec::new();
791        for layer in layers {
792            bytes(3, layer, &mut tile);
793        }
794        tile
795    }
796
797    /// One `Layer` message, each feature given as its id, its tags and its geometry commands.
798    fn layer_body(
799        name: &[u8],
800        extent: u32,
801        keys: &[&[u8]],
802        values: &[Vec<u8>],
803        features: &[(Option<u64>, &[u32], &[u64])],
804    ) -> Vec<u8> {
805        let mut layer = Vec::new();
806        layer.push(field(15, 0));
807        varint(2, &mut layer); // version
808        bytes(1, name, &mut layer);
809        for (id, tags, geometry) in features {
810            let mut feat = Vec::new();
811            if let Some(id) = id {
812                feat.push(field(1, 0));
813                varint(*id, &mut feat);
814            }
815            packed(
816                2,
817                &tags.iter().copied().map(u64::from).collect::<Vec<_>>(),
818                &mut feat,
819            );
820            feat.push(field(3, 0));
821            varint(1, &mut feat); // POINT
822            packed(4, geometry, &mut feat);
823            bytes(2, &feat, &mut layer);
824        }
825        for key in keys {
826            bytes(3, key, &mut layer);
827        }
828        for value in values {
829            bytes(4, value, &mut layer);
830        }
831        layer.push(field(5, 0));
832        varint(u64::from(extent), &mut layer);
833        layer
834    }
835}