Skip to main content

mlt_core/
tile.rs

1//! Owned, row-oriented tile model.
2//!
3//! This is the representation tiles are built in and converted to/from:
4//! it is independent of the wire format, and is shared by the encoder, the MVT and
5//! `GeoJSON` converters, and the language bindings.
6//! The decoder's columnar types live in [`crate::decoder`].
7
8use std::num::NonZeroU32;
9
10use crate::{MltError, MltResult};
11
12/// Non-zero tile extent.
13///
14/// Use [`Extent::new`] to validate raw integer input before storing it in
15/// owned row or staged layer structures.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct Extent(NonZeroU32);
18
19impl Extent {
20    pub fn new(value: u32) -> MltResult<Self> {
21        NonZeroU32::new(value)
22            .map(Self)
23            .ok_or(MltError::InvalidExtent(value))
24    }
25
26    #[must_use]
27    pub fn get(self) -> u32 {
28        self.0.get()
29    }
30}
31
32impl From<Extent> for NonZeroU32 {
33    fn from(value: Extent) -> Self {
34        value.0
35    }
36}
37
38/// Row-oriented working form for the optimizer.
39///
40/// All features are stored as a flat [`Vec<TileFeature>`] so that sorting is
41/// a single `sort_by_cached_key` call.  The `property_names` vec is parallel
42/// to every `TileFeature::properties` slice in this layer.
43#[derive(Debug, Clone, PartialEq)]
44pub struct TileLayer {
45    pub(crate) name: String,
46    pub(crate) extent: Extent,
47    /// Column names, parallel to `TileFeature::properties`.
48    pub(crate) property_names: Vec<String>,
49    /// Column types, parallel to `TileFeature::properties`.
50    pub(crate) property_kinds: Vec<PropKind>,
51    pub(crate) features: Vec<TileFeature>,
52}
53
54/// A single map feature in row form.
55#[derive(Debug, Clone, PartialEq)]
56pub struct TileFeature {
57    pub(crate) id: Option<u64>,
58    /// Geometry as a [`geo_types`] form
59    pub(crate) geometry: geo_types::Geometry<i32>,
60    /// One value per property column, in the same order as
61    /// [`TileLayer::property_names`].
62    pub(crate) properties: Vec<PropValue>,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub struct PropertyKey(usize);
67
68impl PropertyKey {
69    #[must_use]
70    pub fn index(self) -> usize {
71        self.0
72    }
73}
74
75impl TileLayer {
76    pub fn new(name: impl Into<String>, extent: u32) -> MltResult<Self> {
77        Self::with_capacity(name, extent, 0)
78    }
79
80    pub fn with_capacity(name: impl Into<String>, extent: u32, features: usize) -> MltResult<Self> {
81        let name = name.into();
82        validate_layer_name(&name)?;
83        let extent = Extent::new(extent)?;
84        Ok(Self {
85            name,
86            extent,
87            property_names: Vec::new(),
88            property_kinds: Vec::new(),
89            features: Vec::with_capacity(features),
90        })
91    }
92
93    pub(crate) fn from_parts(
94        name: impl Into<String>,
95        extent: u32,
96        property_names: Vec<String>,
97        features: Vec<TileFeature>,
98    ) -> MltResult<Self> {
99        let name = name.into();
100        validate_layer_name(&name)?;
101        let extent = Extent::new(extent)?;
102        validate_property_names(&property_names)?;
103        let property_kinds = infer_property_kinds(&property_names, &features)?;
104        let layer = Self {
105            name,
106            extent,
107            property_names,
108            property_kinds,
109            features,
110        };
111        Ok(layer)
112    }
113
114    #[must_use]
115    pub fn name(&self) -> &str {
116        &self.name
117    }
118
119    #[must_use]
120    pub fn extent(&self) -> Extent {
121        self.extent
122    }
123
124    #[must_use]
125    pub fn property_names(&self) -> &[String] {
126        &self.property_names
127    }
128
129    #[must_use]
130    pub fn features(&self) -> &[TileFeature] {
131        &self.features
132    }
133
134    #[must_use]
135    pub(crate) fn features_mut(&mut self) -> &mut [TileFeature] {
136        &mut self.features
137    }
138
139    #[must_use]
140    pub fn feature_count(&self) -> usize {
141        self.features.len()
142    }
143
144    pub fn add_property(
145        &mut self,
146        name: impl Into<String>,
147        kind: PropKind,
148    ) -> MltResult<PropertyKey> {
149        let name = name.into();
150        if self.property_names.contains(&name) {
151            return Err(MltError::DuplicatePropertyName(name));
152        }
153        for feature in &mut self.features {
154            feature.properties.push(PropValue::null(kind));
155        }
156        self.property_names.push(name);
157        self.property_kinds.push(kind);
158        Ok(PropertyKey(self.property_names.len() - 1))
159    }
160
161    pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
162        self.validate_feature(&feature)?;
163        self.features.push(feature);
164        Ok(())
165    }
166
167    pub fn builder(name: impl Into<String>, extent: u32) -> MltResult<TileLayerBuilder> {
168        Ok(TileLayerBuilder {
169            layer: Self::new(name, extent)?,
170        })
171    }
172
173    fn validate_feature(&self, feature: &TileFeature) -> MltResult<()> {
174        let expected = self.property_names.len();
175        let actual = feature.properties.len();
176        if actual != expected {
177            return Err(MltError::PropertyLengthMismatch { expected, actual });
178        }
179        for (idx, prop) in feature.properties.iter().enumerate() {
180            let expected = self.property_kinds[idx];
181            let actual = PropKind::from(prop);
182            if actual != expected {
183                return Err(MltError::PropertyKindMismatch {
184                    index: idx,
185                    expected,
186                    actual,
187                });
188            }
189        }
190        Ok(())
191    }
192}
193
194impl TileFeature {
195    #[must_use]
196    pub fn new(geometry: geo_types::Geometry<i32>) -> Self {
197        Self {
198            id: None,
199            geometry,
200            properties: Vec::new(),
201        }
202    }
203
204    #[must_use]
205    pub fn with_id(geometry: geo_types::Geometry<i32>, id: u64) -> Self {
206        Self {
207            id: Some(id),
208            geometry,
209            properties: Vec::new(),
210        }
211    }
212
213    #[must_use]
214    pub fn id(&self) -> Option<u64> {
215        self.id
216    }
217
218    #[must_use]
219    pub fn geometry(&self) -> &geo_types::Geometry<i32> {
220        &self.geometry
221    }
222
223    #[must_use]
224    pub fn properties(&self) -> &[PropValue] {
225        &self.properties
226    }
227
228    #[must_use]
229    pub(crate) fn properties_mut(&mut self) -> &mut [PropValue] {
230        &mut self.properties
231    }
232
233    pub fn set_property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<()> {
234        let Some(prop) = self.properties.get_mut(key.index()) else {
235            return Err(MltError::PropertyLengthMismatch {
236                expected: key.index() + 1,
237                actual: self.properties.len(),
238            });
239        };
240        let expected = PropKind::from(&*prop);
241        let actual = PropKind::from(&value);
242        if actual != expected {
243            return Err(MltError::PropertyKindMismatch {
244                index: key.index(),
245                expected,
246                actual,
247            });
248        }
249        *prop = value;
250        Ok(())
251    }
252}
253
254pub struct TileLayerBuilder {
255    layer: TileLayer,
256}
257
258impl TileLayerBuilder {
259    pub fn add_property(
260        &mut self,
261        name: impl Into<String>,
262        kind: PropKind,
263    ) -> MltResult<PropertyKey> {
264        self.layer.add_property(name, kind)
265    }
266
267    pub fn feature(&mut self, geometry: geo_types::Geometry<i32>) -> TileFeatureBuilder<'_> {
268        let properties = self
269            .layer
270            .property_kinds
271            .iter()
272            .copied()
273            .map(PropValue::null)
274            .collect();
275        TileFeatureBuilder {
276            layer: self,
277            feature: TileFeature {
278                id: None,
279                geometry,
280                properties,
281            },
282        }
283    }
284
285    pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
286        self.layer.push_feature(feature)
287    }
288
289    #[must_use]
290    pub fn finish(self) -> TileLayer {
291        self.layer
292    }
293}
294
295pub struct TileFeatureBuilder<'a> {
296    layer: &'a mut TileLayerBuilder,
297    feature: TileFeature,
298}
299
300impl TileFeatureBuilder<'_> {
301    pub fn id(&mut self, id: Option<u64>) -> &mut Self {
302        self.feature.id = id;
303        self
304    }
305
306    pub fn property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<&mut Self> {
307        self.feature.set_property(key, value)?;
308        Ok(self)
309    }
310
311    pub fn finish(self) -> MltResult<()> {
312        self.layer.push_feature(self.feature)
313    }
314}
315
316/// A single typed value for one property of one feature.
317///
318/// Mirrors the scalar variants of `ParsedProperty` at the per-feature
319/// level. `SharedDict` items are flattened: each sub-field becomes its own
320/// `PropValue::Str` entry in `TileFeature::properties`, with the
321/// corresponding entry in `TileLayer::property_names` set to
322/// `"prefix:suffix"`.
323#[derive(Debug, Clone, PartialEq)]
324pub enum PropValue {
325    Bool(Option<bool>),
326    I8(Option<i8>),
327    U8(Option<u8>),
328    I32(Option<i32>),
329    U32(Option<u32>),
330    I64(Option<i64>),
331    U64(Option<u64>),
332    F32(Option<f32>),
333    F64(Option<f64>),
334    Str(Option<String>),
335}
336
337impl PropValue {
338    #[must_use]
339    pub fn kind(&self) -> PropKind {
340        self.into()
341    }
342
343    #[must_use]
344    pub fn is_null(&self) -> bool {
345        match self {
346            Self::Bool(v) => v.is_none(),
347            Self::I8(v) => v.is_none(),
348            Self::U8(v) => v.is_none(),
349            Self::I32(v) => v.is_none(),
350            Self::U32(v) => v.is_none(),
351            Self::I64(v) => v.is_none(),
352            Self::U64(v) => v.is_none(),
353            Self::F32(v) => v.is_none(),
354            Self::F64(v) => v.is_none(),
355            Self::Str(v) => v.is_none(),
356        }
357    }
358
359    #[must_use]
360    pub fn null(kind: PropKind) -> Self {
361        match kind {
362            PropKind::Bool => Self::Bool(None),
363            PropKind::I8 => Self::I8(None),
364            PropKind::U8 => Self::U8(None),
365            PropKind::I32 => Self::I32(None),
366            PropKind::U32 => Self::U32(None),
367            PropKind::I64 => Self::I64(None),
368            PropKind::U64 => Self::U64(None),
369            PropKind::F32 => Self::F32(None),
370            PropKind::F64 => Self::F64(None),
371            PropKind::Str => Self::Str(None),
372        }
373    }
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)]
377#[strum(serialize_all = "lowercase")]
378pub enum PropKind {
379    Bool,
380    I8,
381    U8,
382    I32,
383    U32,
384    I64,
385    U64,
386    F32,
387    F64,
388    Str,
389}
390
391impl From<&PropValue> for PropKind {
392    fn from(prop: &PropValue) -> Self {
393        match prop {
394            PropValue::Bool(_) => Self::Bool,
395            PropValue::I8(_) => Self::I8,
396            PropValue::U8(_) => Self::U8,
397            PropValue::I32(_) => Self::I32,
398            PropValue::U32(_) => Self::U32,
399            PropValue::I64(_) => Self::I64,
400            PropValue::U64(_) => Self::U64,
401            PropValue::F32(_) => Self::F32,
402            PropValue::F64(_) => Self::F64,
403            PropValue::Str(_) => Self::Str,
404        }
405    }
406}
407
408fn validate_layer_name(name: &str) -> MltResult<()> {
409    if name.is_empty() {
410        Err(MltError::MissingLayerName)
411    } else {
412        Ok(())
413    }
414}
415
416fn validate_property_names(names: &[String]) -> MltResult<()> {
417    // Linear scan, not a HashSet: column counts are small, so this skips a per-layer alloc.
418    // Empty names are allowed; real MVT tiles contain them.
419    for (i, name) in names.iter().enumerate() {
420        if names[..i].iter().any(|n| n == name) {
421            return Err(MltError::DuplicatePropertyName(name.clone()));
422        }
423    }
424    Ok(())
425}
426
427fn infer_property_kinds(names: &[String], features: &[TileFeature]) -> MltResult<Vec<PropKind>> {
428    let mut kinds = vec![None; names.len()];
429    for feature in features {
430        let expected = names.len();
431        let actual = feature.properties.len();
432        if actual != expected {
433            return Err(MltError::PropertyLengthMismatch { expected, actual });
434        }
435        for (idx, prop) in feature.properties.iter().enumerate() {
436            let actual = PropKind::from(prop);
437            match kinds[idx] {
438                Some(expected) if expected != actual => {
439                    return Err(MltError::PropertyKindMismatch {
440                        index: idx,
441                        expected,
442                        actual,
443                    });
444                }
445                None => kinds[idx] = Some(actual),
446                _ => {}
447            }
448        }
449    }
450    Ok(kinds
451        .into_iter()
452        .map(|kind| kind.unwrap_or(PropKind::Str))
453        .collect())
454}
455
456#[cfg(test)]
457mod tests {
458    use geo_types::{Geometry, Point};
459
460    use super::*;
461
462    fn point_feature(properties: Vec<PropValue>) -> TileFeature {
463        TileFeature {
464            id: None,
465            geometry: Geometry::Point(Point::new(0, 0)),
466            properties,
467        }
468    }
469
470    #[test]
471    fn tile_layer_constructor_rejects_empty_name() {
472        assert!(matches!(
473            TileLayer::new("", 4096),
474            Err(MltError::MissingLayerName)
475        ));
476    }
477
478    #[test]
479    fn tile_layer_constructor_rejects_zero_extent() {
480        assert!(matches!(
481            TileLayer::new("layer", 0),
482            Err(MltError::InvalidExtent(0))
483        ));
484    }
485
486    #[test]
487    fn add_property_rejects_duplicate_names() {
488        let mut layer = TileLayer::new("layer", 4096).unwrap();
489        layer.add_property("name", PropKind::Str).unwrap();
490        assert!(matches!(
491            layer.add_property("name", PropKind::Str),
492            Err(MltError::DuplicatePropertyName(name)) if name == "name"
493        ));
494    }
495
496    #[test]
497    fn from_parts_allows_empty_property_name() {
498        // Real MVT tiles contain empty keys.
499        assert!(TileLayer::from_parts("layer", 4096, vec![String::new()], vec![]).is_ok());
500    }
501
502    #[test]
503    fn from_parts_rejects_duplicate_property_name() {
504        assert!(matches!(
505            TileLayer::from_parts("layer", 4096, vec!["dup".into(), "dup".into()], vec![]),
506            Err(MltError::DuplicatePropertyName(name)) if name == "dup"
507        ));
508    }
509
510    #[test]
511    fn push_feature_validates_property_count() {
512        let mut layer = TileLayer::new("layer", 4096).unwrap();
513        layer.add_property("name", PropKind::Str).unwrap();
514        assert!(matches!(
515            layer.push_feature(point_feature(vec![])),
516            Err(MltError::PropertyLengthMismatch {
517                expected: 1,
518                actual: 0
519            })
520        ));
521    }
522
523    #[test]
524    fn push_feature_validates_property_kind() {
525        let mut layer = TileLayer::new("layer", 4096).unwrap();
526        layer.add_property("flag", PropKind::Bool).unwrap();
527        layer
528            .push_feature(point_feature(vec![PropValue::Bool(Some(true))]))
529            .unwrap();
530        assert!(matches!(
531            layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
532            Err(MltError::PropertyKindMismatch {
533                index: 0,
534                expected: PropKind::Bool,
535                actual: PropKind::I32,
536            })
537        ));
538    }
539
540    #[test]
541    fn declared_property_kind_is_enforced_for_first_feature() {
542        let mut layer = TileLayer::new("layer", 4096).unwrap();
543        layer.add_property("flag", PropKind::Bool).unwrap();
544        assert!(matches!(
545            layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
546            Err(MltError::PropertyKindMismatch {
547                index: 0,
548                expected: PropKind::Bool,
549                actual: PropKind::I32,
550            })
551        ));
552    }
553
554    #[test]
555    fn builder_uses_declared_property_kind_for_defaults() {
556        let mut builder = TileLayer::builder("layer", 4096).unwrap();
557        let flag = builder.add_property("flag", PropKind::Bool).unwrap();
558        let mut feature = builder.feature(Geometry::Point(Point::new(0, 0)));
559        feature.property(flag, PropValue::Bool(Some(true))).unwrap();
560        feature.finish().unwrap();
561        let layer = builder.finish();
562
563        assert_eq!(
564            layer.features()[0].properties()[0],
565            PropValue::Bool(Some(true))
566        );
567    }
568}