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