mlt_core/convert/mvt/
decode.rs1use std::collections::{BTreeMap, HashMap};
4
5use fast_mvt::{MvtLayer, MvtReaderRef, MvtValue};
6use serde_json::Value;
7
8use crate::decoder::{PropValue, TileFeature, TileLayer};
9use crate::geojson::{Feature, FeatureCollection};
10use crate::{MltError, MltResult};
11
12fn read_mvt_layers(data: &[u8]) -> MltResult<Vec<MvtLayer>> {
17 let layers = MvtReaderRef::new(data)?.to_tile()?.layers;
18 if layers.iter().any(|layer| layer.name.is_empty()) {
19 return Err(MltError::MissingLayerName);
20 }
21 Ok(layers)
22}
23
24pub fn mvt_to_feature_collection(data: impl AsRef<[u8]>) -> MltResult<FeatureCollection> {
26 let mut features = Vec::new();
27
28 for layer in read_mvt_layers(data.as_ref())? {
29 for feat in layer.features {
30 let mut properties = feat
31 .properties
32 .into_iter()
33 .map(|(k, v)| Ok((k, Value::try_from(v)?)))
34 .collect::<MltResult<BTreeMap<_, _>>>()?;
35 properties.insert("_layer".into(), Value::String(layer.name.clone()));
36 properties.insert("_extent".into(), Value::Number(layer.extent.get().into()));
37 features.push(Feature {
38 geometry: feat.geometry,
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
52pub fn mvt_to_tile_layers(data: impl AsRef<[u8]>) -> MltResult<Vec<TileLayer>> {
59 read_mvt_layers(data.as_ref())?
60 .into_iter()
61 .map(TileLayer::try_from)
62 .collect::<Result<Vec<_>, _>>()
63}
64
65impl TryFrom<MvtLayer> for TileLayer {
66 type Error = MltError;
67
68 fn try_from(layer: MvtLayer) -> Result<Self, Self::Error> {
69 if layer.name.is_empty() {
70 return Err(MltError::MissingLayerName);
71 }
72
73 let mut col_names: Vec<String> = Vec::new();
75 let mut col_index: HashMap<String, usize> = HashMap::new();
76 let mut col_types: Vec<InferredType> = Vec::new();
77
78 for feat in &layer.features {
79 for (key, val) in &feat.properties {
80 let idx = *col_index.entry(key.clone()).or_insert_with(|| {
81 let i = col_names.len();
82 col_names.push(key.clone());
83 col_types.push(InferredType::Unknown);
84 i
85 });
86 col_types[idx] = col_types[idx].merge(InferredType::from_mvt(val));
87 }
88 }
89
90 for t in &mut col_types {
92 if *t == InferredType::Unknown {
93 *t = InferredType::Str;
94 }
95 }
96
97 let mut tile_features = Vec::with_capacity(layer.features.len());
99 for feat in layer.features {
100 let mut properties: Vec<PropValue> = col_types.iter().map(|t| t.typed_null()).collect();
102 for (key, val) in feat.properties {
103 if let Some(&idx) = col_index.get(&key)
104 && !matches!(val, MvtValue::Null)
105 {
106 properties[idx] = col_types[idx].convert(val);
107 }
108 }
109 tile_features.push(TileFeature {
110 id: feat.id,
111 geometry: feat.geometry,
112 properties,
113 });
114 }
115
116 Self::from_parts(layer.name, layer.extent.get(), col_names, tile_features)
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122enum InferredType {
123 Unknown,
124 Bool,
125 I64,
126 U64,
127 F32,
128 F64,
129 Str,
130}
131
132impl InferredType {
133 fn from_mvt(val: &MvtValue) -> Self {
134 match val {
135 MvtValue::Bool(_) => Self::Bool,
136 MvtValue::Int(_) | MvtValue::SInt(_) => Self::I64,
137 MvtValue::UInt(_) => Self::U64,
138 MvtValue::Float(_) => Self::F32,
139 MvtValue::Double(_) => Self::F64,
140 MvtValue::String(_) => Self::Str,
141 MvtValue::Null => Self::Unknown,
142 }
143 }
144
145 fn merge(self, other: Self) -> Self {
147 if self == Self::Unknown {
148 return other;
149 }
150 if other == Self::Unknown || self == other {
151 return self;
152 }
153 if matches!(
154 (self, other),
155 (Self::I64, Self::U64) | (Self::U64, Self::I64)
156 ) {
157 return Self::I64;
158 }
159 if matches!(
160 (self, other),
161 (Self::F32, Self::F64) | (Self::F64, Self::F32)
162 ) {
163 return Self::F64;
164 }
165 Self::Str
166 }
167
168 fn typed_null(self) -> PropValue {
169 match self {
170 Self::Unknown | Self::Str => PropValue::Str(None),
171 Self::Bool => PropValue::Bool(None),
172 Self::I64 => PropValue::I64(None),
173 Self::U64 => PropValue::U64(None),
174 Self::F32 => PropValue::F32(None),
175 Self::F64 => PropValue::F64(None),
176 }
177 }
178
179 fn convert(self, val: MvtValue) -> PropValue {
181 match (self, val) {
182 (_, MvtValue::Null) => self.typed_null(),
183 (Self::Bool, MvtValue::Bool(b)) => PropValue::Bool(Some(b)),
184 (Self::I64, MvtValue::Int(i) | MvtValue::SInt(i)) => PropValue::I64(Some(i)),
185 (Self::I64, MvtValue::UInt(u)) if i64::try_from(u).is_ok() => {
186 #[expect(clippy::cast_possible_wrap, reason = "checked above")]
188 PropValue::I64(Some(u as i64))
189 }
190 (Self::U64, MvtValue::UInt(u)) => PropValue::U64(Some(u)),
191 (Self::F32, MvtValue::Float(f)) => PropValue::F32(Some(f)),
192 (Self::F64, MvtValue::Double(f)) => PropValue::F64(Some(f)),
193 (Self::F64, MvtValue::Float(f)) => PropValue::F64(Some(f64::from(f))),
194 (_, MvtValue::String(s)) => PropValue::Str(Some(s)),
195 (_, v) => PropValue::Str(Some(format!("{v:?}"))),
197 }
198 }
199}