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
8#[cfg(feature = "unstable-v2")]
9use std::collections::BTreeMap;
10use std::num::NonZeroU32;
11
12use geo_types::{Geometry, LineString};
13#[cfg(feature = "unstable-v2")]
14use num_traits::ToPrimitive as _;
15
16use crate::{MltError, MltResult};
17
18/// Non-zero tile extent.
19///
20/// Use [`Extent::new`] to validate raw integer input before storing it in
21/// owned row or staged layer structures.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub struct Extent(NonZeroU32);
24
25impl Extent {
26    pub fn new(value: u32) -> MltResult<Self> {
27        NonZeroU32::new(value)
28            .map(Self)
29            .ok_or(MltError::InvalidExtent(value))
30    }
31
32    #[must_use]
33    pub fn get(self) -> u32 {
34        self.0.get()
35    }
36}
37
38impl From<Extent> for NonZeroU32 {
39    fn from(value: Extent) -> Self {
40        value.0
41    }
42}
43
44/// The vertical grid a layer's z coordinates lie on, a power-of-ten step in metres.
45///
46/// A z of `0` sits at Terrain-RGB's base of -10000 m, so a coordinate's elevation is `-10000 + z * step`.
47#[cfg(feature = "unstable-v2")]
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub struct ZStep(i8);
50
51#[cfg(feature = "unstable-v2")]
52impl ZStep {
53    /// The finest step, 10^-3 m.
54    pub const MIN_EXPONENT: i8 = -3;
55    /// The coarsest step, 10^4 m.
56    pub const MAX_EXPONENT: i8 = 4;
57    /// The elevation of `z = 0`, in metres.
58    pub const BASE_METRES: f64 = -10_000.0;
59
60    /// A step of `10^exponent` metres.
61    pub fn new(exponent: i8) -> MltResult<Self> {
62        if (Self::MIN_EXPONENT..=Self::MAX_EXPONENT).contains(&exponent) {
63            Ok(Self(exponent))
64        } else {
65            Err(MltError::InvalidZStep(exponent))
66        }
67    }
68
69    #[must_use]
70    pub fn exponent(self) -> i8 {
71        self.0
72    }
73
74    /// The byte a vertex stream stores this step as, `exponent + 3`.
75    pub(crate) fn code(self) -> u8 {
76        (self.0 - Self::MIN_EXPONENT).cast_unsigned()
77    }
78
79    /// The step a vertex stream's byte names.
80    pub(crate) fn from_code(code: u8) -> MltResult<Self> {
81        i8::try_from(code)
82            .ok()
83            .and_then(|c| Self::new(c + Self::MIN_EXPONENT).ok())
84            .ok_or(MltError::InvalidZStepCode(code))
85    }
86
87    /// The step in metres.
88    #[must_use]
89    pub fn metres(self) -> f64 {
90        if self.0 < 0 {
91            1.0 / self.power()
92        } else {
93            self.power()
94        }
95    }
96
97    /// The elevation of `z`, in metres.
98    ///
99    /// A fine step moves the base onto the grid and divides by an exact integer last, so `1_001_234` at `10^-2` reads as `12.34`.
100    #[must_use]
101    pub fn elevation(self, z: i32) -> f64 {
102        let power = self.power();
103        if self.0 < 0 {
104            (f64::from(z) + Self::BASE_METRES * power) / power
105        } else {
106            Self::BASE_METRES + f64::from(z) * power
107        }
108    }
109
110    /// The grid value nearest `elevation` metres, or [`None`] when it falls outside `i32`.
111    #[must_use]
112    pub fn z(self, elevation: f64) -> Option<i32> {
113        let power = self.power();
114        let z = if self.0 < 0 {
115            elevation * power - Self::BASE_METRES * power
116        } else {
117            (elevation - Self::BASE_METRES) / power
118        };
119        z.round().to_i32()
120    }
121
122    /// `10^|exponent|`, an integer `f64` holds exactly.
123    fn power(self) -> f64 {
124        10f64.powi(self.0.unsigned_abs().into())
125    }
126}
127
128/// The kind of column a name belongs to, which a layer's names are unique across.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display)]
130pub enum ColumnRole {
131    /// An ordinary column of values, a shared dictionary's children included.
132    #[strum(serialize = "property")]
133    Property,
134    /// A vertex-scoped column of the m-value section.
135    #[cfg(feature = "unstable-v2")]
136    #[strum(serialize = "m-value")]
137    MValue,
138    /// A shredded column of maps or lists.
139    #[cfg(feature = "unstable-v2")]
140    #[strum(serialize = "nested")]
141    Nested,
142}
143
144/// Row-oriented working form for the optimizer.
145///
146/// All features are stored as a flat [`Vec<TileFeature>`] so that sorting is
147/// a single `sort_by_cached_key` call.  The `property_names` vec is parallel
148/// to every `TileFeature::properties` slice in this layer.
149#[derive(Debug, Clone, PartialEq)]
150pub struct TileLayer {
151    pub(crate) name: String,
152    pub(crate) extent: Extent,
153    /// Column names, parallel to `TileFeature::properties`.
154    pub(crate) property_names: Vec<String>,
155    /// Column types, parallel to `TileFeature::properties`.
156    pub(crate) property_kinds: Vec<PropKind>,
157    /// Vertex-scoped column names, parallel to `TileFeature::m_values`.
158    #[cfg(feature = "unstable-v2")]
159    pub(crate) m_value_names: Vec<String>,
160    /// Vertex-scoped column types, parallel to `TileFeature::m_values`.
161    #[cfg(feature = "unstable-v2")]
162    pub(crate) m_value_kinds: Vec<PropKind>,
163    /// Nested column names, parallel to `TileFeature::nested`.
164    #[cfg(feature = "unstable-v2")]
165    pub(crate) nested_names: Vec<String>,
166    /// Nested column shapes, parallel to `TileFeature::nested`.
167    #[cfg(feature = "unstable-v2")]
168    pub(crate) nested_kinds: Vec<NestedKind>,
169    /// The grid of every feature's `TileFeature::z`, or [`None`] for a flat layer.
170    #[cfg(feature = "unstable-v2")]
171    pub(crate) z_step: Option<ZStep>,
172    pub(crate) features: Vec<TileFeature>,
173}
174
175/// A single map feature in row form.
176#[derive(Debug, Clone, PartialEq)]
177pub struct TileFeature {
178    pub(crate) id: Option<u64>,
179    /// Geometry as a [`geo_types`] form
180    pub(crate) geometry: Geometry<i32>,
181    /// One value per property column, in the same order as
182    /// [`TileLayer::property_names`].
183    pub(crate) properties: Vec<PropValue>,
184    /// One entry per m-value column, in the same order as
185    /// [`TileLayer::m_value_names`], each holding one value per vertex of this
186    /// feature's geometry or none at all.
187    #[cfg(feature = "unstable-v2")]
188    pub(crate) m_values: Vec<MValue>,
189    /// One value per nested column, in the same order as [`TileLayer::nested_names`].
190    #[cfg(feature = "unstable-v2")]
191    pub(crate) nested: Vec<NestedValue>,
192    /// One z per vertex of the geometry on the layer's [`ZStep`] grid, or none in a flat layer.
193    #[cfg(feature = "unstable-v2")]
194    pub(crate) z: Vec<i32>,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
198pub struct PropertyKey(usize);
199
200impl PropertyKey {
201    #[must_use]
202    pub fn index(self) -> usize {
203        self.0
204    }
205}
206
207/// Handle to one of a layer's m-value columns, as [`PropertyKey`] is to a property column.
208#[cfg(feature = "unstable-v2")]
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
210pub struct MValueKey(usize);
211
212#[cfg(feature = "unstable-v2")]
213impl MValueKey {
214    #[must_use]
215    pub fn index(self) -> usize {
216        self.0
217    }
218}
219
220/// Handle to one of a layer's nested columns, as [`PropertyKey`] is to a property column.
221#[cfg(feature = "unstable-v2")]
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
223pub struct NestedKey(usize);
224
225#[cfg(feature = "unstable-v2")]
226impl NestedKey {
227    #[must_use]
228    pub fn index(self) -> usize {
229        self.0
230    }
231}
232
233/// How deep a nested column may go, counting its root as the first level.
234#[cfg(feature = "unstable-v2")]
235pub(crate) const MAX_NESTED_DEPTH: usize = 8;
236
237/// The shape of one nested column, the union of every value it holds.
238#[cfg(feature = "unstable-v2")]
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub enum NestedKind {
241    Leaf(PropKind),
242    List(Box<Self>),
243    /// String keys, each with its own kind, so a heterogeneous object is one of these.
244    Map(BTreeMap<String, Self>),
245}
246
247#[cfg(feature = "unstable-v2")]
248impl NestedKind {
249    /// A map of the given fields, the shape a struct of them shreds into.
250    #[must_use]
251    pub fn map<K: Into<String>>(fields: impl IntoIterator<Item = (K, Self)>) -> Self {
252        Self::Map(fields.into_iter().map(|(k, v)| (k.into(), v)).collect())
253    }
254
255    /// A list of `element`.
256    #[must_use]
257    pub fn list(element: Self) -> Self {
258        Self::List(Box::new(element))
259    }
260
261    /// How many levels this shape has, counting itself as the first.
262    #[must_use]
263    pub fn depth(&self) -> usize {
264        1 + match self {
265            Self::Leaf(_) => 0,
266            Self::List(element) => element.depth(),
267            Self::Map(fields) => fields.values().map(Self::depth).max().unwrap_or(0),
268        }
269    }
270
271    /// The value a feature that carries nothing for this column holds.
272    #[must_use]
273    pub fn null_value(&self) -> NestedValue {
274        match self {
275            Self::Leaf(kind) => NestedValue::Leaf(PropValue::null(*kind)),
276            Self::List(_) => NestedValue::List(None),
277            Self::Map(_) => NestedValue::Map(None),
278        }
279    }
280
281    /// Whether `value` is one this shape describes.
282    ///
283    /// A map may leave a field out, which is how a null field is written.
284    #[must_use]
285    pub fn accepts(&self, value: &NestedValue) -> bool {
286        match (self, value) {
287            (Self::Leaf(kind), NestedValue::Leaf(value)) => value.kind() == *kind,
288            (Self::List(_), NestedValue::List(None)) | (Self::Map(_), NestedValue::Map(None)) => {
289                true
290            }
291            (Self::List(element), NestedValue::List(Some(items))) => {
292                items.iter().all(|item| element.accepts(item))
293            }
294            (Self::Map(fields), NestedValue::Map(Some(entries))) => entries
295                .iter()
296                .all(|(key, value)| fields.get(key).is_some_and(|kind| kind.accepts(value))),
297            _ => false,
298        }
299    }
300
301    /// Whether a shape with no fields, which no struct node can hold, sits anywhere in this one.
302    fn has_empty_map(&self) -> bool {
303        match self {
304            Self::Leaf(_) => false,
305            Self::List(element) => element.has_empty_map(),
306            Self::Map(fields) => fields.is_empty() || fields.values().any(Self::has_empty_map),
307        }
308    }
309}
310
311/// One feature's value for one nested column.
312///
313/// A map leaves out the keys it has no value for, so a null field is an absent one.
314#[cfg(feature = "unstable-v2")]
315#[derive(Debug, Clone, PartialEq)]
316pub enum NestedValue {
317    Leaf(PropValue),
318    List(Option<Vec<Self>>),
319    Map(Option<BTreeMap<String, Self>>),
320}
321
322#[cfg(feature = "unstable-v2")]
323impl NestedValue {
324    /// A map of the given entries.
325    #[must_use]
326    pub fn map<K: Into<String>>(entries: impl IntoIterator<Item = (K, Self)>) -> Self {
327        Self::Map(Some(
328            entries.into_iter().map(|(k, v)| (k.into(), v)).collect(),
329        ))
330    }
331
332    /// A list of the given items.
333    #[must_use]
334    pub fn list(items: impl IntoIterator<Item = Self>) -> Self {
335        Self::List(Some(items.into_iter().collect()))
336    }
337
338    /// Whether this carries nothing at all, which is how a nested value is null.
339    #[must_use]
340    pub fn is_null(&self) -> bool {
341        match self {
342            Self::Leaf(value) => value.is_null(),
343            Self::List(items) => items.is_none(),
344            Self::Map(entries) => entries.is_none(),
345        }
346    }
347
348    /// The heap bytes this value holds, which the decoder charges its budget for.
349    #[must_use]
350    pub(crate) fn heap_bytes(&self) -> usize {
351        match self {
352            Self::Leaf(PropValue::Str(Some(value))) => value.len(),
353            Self::Leaf(_) => size_of::<PropValue>(),
354            Self::List(items) => items.iter().flatten().map(Self::heap_bytes).sum::<usize>(),
355            Self::Map(entries) => entries
356                .iter()
357                .flatten()
358                .map(|(key, value)| key.len() + value.heap_bytes())
359                .sum(),
360        }
361    }
362
363    /// Whether the two are the same kind of value, which is all a feature can check
364    /// without the column's shape.
365    fn same_shape(&self, other: &Self) -> bool {
366        matches!(
367            (self, other),
368            (Self::Leaf(_), Self::Leaf(_))
369                | (Self::List(_), Self::List(_))
370                | (Self::Map(_), Self::Map(_))
371        )
372    }
373}
374
375impl TileLayer {
376    pub fn new(name: impl Into<String>, extent: u32) -> MltResult<Self> {
377        Self::with_capacity(name, extent, 0)
378    }
379
380    pub fn with_capacity(name: impl Into<String>, extent: u32, features: usize) -> MltResult<Self> {
381        let name = name.into();
382        validate_layer_name(&name)?;
383        let extent = Extent::new(extent)?;
384        Ok(Self {
385            name,
386            extent,
387            property_names: Vec::new(),
388            property_kinds: Vec::new(),
389            #[cfg(feature = "unstable-v2")]
390            m_value_names: Vec::new(),
391            #[cfg(feature = "unstable-v2")]
392            m_value_kinds: Vec::new(),
393            #[cfg(feature = "unstable-v2")]
394            nested_names: Vec::new(),
395            #[cfg(feature = "unstable-v2")]
396            nested_kinds: Vec::new(),
397            #[cfg(feature = "unstable-v2")]
398            z_step: None,
399            features: Vec::with_capacity(features),
400        })
401    }
402
403    pub(crate) fn from_parts(
404        name: impl Into<String>,
405        extent: u32,
406        property_names: Vec<String>,
407        features: Vec<TileFeature>,
408    ) -> MltResult<Self> {
409        let name = name.into();
410        validate_layer_name(&name)?;
411        let extent = Extent::new(extent)?;
412        validate_unique_names(&property_names, ColumnRole::Property)?;
413        let property_kinds = infer_kinds::<PropValue>(&property_names, &features)?;
414        let layer = Self {
415            name,
416            extent,
417            property_names,
418            property_kinds,
419            #[cfg(feature = "unstable-v2")]
420            m_value_names: Vec::new(),
421            #[cfg(feature = "unstable-v2")]
422            m_value_kinds: Vec::new(),
423            #[cfg(feature = "unstable-v2")]
424            nested_names: Vec::new(),
425            #[cfg(feature = "unstable-v2")]
426            nested_kinds: Vec::new(),
427            #[cfg(feature = "unstable-v2")]
428            z_step: None,
429            features,
430        };
431        Ok(layer)
432    }
433
434    #[must_use]
435    pub fn name(&self) -> &str {
436        &self.name
437    }
438
439    #[must_use]
440    pub fn extent(&self) -> Extent {
441        self.extent
442    }
443
444    #[must_use]
445    pub fn property_names(&self) -> &[String] {
446        &self.property_names
447    }
448
449    #[must_use]
450    pub fn features(&self) -> &[TileFeature] {
451        &self.features
452    }
453
454    #[must_use]
455    pub(crate) fn features_mut(&mut self) -> &mut [TileFeature] {
456        &mut self.features
457    }
458
459    /// Widen every `I8` and `U8` property column to `I32` and `U32`.
460    pub(crate) fn widen_8bit_ints(&mut self) {
461        for kind in &mut self.property_kinds {
462            if *kind == PropKind::I8 {
463                *kind = PropKind::I32;
464            } else if *kind == PropKind::U8 {
465                *kind = PropKind::U32;
466            }
467        }
468        for prop in self.features.iter_mut().flat_map(|f| &mut f.properties) {
469            if let PropValue::I8(v) = *prop {
470                *prop = PropValue::I32(v.map(i32::from));
471            } else if let PropValue::U8(v) = *prop {
472                *prop = PropValue::U32(v.map(u32::from));
473            }
474        }
475    }
476
477    #[must_use]
478    pub fn feature_count(&self) -> usize {
479        self.features.len()
480    }
481
482    /// Reject a column name any column of this layer already holds.
483    fn reject_name_in_use(&self, name: &str, role: ColumnRole) -> MltResult<()> {
484        let taken = self
485            .property_names
486            .iter()
487            .map(|n| (n.as_str(), ColumnRole::Property));
488        #[cfg(feature = "unstable-v2")]
489        let taken = taken
490            .chain(
491                self.m_value_names
492                    .iter()
493                    .map(|n| (n.as_str(), ColumnRole::MValue)),
494            )
495            .chain(
496                self.nested_names
497                    .iter()
498                    .map(|n| (n.as_str(), ColumnRole::Nested)),
499            );
500        reject_taken_name(name, role, taken)
501    }
502
503    pub fn add_property(
504        &mut self,
505        name: impl Into<String>,
506        kind: PropKind,
507    ) -> MltResult<PropertyKey> {
508        let name = name.into();
509        self.reject_name_in_use(&name, ColumnRole::Property)?;
510        for feature in &mut self.features {
511            feature.properties.push(PropValue::null(kind));
512        }
513        self.property_names.push(name);
514        self.property_kinds.push(kind);
515        Ok(PropertyKey(self.property_names.len() - 1))
516    }
517
518    #[cfg(feature = "unstable-v2")]
519    #[must_use]
520    pub fn m_value_names(&self) -> &[String] {
521        &self.m_value_names
522    }
523
524    #[cfg(feature = "unstable-v2")]
525    #[must_use]
526    pub fn m_value_kinds(&self) -> &[PropKind] {
527        &self.m_value_kinds
528    }
529
530    /// Declare a vertex-scoped column, whose values every feature then holds or does not.
531    #[cfg(feature = "unstable-v2")]
532    pub fn add_m_value(&mut self, name: impl Into<String>, kind: PropKind) -> MltResult<MValueKey> {
533        let name = name.into();
534        self.reject_name_in_use(&name, ColumnRole::MValue)?;
535        for feature in &mut self.features {
536            feature.m_values.push(MValue::null(kind));
537        }
538        self.m_value_names.push(name);
539        self.m_value_kinds.push(kind);
540        Ok(MValueKey(self.m_value_names.len() - 1))
541    }
542
543    /// Name the vertex-scoped columns the features already carry, as
544    /// [`Self::from_parts`] names the property columns.
545    #[cfg(feature = "unstable-v2")]
546    pub(crate) fn with_m_value_names(mut self, names: Vec<String>) -> MltResult<Self> {
547        validate_unique_names(&names, ColumnRole::MValue)?;
548        for name in &names {
549            self.reject_name_in_use(name, ColumnRole::MValue)?;
550        }
551        self.m_value_kinds = infer_kinds::<MValue>(&names, &self.features)?;
552        self.m_value_names = names;
553        for feature in &self.features {
554            self.validate_m_values(feature)?;
555        }
556        Ok(self)
557    }
558
559    #[cfg(feature = "unstable-v2")]
560    #[must_use]
561    pub fn nested_names(&self) -> &[String] {
562        &self.nested_names
563    }
564
565    #[cfg(feature = "unstable-v2")]
566    #[must_use]
567    pub fn nested_kinds(&self) -> &[NestedKind] {
568        &self.nested_kinds
569    }
570
571    /// The grid the features' z coordinates lie on, or [`None`] for a flat layer.
572    #[cfg(feature = "unstable-v2")]
573    #[must_use]
574    pub fn z_step(&self) -> Option<ZStep> {
575        self.z_step
576    }
577
578    /// Give the layer z coordinates on `step`, which every feature pushed after this must carry.
579    ///
580    /// A layer that already holds features is rejected, since they carry none.
581    #[cfg(feature = "unstable-v2")]
582    pub fn set_z_step(&mut self, step: ZStep) -> MltResult<()> {
583        if !self.features.is_empty() {
584            return Err(MltError::ZStepOnNonEmptyLayer(self.name.clone()));
585        }
586        self.z_step = Some(step);
587        Ok(())
588    }
589
590    /// Put the features, which already carry their z coordinates, on `step`.
591    #[cfg(feature = "unstable-v2")]
592    pub(crate) fn with_z_step(mut self, step: Option<ZStep>) -> MltResult<Self> {
593        self.z_step = step;
594        for feature in &self.features {
595            self.validate_z(feature)?;
596        }
597        Ok(self)
598    }
599
600    /// Declare a nested column of the given shape, which every feature then holds a value of.
601    ///
602    /// A scalar root is an ordinary property column, so [`NestedKind::Leaf`] is rejected here.
603    #[cfg(feature = "unstable-v2")]
604    pub fn add_nested(
605        &mut self,
606        name: impl Into<String>,
607        kind: NestedKind,
608    ) -> MltResult<NestedKey> {
609        let name = name.into();
610        self.reject_name_in_use(&name, ColumnRole::Nested)?;
611        validate_nested_kind(&name, &kind)?;
612        for feature in &mut self.features {
613            feature.nested.push(kind.null_value());
614        }
615        self.nested_names.push(name);
616        self.nested_kinds.push(kind);
617        Ok(NestedKey(self.nested_names.len() - 1))
618    }
619
620    /// Name and shape the nested columns the features already carry, as
621    /// [`Self::from_parts`] names the property columns.
622    #[cfg(feature = "unstable-v2")]
623    pub(crate) fn with_nested(
624        mut self,
625        names: Vec<String>,
626        kinds: Vec<NestedKind>,
627    ) -> MltResult<Self> {
628        validate_unique_names(&names, ColumnRole::Nested)?;
629        for name in &names {
630            self.reject_name_in_use(name, ColumnRole::Nested)?;
631        }
632        for (name, kind) in names.iter().zip(&kinds) {
633            validate_nested_kind(name, kind)?;
634        }
635        self.nested_names = names;
636        self.nested_kinds = kinds;
637        for feature in &self.features {
638            self.validate_nested(feature)?;
639        }
640        Ok(self)
641    }
642
643    pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
644        self.validate_feature(&feature)?;
645        self.features.push(feature);
646        Ok(())
647    }
648
649    pub fn builder(name: impl Into<String>, extent: u32) -> MltResult<TileLayerBuilder> {
650        Ok(TileLayerBuilder {
651            layer: Self::new(name, extent)?,
652        })
653    }
654
655    fn validate_feature(&self, feature: &TileFeature) -> MltResult<()> {
656        validate_kinds::<PropValue>(&self.property_kinds, feature)?;
657        #[cfg(feature = "unstable-v2")]
658        self.validate_m_values(feature)?;
659        #[cfg(feature = "unstable-v2")]
660        self.validate_nested(feature)?;
661        #[cfg(feature = "unstable-v2")]
662        self.validate_z(feature)?;
663        Ok(())
664    }
665
666    /// Check a feature's z coordinates against the layer: one per vertex on a z layer, none on a flat one.
667    #[cfg(feature = "unstable-v2")]
668    fn validate_z(&self, feature: &TileFeature) -> MltResult<()> {
669        let expected = if self.z_step.is_some() {
670            feature.vertex_count()
671        } else {
672            0
673        };
674        if feature.z.len() == expected {
675            Ok(())
676        } else {
677            Err(MltError::ZVertexCountMismatch {
678                expected,
679                actual: feature.z.len(),
680            })
681        }
682    }
683
684    /// Check a feature's nested values against the layer's columns: one value per
685    /// column, of the shape the column declared.
686    #[cfg(feature = "unstable-v2")]
687    fn validate_nested(&self, feature: &TileFeature) -> MltResult<()> {
688        if feature.nested.len() != self.nested_kinds.len() {
689            return Err(MltError::NestedColumnCountMismatch {
690                expected: self.nested_kinds.len(),
691                actual: feature.nested.len(),
692            });
693        }
694        for (index, (value, kind)) in feature.nested.iter().zip(&self.nested_kinds).enumerate() {
695            if !kind.accepts(value) {
696                return Err(MltError::NestedValueMismatch {
697                    index,
698                    name: self.nested_names[index].clone(),
699                });
700            }
701        }
702        Ok(())
703    }
704
705    /// Check a feature's m-values against the layer's columns: one entry per
706    /// column, of the column's kind, holding one value per vertex it has.
707    #[cfg(feature = "unstable-v2")]
708    fn validate_m_values(&self, feature: &TileFeature) -> MltResult<()> {
709        validate_kinds::<MValue>(&self.m_value_kinds, feature)?;
710        let vertices = feature.vertex_count();
711        for (index, m_value) in feature.m_values.iter().enumerate() {
712            if let Some(len) = m_value.count()
713                && len != vertices
714            {
715                return Err(MltError::MValueVertexCountMismatch {
716                    index,
717                    expected: vertices,
718                    actual: len,
719                });
720            }
721        }
722        Ok(())
723    }
724}
725
726impl TileFeature {
727    #[must_use]
728    pub fn new(geometry: Geometry<i32>) -> Self {
729        Self {
730            id: None,
731            geometry,
732            properties: Vec::new(),
733            #[cfg(feature = "unstable-v2")]
734            m_values: Vec::new(),
735            #[cfg(feature = "unstable-v2")]
736            nested: Vec::new(),
737            #[cfg(feature = "unstable-v2")]
738            z: Vec::new(),
739        }
740    }
741
742    #[must_use]
743    pub fn with_id(geometry: Geometry<i32>, id: u64) -> Self {
744        Self {
745            id: Some(id),
746            geometry,
747            properties: Vec::new(),
748            #[cfg(feature = "unstable-v2")]
749            m_values: Vec::new(),
750            #[cfg(feature = "unstable-v2")]
751            nested: Vec::new(),
752            #[cfg(feature = "unstable-v2")]
753            z: Vec::new(),
754        }
755    }
756
757    #[must_use]
758    pub fn id(&self) -> Option<u64> {
759        self.id
760    }
761
762    #[must_use]
763    pub fn geometry(&self) -> &Geometry<i32> {
764        &self.geometry
765    }
766
767    #[must_use]
768    pub fn properties(&self) -> &[PropValue] {
769        &self.properties
770    }
771
772    #[must_use]
773    pub(crate) fn properties_mut(&mut self) -> &mut [PropValue] {
774        &mut self.properties
775    }
776
777    /// How many vertices this feature stores, which is how many values each of
778    /// its m-values holds.
779    ///
780    /// A polygon ring's closing vertex is not stored, so it is not counted.
781    #[cfg(feature = "unstable-v2")]
782    #[must_use]
783    pub fn vertex_count(&self) -> usize {
784        stored_vertex_count(&self.geometry)
785    }
786
787    #[cfg(feature = "unstable-v2")]
788    #[must_use]
789    pub fn m_values(&self) -> &[MValue] {
790        &self.m_values
791    }
792
793    /// One z per vertex, in the order [`Self::vertex_count`] counts them, or none in a flat layer.
794    #[cfg(feature = "unstable-v2")]
795    #[must_use]
796    pub fn z(&self) -> &[i32] {
797        &self.z
798    }
799
800    /// Set this feature's z coordinates, one per vertex it stores.
801    #[cfg(feature = "unstable-v2")]
802    pub fn set_z(&mut self, z: Vec<i32>) -> MltResult<()> {
803        let expected = self.vertex_count();
804        if z.len() != expected {
805            return Err(MltError::ZVertexCountMismatch {
806                expected,
807                actual: z.len(),
808            });
809        }
810        self.z = z;
811        Ok(())
812    }
813
814    /// Set this feature's values for one m-value column, which must hold one
815    /// value per vertex unless the feature has none at all.
816    #[cfg(feature = "unstable-v2")]
817    pub fn set_m_value(&mut self, key: MValueKey, value: MValue) -> MltResult<()> {
818        let vertices = self.vertex_count();
819        let Some(slot) = self.m_values.get_mut(key.index()) else {
820            return Err(MltError::UnknownMValueKey {
821                index: key.index(),
822                columns: self.m_values.len(),
823            });
824        };
825        let expected = slot.kind();
826        let actual = value.kind();
827        if actual != expected {
828            return Err(MltError::MValueKindMismatch {
829                index: key.index(),
830                expected,
831                actual,
832            });
833        }
834        if let Some(len) = value.count()
835            && len != vertices
836        {
837            return Err(MltError::MValueVertexCountMismatch {
838                index: key.index(),
839                expected: vertices,
840                actual: len,
841            });
842        }
843        *slot = value;
844        Ok(())
845    }
846
847    #[cfg(feature = "unstable-v2")]
848    #[must_use]
849    pub fn nested(&self) -> &[NestedValue] {
850        &self.nested
851    }
852
853    /// Set this feature's value for one nested column, which must be the kind of
854    /// value the column holds.
855    ///
856    /// Only the layer knows the column's full shape, so that is checked when the
857    /// feature is pushed.
858    #[cfg(feature = "unstable-v2")]
859    pub fn set_nested(&mut self, key: NestedKey, value: NestedValue) -> MltResult<()> {
860        let columns = self.nested.len();
861        let Some(slot) = self.nested.get_mut(key.index()) else {
862            return Err(MltError::UnknownNestedKey {
863                index: key.index(),
864                columns,
865            });
866        };
867        if !slot.same_shape(&value) {
868            return Err(MltError::NestedShapeMismatch { index: key.index() });
869        }
870        *slot = value;
871        Ok(())
872    }
873
874    pub fn set_property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<()> {
875        let Some(prop) = self.properties.get_mut(key.index()) else {
876            return Err(MltError::PropertyLengthMismatch {
877                expected: key.index() + 1,
878                actual: self.properties.len(),
879            });
880        };
881        let expected = PropKind::from(&*prop);
882        let actual = PropKind::from(&value);
883        if actual != expected {
884            return Err(MltError::PropertyKindMismatch {
885                index: key.index(),
886                expected,
887                actual,
888            });
889        }
890        *prop = value;
891        Ok(())
892    }
893}
894
895pub struct TileLayerBuilder {
896    layer: TileLayer,
897}
898
899impl TileLayerBuilder {
900    pub fn add_property(
901        &mut self,
902        name: impl Into<String>,
903        kind: PropKind,
904    ) -> MltResult<PropertyKey> {
905        self.layer.add_property(name, kind)
906    }
907
908    #[cfg(feature = "unstable-v2")]
909    pub fn add_m_value(&mut self, name: impl Into<String>, kind: PropKind) -> MltResult<MValueKey> {
910        self.layer.add_m_value(name, kind)
911    }
912
913    #[cfg(feature = "unstable-v2")]
914    pub fn add_nested(
915        &mut self,
916        name: impl Into<String>,
917        kind: NestedKind,
918    ) -> MltResult<NestedKey> {
919        self.layer.add_nested(name, kind)
920    }
921
922    #[cfg(feature = "unstable-v2")]
923    pub fn set_z_step(&mut self, step: ZStep) -> MltResult<()> {
924        self.layer.set_z_step(step)
925    }
926
927    pub fn feature(&mut self, geometry: Geometry<i32>) -> TileFeatureBuilder<'_> {
928        let properties = self
929            .layer
930            .property_kinds
931            .iter()
932            .copied()
933            .map(PropValue::null)
934            .collect();
935        #[cfg(feature = "unstable-v2")]
936        let m_values = self
937            .layer
938            .m_value_kinds
939            .iter()
940            .copied()
941            .map(MValue::null)
942            .collect();
943        #[cfg(feature = "unstable-v2")]
944        let nested = self
945            .layer
946            .nested_kinds
947            .iter()
948            .map(NestedKind::null_value)
949            .collect();
950        TileFeatureBuilder {
951            layer: self,
952            feature: TileFeature {
953                id: None,
954                geometry,
955                properties,
956                #[cfg(feature = "unstable-v2")]
957                m_values,
958                #[cfg(feature = "unstable-v2")]
959                nested,
960                #[cfg(feature = "unstable-v2")]
961                z: Vec::new(),
962            },
963        }
964    }
965
966    pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
967        self.layer.push_feature(feature)
968    }
969
970    #[must_use]
971    pub fn finish(self) -> TileLayer {
972        self.layer
973    }
974}
975
976pub struct TileFeatureBuilder<'a> {
977    layer: &'a mut TileLayerBuilder,
978    feature: TileFeature,
979}
980
981impl TileFeatureBuilder<'_> {
982    pub fn id(&mut self, id: Option<u64>) -> &mut Self {
983        self.feature.id = id;
984        self
985    }
986
987    pub fn property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<&mut Self> {
988        self.feature.set_property(key, value)?;
989        Ok(self)
990    }
991
992    #[cfg(feature = "unstable-v2")]
993    pub fn m_value(&mut self, key: MValueKey, value: MValue) -> MltResult<&mut Self> {
994        self.feature.set_m_value(key, value)?;
995        Ok(self)
996    }
997
998    #[cfg(feature = "unstable-v2")]
999    pub fn nested(&mut self, key: NestedKey, value: NestedValue) -> MltResult<&mut Self> {
1000        self.feature.set_nested(key, value)?;
1001        Ok(self)
1002    }
1003
1004    #[cfg(feature = "unstable-v2")]
1005    pub fn z(&mut self, z: Vec<i32>) -> MltResult<&mut Self> {
1006        self.feature.set_z(z)?;
1007        Ok(self)
1008    }
1009
1010    pub fn finish(self) -> MltResult<()> {
1011        self.layer.push_feature(self.feature)
1012    }
1013}
1014
1015/// A single typed value for one property of one feature.
1016///
1017/// Mirrors the scalar variants of `ParsedProperty` at the per-feature
1018/// level. `SharedDict` items are flattened: each sub-field becomes its own
1019/// `PropValue::Str` entry in `TileFeature::properties`, with the
1020/// corresponding entry in `TileLayer::property_names` set to
1021/// `"prefix:suffix"`.
1022#[derive(Debug, Clone, PartialEq)]
1023pub enum PropValue {
1024    Bool(Option<bool>),
1025    I8(Option<i8>),
1026    U8(Option<u8>),
1027    I32(Option<i32>),
1028    U32(Option<u32>),
1029    I64(Option<i64>),
1030    U64(Option<u64>),
1031    F32(Option<f32>),
1032    F64(Option<f64>),
1033    Str(Option<String>),
1034}
1035
1036impl PropValue {
1037    #[must_use]
1038    pub fn kind(&self) -> PropKind {
1039        self.into()
1040    }
1041}
1042
1043/// A feature's values for one vertex-scoped column: one per vertex, or none at all.
1044///
1045/// The option is around the whole vector because a null is a feature's, not a
1046/// vertex's: a feature either measures every one of its vertices or none of them.
1047#[cfg(feature = "unstable-v2")]
1048#[derive(Debug, Clone, PartialEq)]
1049pub enum MValue {
1050    Bool(Option<Vec<bool>>),
1051    I8(Option<Vec<i8>>),
1052    U8(Option<Vec<u8>>),
1053    I32(Option<Vec<i32>>),
1054    U32(Option<Vec<u32>>),
1055    I64(Option<Vec<i64>>),
1056    U64(Option<Vec<u64>>),
1057    F32(Option<Vec<f32>>),
1058    F64(Option<Vec<f64>>),
1059    Str(Option<Vec<String>>),
1060}
1061
1062#[cfg(feature = "unstable-v2")]
1063impl MValue {
1064    #[must_use]
1065    pub fn is_null(&self) -> bool {
1066        self.count().is_none()
1067    }
1068}
1069
1070macro_rules! kind_mappings {
1071    (
1072        scalar { $($sv:ident),* $(,)? }
1073        string { $($gv:ident),* $(,)? }
1074    ) => {
1075        /// The data type of one property or m-value column.
1076        #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)]
1077        #[strum(serialize_all = "lowercase")]
1078        pub enum PropKind {
1079            $($sv,)*
1080            $($gv,)*
1081        }
1082
1083        impl From<&PropValue> for PropKind {
1084            fn from(prop: &PropValue) -> Self {
1085                match prop {
1086                    $(PropValue::$sv(_) => Self::$sv,)*
1087                    $(PropValue::$gv(_) => Self::$gv,)*
1088                }
1089            }
1090        }
1091
1092        impl PropValue {
1093            #[must_use]
1094            pub fn is_null(&self) -> bool {
1095                match self {
1096                    $(Self::$sv(v) => v.is_none(),)*
1097                    $(Self::$gv(v) => v.is_none(),)*
1098                }
1099            }
1100
1101            /// The null value of `kind`.
1102            #[must_use]
1103            pub fn null(kind: PropKind) -> Self {
1104                match kind {
1105                    $(PropKind::$sv => Self::$sv(None),)*
1106                    $(PropKind::$gv => Self::$gv(None),)*
1107                }
1108            }
1109        }
1110
1111        #[cfg(feature = "unstable-v2")]
1112        impl MValue {
1113            #[must_use]
1114            pub fn kind(&self) -> PropKind {
1115                match self {
1116                    $(Self::$sv(_) => PropKind::$sv,)*
1117                    $(Self::$gv(_) => PropKind::$gv,)*
1118                }
1119            }
1120
1121            /// How many values this holds, or [`None`] when the feature carries none.
1122            #[must_use]
1123            pub fn count(&self) -> Option<usize> {
1124                match self {
1125                    $(Self::$sv(v) => v.as_ref().map(Vec::len),)*
1126                    $(Self::$gv(v) => v.as_ref().map(Vec::len),)*
1127                }
1128            }
1129
1130            /// The empty value of `kind`, which is what a feature with no values holds.
1131            #[must_use]
1132            pub fn null(kind: PropKind) -> Self {
1133                match kind {
1134                    $(PropKind::$sv => Self::$sv(None),)*
1135                    $(PropKind::$gv => Self::$gv(None),)*
1136                }
1137            }
1138        }
1139    };
1140}
1141
1142with_kinds!(kind_mappings);
1143
1144/// How many of a ring's coordinates MLT stores, which is all of them but a closing one.
1145pub(crate) fn stored_ring_len(ring: &LineString<i32>) -> usize {
1146    let coords = &ring.0;
1147    if coords.len() > 1 && coords.last() == coords.first() {
1148        coords.len() - 1
1149    } else {
1150        coords.len()
1151    }
1152}
1153
1154/// How many vertices a geometry contributes to the layer's vertex sequence, which
1155/// is what an m-value column holds one value per.
1156#[cfg(feature = "unstable-v2")]
1157pub(crate) fn stored_vertex_count(geom: &Geometry<i32>) -> usize {
1158    let polygon = |poly: &geo_types::Polygon<i32>| {
1159        std::iter::once(poly.exterior())
1160            .chain(poly.interiors())
1161            .map(stored_ring_len)
1162            .sum::<usize>()
1163    };
1164    match geom {
1165        Geometry::Point(_) => 1,
1166        Geometry::Line(_) => 2,
1167        Geometry::LineString(ls) => ls.0.len(),
1168        Geometry::Polygon(p) => polygon(p),
1169        Geometry::MultiPoint(mp) => mp.0.len(),
1170        Geometry::MultiLineString(mls) => mls.iter().map(|ls| ls.0.len()).sum(),
1171        Geometry::MultiPolygon(mp) => mp.iter().map(polygon).sum(),
1172        Geometry::Triangle(t) => polygon(&t.to_polygon()),
1173        Geometry::Rect(r) => polygon(&r.to_polygon()),
1174        Geometry::GeometryCollection(gc) => gc.iter().map(stored_vertex_count).sum(),
1175    }
1176}
1177
1178/// Check a nested column's shape: an interior root, no empty field set, and no
1179/// more levels than the wire allows.
1180#[cfg(feature = "unstable-v2")]
1181fn validate_nested_kind(name: &str, kind: &NestedKind) -> MltResult<()> {
1182    if matches!(kind, NestedKind::Leaf(_)) {
1183        return Err(MltError::NestedRootIsLeaf(name.to_string()));
1184    }
1185    if kind.has_empty_map() {
1186        return Err(MltError::EmptyStructNode);
1187    }
1188    let depth = kind.depth();
1189    if depth > MAX_NESTED_DEPTH {
1190        return Err(MltError::NestedTooDeep(depth));
1191    }
1192    Ok(())
1193}
1194
1195fn validate_layer_name(name: &str) -> MltResult<()> {
1196    if name.is_empty() {
1197        Err(MltError::MissingLayerName)
1198    } else {
1199        Ok(())
1200    }
1201}
1202
1203/// Reject a column name one of `taken` already holds, naming the kind of column that holds it.
1204///
1205/// A layer has one namespace of column names, so this is the only rule every named column obeys.
1206pub(crate) fn reject_taken_name<'a>(
1207    name: &str,
1208    role: ColumnRole,
1209    taken: impl IntoIterator<Item = (&'a str, ColumnRole)>,
1210) -> MltResult<()> {
1211    for (seen, taken_by) in taken {
1212        if seen == name {
1213            return Err(MltError::DuplicateColumnName {
1214                name: name.to_string(),
1215                role,
1216                taken_by,
1217            });
1218        }
1219    }
1220    Ok(())
1221}
1222
1223/// Reject a name repeated within one list of column names.
1224fn validate_unique_names(names: &[String], role: ColumnRole) -> MltResult<()> {
1225    // Linear scan, not a HashSet: column counts are small, so this skips a per-layer alloc.
1226    // Empty names are allowed; real MVT tiles contain them.
1227    for (i, name) in names.iter().enumerate() {
1228        reject_taken_name(name, role, names[..i].iter().map(|n| (n.as_str(), role)))?;
1229    }
1230    Ok(())
1231}
1232
1233/// One feature's values for one kind of column, so the checks over them are written once.
1234trait FeatureColumn: Sized {
1235    /// This kind of column's values, one per column of the layer.
1236    fn of(feature: &TileFeature) -> &[Self];
1237    fn column_kind(&self) -> PropKind;
1238    /// A feature holding a different number of values than the layer has columns.
1239    fn count_mismatch(expected: usize, actual: usize) -> MltError;
1240    /// A value of a different kind than its column.
1241    fn kind_mismatch(index: usize, expected: PropKind, actual: PropKind) -> MltError;
1242}
1243
1244impl FeatureColumn for PropValue {
1245    fn of(feature: &TileFeature) -> &[Self] {
1246        &feature.properties
1247    }
1248
1249    fn column_kind(&self) -> PropKind {
1250        PropKind::from(self)
1251    }
1252
1253    fn count_mismatch(expected: usize, actual: usize) -> MltError {
1254        MltError::PropertyLengthMismatch { expected, actual }
1255    }
1256
1257    fn kind_mismatch(index: usize, expected: PropKind, actual: PropKind) -> MltError {
1258        MltError::PropertyKindMismatch {
1259            index,
1260            expected,
1261            actual,
1262        }
1263    }
1264}
1265
1266#[cfg(feature = "unstable-v2")]
1267impl FeatureColumn for MValue {
1268    fn of(feature: &TileFeature) -> &[Self] {
1269        &feature.m_values
1270    }
1271
1272    fn column_kind(&self) -> PropKind {
1273        self.kind()
1274    }
1275
1276    fn count_mismatch(expected: usize, actual: usize) -> MltError {
1277        MltError::MValueColumnCountMismatch { expected, actual }
1278    }
1279
1280    fn kind_mismatch(index: usize, expected: PropKind, actual: PropKind) -> MltError {
1281        MltError::MValueKindMismatch {
1282            index,
1283            expected,
1284            actual,
1285        }
1286    }
1287}
1288
1289/// The kind of each column, taken from the features that carry values for it.
1290///
1291/// A column no feature carries a value for is [`PropKind::Str`].
1292fn infer_kinds<T: FeatureColumn>(
1293    names: &[String],
1294    features: &[TileFeature],
1295) -> MltResult<Vec<PropKind>> {
1296    let mut kinds = vec![None; names.len()];
1297    for feature in features {
1298        let values = T::of(feature);
1299        if values.len() != names.len() {
1300            return Err(T::count_mismatch(names.len(), values.len()));
1301        }
1302        for (index, value) in values.iter().enumerate() {
1303            let actual = value.column_kind();
1304            match kinds[index] {
1305                Some(expected) if expected != actual => {
1306                    return Err(T::kind_mismatch(index, expected, actual));
1307                }
1308                None => kinds[index] = Some(actual),
1309                _ => {}
1310            }
1311        }
1312    }
1313    Ok(kinds
1314        .into_iter()
1315        .map(|kind| kind.unwrap_or(PropKind::Str))
1316        .collect())
1317}
1318
1319/// Check a feature's values for one kind of column: one per column, of the column's kind.
1320fn validate_kinds<T: FeatureColumn>(kinds: &[PropKind], feature: &TileFeature) -> MltResult<()> {
1321    let values = T::of(feature);
1322    if values.len() != kinds.len() {
1323        return Err(T::count_mismatch(kinds.len(), values.len()));
1324    }
1325    for (index, (value, expected)) in values.iter().zip(kinds).enumerate() {
1326        let actual = value.column_kind();
1327        if actual != *expected {
1328            return Err(T::kind_mismatch(index, *expected, actual));
1329        }
1330    }
1331    Ok(())
1332}
1333
1334#[cfg(test)]
1335mod tests {
1336    use geo_types::{Geometry, Point};
1337
1338    use super::*;
1339
1340    fn point_feature(properties: Vec<PropValue>) -> TileFeature {
1341        TileFeature {
1342            id: None,
1343            geometry: Geometry::Point(Point::new(0, 0)),
1344            properties,
1345            #[cfg(feature = "unstable-v2")]
1346            m_values: Vec::new(),
1347            #[cfg(feature = "unstable-v2")]
1348            nested: Vec::new(),
1349            #[cfg(feature = "unstable-v2")]
1350            z: Vec::new(),
1351        }
1352    }
1353
1354    #[test]
1355    fn tile_layer_constructor_rejects_empty_name() {
1356        assert!(matches!(
1357            TileLayer::new("", 4096),
1358            Err(MltError::MissingLayerName)
1359        ));
1360    }
1361
1362    #[test]
1363    fn tile_layer_constructor_rejects_zero_extent() {
1364        assert!(matches!(
1365            TileLayer::new("layer", 0),
1366            Err(MltError::InvalidExtent(0))
1367        ));
1368    }
1369
1370    #[test]
1371    fn add_property_rejects_duplicate_names() {
1372        let mut layer = TileLayer::new("layer", 4096).unwrap();
1373        layer.add_property("name", PropKind::Str).unwrap();
1374        assert_eq!(
1375            layer
1376                .add_property("name", PropKind::Str)
1377                .unwrap_err()
1378                .to_string(),
1379            "duplicate column name name: the property column repeats the property column"
1380        );
1381    }
1382
1383    #[test]
1384    fn from_parts_allows_empty_property_name() {
1385        // Real MVT tiles contain empty keys.
1386        assert!(TileLayer::from_parts("layer", 4096, vec![String::new()], vec![]).is_ok());
1387    }
1388
1389    #[test]
1390    fn from_parts_rejects_duplicate_property_name() {
1391        assert_eq!(
1392            TileLayer::from_parts("layer", 4096, vec!["dup".into(), "dup".into()], vec![])
1393                .unwrap_err()
1394                .to_string(),
1395            "duplicate column name dup: the property column repeats the property column"
1396        );
1397    }
1398
1399    #[test]
1400    fn push_feature_validates_property_count() {
1401        let mut layer = TileLayer::new("layer", 4096).unwrap();
1402        layer.add_property("name", PropKind::Str).unwrap();
1403        assert!(matches!(
1404            layer.push_feature(point_feature(vec![])),
1405            Err(MltError::PropertyLengthMismatch {
1406                expected: 1,
1407                actual: 0
1408            })
1409        ));
1410    }
1411
1412    #[test]
1413    fn push_feature_validates_property_kind() {
1414        let mut layer = TileLayer::new("layer", 4096).unwrap();
1415        layer.add_property("flag", PropKind::Bool).unwrap();
1416        layer
1417            .push_feature(point_feature(vec![PropValue::Bool(Some(true))]))
1418            .unwrap();
1419        assert!(matches!(
1420            layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
1421            Err(MltError::PropertyKindMismatch {
1422                index: 0,
1423                expected: PropKind::Bool,
1424                actual: PropKind::I32,
1425            })
1426        ));
1427    }
1428
1429    #[test]
1430    fn declared_property_kind_is_enforced_for_first_feature() {
1431        let mut layer = TileLayer::new("layer", 4096).unwrap();
1432        layer.add_property("flag", PropKind::Bool).unwrap();
1433        assert!(matches!(
1434            layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
1435            Err(MltError::PropertyKindMismatch {
1436                index: 0,
1437                expected: PropKind::Bool,
1438                actual: PropKind::I32,
1439            })
1440        ));
1441    }
1442
1443    #[cfg(feature = "unstable-v2")]
1444    #[test]
1445    fn push_feature_validates_m_value_count() {
1446        let mut layer = TileLayer::new("layer", 4096).unwrap();
1447        layer.add_m_value("m", PropKind::I32).unwrap();
1448        assert!(matches!(
1449            layer.push_feature(point_feature(vec![])),
1450            Err(MltError::MValueColumnCountMismatch {
1451                expected: 1,
1452                actual: 0
1453            })
1454        ));
1455    }
1456
1457    #[test]
1458    fn with_id_is_new_with_that_id() {
1459        let geometry = Geometry::Point(Point::new(1, 2));
1460        let mut expected = TileFeature::new(geometry.clone());
1461        expected.id = Some(7);
1462        assert_eq!(TileFeature::with_id(geometry, 7), expected);
1463    }
1464
1465    #[cfg(feature = "unstable-v2")]
1466    #[test]
1467    fn push_feature_validates_one_value_per_vertex() {
1468        let mut layer = TileLayer::new("layer", 4096).unwrap();
1469        layer.add_m_value("m", PropKind::I32).unwrap();
1470        let mut feature = point_feature(vec![]);
1471        feature.m_values = vec![MValue::I32(Some(vec![1, 2]))];
1472        assert!(matches!(
1473            layer.push_feature(feature),
1474            Err(MltError::MValueVertexCountMismatch {
1475                index: 0,
1476                expected: 1,
1477                actual: 2,
1478            })
1479        ));
1480    }
1481
1482    #[cfg(feature = "unstable-v2")]
1483    #[test]
1484    fn a_polygon_does_not_count_its_closing_vertices() {
1485        let ring = |pts: &[(i32, i32)]| {
1486            let mut ls = LineString::new(
1487                pts.iter()
1488                    .map(|&(x, y)| geo_types::Coord { x, y })
1489                    .collect(),
1490            );
1491            ls.close();
1492            ls
1493        };
1494        let poly = geo_types::Polygon::new(
1495            ring(&[(0, 0), (0, 4), (4, 4), (4, 0)]),
1496            vec![ring(&[(1, 1), (1, 2), (2, 2)])],
1497        );
1498        let feature = TileFeature::new(Geometry::Polygon(poly));
1499        assert_eq!(feature.vertex_count(), 7);
1500    }
1501
1502    #[cfg(feature = "unstable-v2")]
1503    #[test]
1504    fn a_shape_counts_its_root_as_the_first_level() {
1505        let kind = NestedKind::list(NestedKind::map([("a", NestedKind::Leaf(PropKind::I32))]));
1506        assert_eq!(kind.depth(), 3);
1507    }
1508
1509    #[cfg(feature = "unstable-v2")]
1510    #[test]
1511    fn a_map_may_leave_out_a_field_but_not_add_one() {
1512        let kind = NestedKind::map([
1513            ("a", NestedKind::Leaf(PropKind::I32)),
1514            ("b", NestedKind::Leaf(PropKind::Str)),
1515        ]);
1516        assert!(kind.accepts(&NestedValue::map([(
1517            "a",
1518            NestedValue::Leaf(PropValue::I32(Some(1)))
1519        )])));
1520        assert!(!kind.accepts(&NestedValue::map([(
1521            "c",
1522            NestedValue::Leaf(PropValue::I32(Some(1)))
1523        )])));
1524        assert!(!kind.accepts(&NestedValue::map([(
1525            "a",
1526            NestedValue::Leaf(PropValue::Str(Some("x".into())))
1527        )])));
1528    }
1529
1530    #[cfg(feature = "unstable-v2")]
1531    fn list_kind() -> NestedKind {
1532        NestedKind::list(NestedKind::Leaf(PropKind::I32))
1533    }
1534
1535    #[cfg(feature = "unstable-v2")]
1536    #[test]
1537    fn add_m_value_rejects_a_name_a_property_has() {
1538        let mut layer = TileLayer::new("layer", 4096).unwrap();
1539        layer.add_property("n", PropKind::I32).unwrap();
1540        assert_eq!(
1541            layer
1542                .add_m_value("n", PropKind::I32)
1543                .unwrap_err()
1544                .to_string(),
1545            "duplicate column name n: the m-value column repeats the property column"
1546        );
1547    }
1548
1549    #[cfg(feature = "unstable-v2")]
1550    #[test]
1551    fn add_m_value_rejects_a_name_a_nested_column_has() {
1552        let mut layer = TileLayer::new("layer", 4096).unwrap();
1553        layer.add_nested("n", list_kind()).unwrap();
1554        assert_eq!(
1555            layer
1556                .add_m_value("n", PropKind::I32)
1557                .unwrap_err()
1558                .to_string(),
1559            "duplicate column name n: the m-value column repeats the nested column"
1560        );
1561    }
1562
1563    #[cfg(feature = "unstable-v2")]
1564    #[test]
1565    fn add_m_value_rejects_duplicate_names() {
1566        let mut layer = TileLayer::new("layer", 4096).unwrap();
1567        layer.add_m_value("n", PropKind::I32).unwrap();
1568        assert_eq!(
1569            layer
1570                .add_m_value("n", PropKind::I32)
1571                .unwrap_err()
1572                .to_string(),
1573            "duplicate column name n: the m-value column repeats the m-value column"
1574        );
1575    }
1576
1577    #[cfg(feature = "unstable-v2")]
1578    #[test]
1579    fn add_property_rejects_a_name_an_m_value_has() {
1580        let mut layer = TileLayer::new("layer", 4096).unwrap();
1581        layer.add_m_value("n", PropKind::I32).unwrap();
1582        assert_eq!(
1583            layer
1584                .add_property("n", PropKind::I32)
1585                .unwrap_err()
1586                .to_string(),
1587            "duplicate column name n: the property column repeats the m-value column"
1588        );
1589    }
1590
1591    #[cfg(feature = "unstable-v2")]
1592    #[test]
1593    fn add_property_rejects_a_name_a_nested_column_has() {
1594        let mut layer = TileLayer::new("layer", 4096).unwrap();
1595        layer.add_nested("n", list_kind()).unwrap();
1596        assert_eq!(
1597            layer
1598                .add_property("n", PropKind::I32)
1599                .unwrap_err()
1600                .to_string(),
1601            "duplicate column name n: the property column repeats the nested column"
1602        );
1603    }
1604
1605    #[cfg(feature = "unstable-v2")]
1606    #[test]
1607    fn add_nested_rejects_a_name_a_property_has() {
1608        let mut layer = TileLayer::new("layer", 4096).unwrap();
1609        layer.add_property("n", PropKind::I32).unwrap();
1610        assert_eq!(
1611            layer.add_nested("n", list_kind()).unwrap_err().to_string(),
1612            "duplicate column name n: the nested column repeats the property column"
1613        );
1614    }
1615
1616    #[cfg(feature = "unstable-v2")]
1617    #[test]
1618    fn add_nested_rejects_a_name_an_m_value_already_has() {
1619        let mut layer = TileLayer::new("layer", 4096).unwrap();
1620        layer.add_m_value("n", PropKind::I32).unwrap();
1621        assert_eq!(
1622            layer.add_nested("n", list_kind()).unwrap_err().to_string(),
1623            "duplicate column name n: the nested column repeats the m-value column"
1624        );
1625    }
1626
1627    #[cfg(feature = "unstable-v2")]
1628    #[test]
1629    fn add_nested_rejects_duplicate_names() {
1630        let mut layer = TileLayer::new("layer", 4096).unwrap();
1631        layer.add_nested("n", list_kind()).unwrap();
1632        assert_eq!(
1633            layer.add_nested("n", list_kind()).unwrap_err().to_string(),
1634            "duplicate column name n: the nested column repeats the nested column"
1635        );
1636    }
1637
1638    #[cfg(feature = "unstable-v2")]
1639    #[test]
1640    fn set_nested_rejects_a_value_of_another_shape() {
1641        let mut layer = TileLayer::new("layer", 4096).unwrap();
1642        let key = layer
1643            .add_nested("n", NestedKind::list(NestedKind::Leaf(PropKind::I32)))
1644            .unwrap();
1645        let mut feature = point_feature(vec![]);
1646        feature.nested = vec![NestedValue::List(None)];
1647        assert_eq!(
1648            feature
1649                .set_nested(key, NestedValue::Map(None))
1650                .unwrap_err()
1651                .to_string(),
1652            "nested column 0 was given a value of another shape"
1653        );
1654    }
1655
1656    #[test]
1657    fn builder_uses_declared_property_kind_for_defaults() {
1658        let mut builder = TileLayer::builder("layer", 4096).unwrap();
1659        let flag = builder.add_property("flag", PropKind::Bool).unwrap();
1660        let mut feature = builder.feature(Geometry::Point(Point::new(0, 0)));
1661        feature.property(flag, PropValue::Bool(Some(true))).unwrap();
1662        feature.finish().unwrap();
1663        let layer = builder.finish();
1664
1665        assert_eq!(
1666            layer.features()[0].properties()[0],
1667            PropValue::Bool(Some(true))
1668        );
1669    }
1670}