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            // Type conflict at runtime: fall back to a debug string.
280            (_, v) => PropValue::Str(Some(format!("{v:?}"))),
281        }
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn malformed_tags_are_reported() {
291        for (tags, expected) in [
292            (&[5, 0][..], "invalid key index 5"),
293            (&[0, 9][..], "invalid value index 9"),
294            (&[0][..], "invalid feature tags length: 1"),
295        ] {
296            let err = mvt_to_tile_layers(mvt_with_tags(tags))
297                .expect_err("malformed tags must error")
298                .to_string();
299            assert!(err.contains(expected), "got {err:?}, wanted {expected:?}");
300        }
301    }
302
303    /// Minimal hand-written MVT tile with one point feature carrying `tags`.
304    fn mvt_with_tags(tags: &[u32]) -> Vec<u8> {
305        fn field(number: u32, wire: u32) -> u8 {
306            u8::try_from((number << 3) | wire).expect("small field number")
307        }
308        fn varint(mut value: u64, out: &mut Vec<u8>) {
309            loop {
310                let byte = u8::try_from(value & 0x7f).expect("masked");
311                value >>= 7;
312                if value == 0 {
313                    out.push(byte);
314                    return;
315                }
316                out.push(byte | 0x80);
317            }
318        }
319        fn packed(number: u32, values: &[u64], out: &mut Vec<u8>) {
320            let mut body = Vec::new();
321            for value in values {
322                varint(*value, &mut body);
323            }
324            out.push(field(number, 2));
325            varint(u64::try_from(body.len()).expect("small"), out);
326            out.extend(&body);
327        }
328        fn bytes(number: u32, body: &[u8], out: &mut Vec<u8>) {
329            out.push(field(number, 2));
330            varint(u64::try_from(body.len()).expect("small"), out);
331            out.extend(body);
332        }
333
334        let mut feat = Vec::new();
335        packed(
336            2,
337            &tags.iter().copied().map(u64::from).collect::<Vec<_>>(),
338            &mut feat,
339        );
340        feat.push(field(3, 0));
341        varint(1, &mut feat); // POINT
342        packed(4, &[9, 2, 2], &mut feat); // MoveTo(1, 1)
343
344        let mut layer = Vec::new();
345        layer.push(field(15, 0));
346        varint(2, &mut layer); // version
347        bytes(1, b"l", &mut layer); // name
348        bytes(2, &feat, &mut layer); // features
349        bytes(3, b"k", &mut layer); // keys
350        layer.push(field(5, 0));
351        varint(4096, &mut layer); // extent
352
353        let mut tile = Vec::new();
354        bytes(3, &layer, &mut tile);
355        tile
356    }
357}