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