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::{Coord, 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 step as the spec's table names it, e.g. `1 dm`.
129#[cfg(feature = "unstable-v2")]
130impl std::fmt::Display for ZStep {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.write_str(match self.0 {
133            -3 => "1 mm",
134            -2 => "1 cm",
135            -1 => "1 dm",
136            0 => "1 m",
137            1 => "10 m",
138            2 => "100 m",
139            3 => "1 km",
140            _ => "10 km",
141        })
142    }
143}
144
145/// The kind of column a name belongs to, which a layer's names are unique across.
146#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display)]
147pub enum ColumnRole {
148    /// An ordinary column of values, a shared dictionary's children included.
149    #[strum(serialize = "property")]
150    Property,
151    /// A vertex-scoped column of the m-value section.
152    #[cfg(feature = "unstable-v2")]
153    #[strum(serialize = "m-value")]
154    MValue,
155    /// A shredded column of maps or lists.
156    #[cfg(feature = "unstable-v2")]
157    #[strum(serialize = "nested")]
158    Nested,
159}
160
161/// Row-oriented working form for the optimizer.
162///
163/// All features are stored as a flat [`Vec<TileFeature>`] so that sorting is
164/// a single `sort_by_cached_key` call.  The `property_names` vec is parallel
165/// to every `TileFeature::properties` slice in this layer.
166#[derive(Debug, Clone, PartialEq)]
167pub struct TileLayer {
168    pub(crate) name: String,
169    pub(crate) extent: Extent,
170    /// Column names, parallel to `TileFeature::properties`.
171    pub(crate) property_names: Vec<String>,
172    /// Column types, parallel to `TileFeature::properties`.
173    pub(crate) property_kinds: Vec<PropKind>,
174    /// Vertex-scoped column names, parallel to `TileFeature::m_values`.
175    #[cfg(feature = "unstable-v2")]
176    pub(crate) m_value_names: Vec<String>,
177    /// Vertex-scoped column types, parallel to `TileFeature::m_values`.
178    #[cfg(feature = "unstable-v2")]
179    pub(crate) m_value_kinds: Vec<PropKind>,
180    /// Nested column names, parallel to `TileFeature::nested`.
181    #[cfg(feature = "unstable-v2")]
182    pub(crate) nested_names: Vec<String>,
183    /// Nested column shapes, parallel to `TileFeature::nested`.
184    #[cfg(feature = "unstable-v2")]
185    pub(crate) nested_kinds: Vec<NestedKind>,
186    /// The grid of every feature's `TileFeature::z`, or [`None`] for a flat layer.
187    #[cfg(feature = "unstable-v2")]
188    pub(crate) z_step: Option<ZStep>,
189    pub(crate) features: Vec<TileFeature>,
190}
191
192/// A single map feature in row form.
193#[derive(Debug, Clone, PartialEq)]
194pub struct TileFeature {
195    pub(crate) id: Option<u64>,
196    /// Geometry as a [`geo_types`] form
197    pub(crate) geometry: Geometry<i32>,
198    /// One value per property column, in the same order as
199    /// [`TileLayer::property_names`].
200    pub(crate) properties: Vec<PropValue>,
201    /// One entry per m-value column, in the same order as
202    /// [`TileLayer::m_value_names`], each holding one value per vertex of this
203    /// feature's geometry or none at all.
204    #[cfg(feature = "unstable-v2")]
205    pub(crate) m_values: Vec<MValue>,
206    /// One value per nested column, in the same order as [`TileLayer::nested_names`].
207    #[cfg(feature = "unstable-v2")]
208    pub(crate) nested: Vec<NestedValue>,
209    /// One z per vertex of the geometry on the layer's [`ZStep`] grid, or none in a flat layer.
210    #[cfg(feature = "unstable-v2")]
211    pub(crate) z: Vec<i32>,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
215pub struct PropertyKey(usize);
216
217impl PropertyKey {
218    pub(crate) const fn new(index: usize) -> Self {
219        Self(index)
220    }
221
222    #[must_use]
223    pub fn index(self) -> usize {
224        self.0
225    }
226}
227
228/// Handle to one of a layer's m-value columns, as [`PropertyKey`] is to a property column.
229#[cfg(feature = "unstable-v2")]
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
231pub struct MValueKey(usize);
232
233#[cfg(feature = "unstable-v2")]
234impl MValueKey {
235    #[must_use]
236    pub fn index(self) -> usize {
237        self.0
238    }
239}
240
241/// Handle to one of a layer's nested columns, as [`PropertyKey`] is to a property column.
242#[cfg(feature = "unstable-v2")]
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
244pub struct NestedKey(usize);
245
246#[cfg(feature = "unstable-v2")]
247impl NestedKey {
248    #[must_use]
249    pub fn index(self) -> usize {
250        self.0
251    }
252}
253
254/// How deep a nested column may go, counting its root as the first level.
255#[cfg(feature = "unstable-v2")]
256pub(crate) const MAX_NESTED_DEPTH: usize = 8;
257
258/// The shape of one nested column, the union of every value it holds.
259#[cfg(feature = "unstable-v2")]
260#[derive(Debug, Clone, PartialEq, Eq)]
261pub enum NestedKind {
262    Leaf(PropKind),
263    List(Box<Self>),
264    /// String keys, each with its own kind, so a heterogeneous object is one of these.
265    Map(BTreeMap<String, Self>),
266}
267
268#[cfg(feature = "unstable-v2")]
269impl NestedKind {
270    /// A map of the given fields, the shape a struct of them shreds into.
271    #[must_use]
272    pub fn map<K: Into<String>>(fields: impl IntoIterator<Item = (K, Self)>) -> Self {
273        Self::Map(fields.into_iter().map(|(k, v)| (k.into(), v)).collect())
274    }
275
276    /// A list of `element`.
277    #[must_use]
278    pub fn list(element: Self) -> Self {
279        Self::List(Box::new(element))
280    }
281
282    /// How many levels this shape has, counting itself as the first.
283    #[must_use]
284    pub fn depth(&self) -> usize {
285        1 + match self {
286            Self::Leaf(_) => 0,
287            Self::List(element) => element.depth(),
288            Self::Map(fields) => fields.values().map(Self::depth).max().unwrap_or(0),
289        }
290    }
291
292    /// The value a feature that carries nothing for this column holds.
293    #[must_use]
294    pub fn null_value(&self) -> NestedValue {
295        match self {
296            Self::Leaf(kind) => NestedValue::Leaf(PropValue::null(*kind)),
297            Self::List(_) => NestedValue::List(None),
298            Self::Map(_) => NestedValue::Map(None),
299        }
300    }
301
302    /// Whether `value` is one this shape describes.
303    ///
304    /// A map may leave a field out, which is how a null field is written.
305    #[must_use]
306    pub fn accepts(&self, value: &NestedValue) -> bool {
307        match (self, value) {
308            (Self::Leaf(kind), NestedValue::Leaf(value)) => value.kind() == *kind,
309            (Self::List(_), NestedValue::List(None)) | (Self::Map(_), NestedValue::Map(None)) => {
310                true
311            }
312            (Self::List(element), NestedValue::List(Some(items))) => {
313                items.iter().all(|item| element.accepts(item))
314            }
315            (Self::Map(fields), NestedValue::Map(Some(entries))) => entries
316                .iter()
317                .all(|(key, value)| fields.get(key).is_some_and(|kind| kind.accepts(value))),
318            _ => false,
319        }
320    }
321
322    /// Whether a shape with no fields, which no struct node can hold, sits anywhere in this one.
323    fn has_empty_map(&self) -> bool {
324        match self {
325            Self::Leaf(_) => false,
326            Self::List(element) => element.has_empty_map(),
327            Self::Map(fields) => fields.is_empty() || fields.values().any(Self::has_empty_map),
328        }
329    }
330}
331
332/// One feature's value for one nested column.
333///
334/// A map leaves out the keys it has no value for, so a null field is an absent one.
335#[cfg(feature = "unstable-v2")]
336#[derive(Debug, Clone, PartialEq)]
337pub enum NestedValue {
338    Leaf(PropValue),
339    List(Option<Vec<Self>>),
340    Map(Option<BTreeMap<String, Self>>),
341}
342
343#[cfg(feature = "unstable-v2")]
344impl NestedValue {
345    /// A map of the given entries.
346    #[must_use]
347    pub fn map<K: Into<String>>(entries: impl IntoIterator<Item = (K, Self)>) -> Self {
348        Self::Map(Some(
349            entries.into_iter().map(|(k, v)| (k.into(), v)).collect(),
350        ))
351    }
352
353    /// A list of the given items.
354    #[must_use]
355    pub fn list(items: impl IntoIterator<Item = Self>) -> Self {
356        Self::List(Some(items.into_iter().collect()))
357    }
358
359    /// Whether this carries nothing at all, which is how a nested value is null.
360    #[must_use]
361    pub fn is_null(&self) -> bool {
362        match self {
363            Self::Leaf(value) => value.is_null(),
364            Self::List(items) => items.is_none(),
365            Self::Map(entries) => entries.is_none(),
366        }
367    }
368
369    /// The heap bytes this value holds, which the decoder charges its budget for.
370    #[must_use]
371    pub(crate) fn heap_bytes(&self) -> usize {
372        match self {
373            Self::Leaf(PropValue::Str(Some(value))) => value.len(),
374            Self::Leaf(_) => size_of::<PropValue>(),
375            Self::List(items) => items.iter().flatten().map(Self::heap_bytes).sum::<usize>(),
376            Self::Map(entries) => entries
377                .iter()
378                .flatten()
379                .map(|(key, value)| key.len() + value.heap_bytes())
380                .sum(),
381        }
382    }
383
384    /// Whether the two are the same kind of value, which is all a feature can check
385    /// without the column's shape.
386    fn same_shape(&self, other: &Self) -> bool {
387        matches!(
388            (self, other),
389            (Self::Leaf(_), Self::Leaf(_))
390                | (Self::List(_), Self::List(_))
391                | (Self::Map(_), Self::Map(_))
392        )
393    }
394}
395
396impl TileLayer {
397    pub fn new(name: impl Into<String>, extent: u32) -> MltResult<Self> {
398        Self::with_capacity(name, extent, 0)
399    }
400
401    pub fn with_capacity(name: impl Into<String>, extent: u32, features: usize) -> MltResult<Self> {
402        let name = name.into();
403        validate_layer_name(&name)?;
404        let extent = Extent::new(extent)?;
405        Ok(Self {
406            name,
407            extent,
408            property_names: Vec::new(),
409            property_kinds: Vec::new(),
410            #[cfg(feature = "unstable-v2")]
411            m_value_names: Vec::new(),
412            #[cfg(feature = "unstable-v2")]
413            m_value_kinds: Vec::new(),
414            #[cfg(feature = "unstable-v2")]
415            nested_names: Vec::new(),
416            #[cfg(feature = "unstable-v2")]
417            nested_kinds: Vec::new(),
418            #[cfg(feature = "unstable-v2")]
419            z_step: None,
420            features: Vec::with_capacity(features),
421        })
422    }
423
424    pub(crate) fn from_parts(
425        name: impl Into<String>,
426        extent: u32,
427        property_names: Vec<String>,
428        features: Vec<TileFeature>,
429    ) -> MltResult<Self> {
430        let name = name.into();
431        validate_layer_name(&name)?;
432        let extent = Extent::new(extent)?;
433        validate_unique_names(&property_names, ColumnRole::Property)?;
434        let property_kinds = infer_kinds::<PropValue>(&property_names, &features)?;
435        let layer = Self {
436            name,
437            extent,
438            property_names,
439            property_kinds,
440            #[cfg(feature = "unstable-v2")]
441            m_value_names: Vec::new(),
442            #[cfg(feature = "unstable-v2")]
443            m_value_kinds: Vec::new(),
444            #[cfg(feature = "unstable-v2")]
445            nested_names: Vec::new(),
446            #[cfg(feature = "unstable-v2")]
447            nested_kinds: Vec::new(),
448            #[cfg(feature = "unstable-v2")]
449            z_step: None,
450            features,
451        };
452        Ok(layer)
453    }
454
455    #[must_use]
456    pub fn name(&self) -> &str {
457        &self.name
458    }
459
460    #[must_use]
461    pub fn extent(&self) -> Extent {
462        self.extent
463    }
464
465    #[must_use]
466    pub fn property_names(&self) -> &[String] {
467        &self.property_names
468    }
469
470    #[must_use]
471    pub fn property_kinds(&self) -> &[PropKind] {
472        &self.property_kinds
473    }
474
475    #[must_use]
476    pub fn features(&self) -> &[TileFeature] {
477        &self.features
478    }
479
480    #[must_use]
481    pub fn feature_count(&self) -> usize {
482        self.features.len()
483    }
484
485    /// Reject a column name any column of this layer already holds.
486    fn reject_name_in_use(&self, name: &str, role: ColumnRole) -> MltResult<()> {
487        let taken = self
488            .property_names
489            .iter()
490            .map(|n| (n.as_str(), ColumnRole::Property));
491        #[cfg(feature = "unstable-v2")]
492        let taken = taken
493            .chain(
494                self.m_value_names
495                    .iter()
496                    .map(|n| (n.as_str(), ColumnRole::MValue)),
497            )
498            .chain(
499                self.nested_names
500                    .iter()
501                    .map(|n| (n.as_str(), ColumnRole::Nested)),
502            );
503        reject_taken_name(name, role, taken)
504    }
505
506    pub fn add_property(
507        &mut self,
508        name: impl Into<String>,
509        kind: PropKind,
510    ) -> MltResult<PropertyKey> {
511        let name = name.into();
512        self.reject_name_in_use(&name, ColumnRole::Property)?;
513        for feature in &mut self.features {
514            feature.properties.push(PropValue::null(kind));
515        }
516        self.property_names.push(name);
517        self.property_kinds.push(kind);
518        Ok(PropertyKey(self.property_names.len() - 1))
519    }
520
521    #[cfg(feature = "unstable-v2")]
522    #[must_use]
523    pub fn m_value_names(&self) -> &[String] {
524        &self.m_value_names
525    }
526
527    #[cfg(feature = "unstable-v2")]
528    #[must_use]
529    pub fn m_value_kinds(&self) -> &[PropKind] {
530        &self.m_value_kinds
531    }
532
533    /// Declare a vertex-scoped column, whose values every feature then holds or does not.
534    #[cfg(feature = "unstable-v2")]
535    pub fn add_m_value(&mut self, name: impl Into<String>, kind: PropKind) -> MltResult<MValueKey> {
536        let name = name.into();
537        self.reject_name_in_use(&name, ColumnRole::MValue)?;
538        for feature in &mut self.features {
539            feature.m_values.push(MValue::null(kind));
540        }
541        self.m_value_names.push(name);
542        self.m_value_kinds.push(kind);
543        Ok(MValueKey(self.m_value_names.len() - 1))
544    }
545
546    /// Name the vertex-scoped columns the features already carry, as
547    /// [`Self::from_parts`] names the property columns.
548    #[cfg(feature = "unstable-v2")]
549    pub(crate) fn with_m_value_names(mut self, names: Vec<String>) -> MltResult<Self> {
550        validate_unique_names(&names, ColumnRole::MValue)?;
551        for name in &names {
552            self.reject_name_in_use(name, ColumnRole::MValue)?;
553        }
554        self.m_value_kinds = infer_kinds::<MValue>(&names, &self.features)?;
555        self.m_value_names = names;
556        for feature in &self.features {
557            self.validate_m_values(feature)?;
558        }
559        Ok(self)
560    }
561
562    #[cfg(feature = "unstable-v2")]
563    #[must_use]
564    pub fn nested_names(&self) -> &[String] {
565        &self.nested_names
566    }
567
568    #[cfg(feature = "unstable-v2")]
569    #[must_use]
570    pub fn nested_kinds(&self) -> &[NestedKind] {
571        &self.nested_kinds
572    }
573
574    /// The grid the features' z coordinates lie on, or [`None`] for a flat layer.
575    #[cfg(feature = "unstable-v2")]
576    #[must_use]
577    pub fn z_step(&self) -> Option<ZStep> {
578        self.z_step
579    }
580
581    /// Give the layer z coordinates on `step`, which every feature pushed after this must carry.
582    ///
583    /// A layer that already holds features is rejected, since they carry none.
584    #[cfg(feature = "unstable-v2")]
585    pub fn set_z_step(&mut self, step: ZStep) -> MltResult<()> {
586        if !self.features.is_empty() {
587            return Err(MltError::ZStepOnNonEmptyLayer(self.name.clone()));
588        }
589        self.z_step = Some(step);
590        Ok(())
591    }
592
593    /// Put the features, which already carry their z coordinates, on `step`.
594    #[cfg(feature = "unstable-v2")]
595    pub(crate) fn with_z_step(mut self, step: Option<ZStep>) -> MltResult<Self> {
596        self.z_step = step;
597        for feature in &self.features {
598            self.validate_z(feature)?;
599        }
600        Ok(self)
601    }
602
603    /// Declare a nested column of the given shape, which every feature then holds a value of.
604    ///
605    /// A scalar root is an ordinary property column, so [`NestedKind::Leaf`] is rejected here.
606    #[cfg(feature = "unstable-v2")]
607    pub fn add_nested(
608        &mut self,
609        name: impl Into<String>,
610        kind: NestedKind,
611    ) -> MltResult<NestedKey> {
612        let name = name.into();
613        self.reject_name_in_use(&name, ColumnRole::Nested)?;
614        validate_nested_kind(&name, &kind)?;
615        for feature in &mut self.features {
616            feature.nested.push(kind.null_value());
617        }
618        self.nested_names.push(name);
619        self.nested_kinds.push(kind);
620        Ok(NestedKey(self.nested_names.len() - 1))
621    }
622
623    /// Name and shape the nested columns the features already carry, as
624    /// [`Self::from_parts`] names the property columns.
625    #[cfg(feature = "unstable-v2")]
626    pub(crate) fn with_nested(
627        mut self,
628        names: Vec<String>,
629        kinds: Vec<NestedKind>,
630    ) -> MltResult<Self> {
631        validate_unique_names(&names, ColumnRole::Nested)?;
632        for name in &names {
633            self.reject_name_in_use(name, ColumnRole::Nested)?;
634        }
635        for (name, kind) in names.iter().zip(&kinds) {
636            validate_nested_kind(name, kind)?;
637        }
638        self.nested_names = names;
639        self.nested_kinds = kinds;
640        for feature in &self.features {
641            self.validate_nested(feature)?;
642        }
643        Ok(self)
644    }
645
646    pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
647        self.validate_feature(&feature)?;
648        self.features.push(feature);
649        Ok(())
650    }
651
652    pub fn builder(name: impl Into<String>, extent: u32) -> MltResult<TileLayerBuilder> {
653        Ok(TileLayerBuilder {
654            layer: Self::new(name, extent)?,
655        })
656    }
657
658    /// A builder for a layer with the name, extent and z step of this one, and no columns or features.
659    #[must_use]
660    pub fn builder_like(&self) -> TileLayerBuilder {
661        let layer = Self {
662            name: self.name.clone(),
663            extent: self.extent,
664            property_names: Vec::new(),
665            property_kinds: Vec::new(),
666            #[cfg(feature = "unstable-v2")]
667            m_value_names: Vec::new(),
668            #[cfg(feature = "unstable-v2")]
669            m_value_kinds: Vec::new(),
670            #[cfg(feature = "unstable-v2")]
671            nested_names: Vec::new(),
672            #[cfg(feature = "unstable-v2")]
673            nested_kinds: Vec::new(),
674            #[cfg(feature = "unstable-v2")]
675            z_step: self.z_step,
676            features: Vec::new(),
677        };
678        TileLayerBuilder { layer }
679    }
680
681    fn validate_feature(&self, feature: &TileFeature) -> MltResult<()> {
682        validate_kinds::<PropValue>(&self.property_kinds, feature)?;
683        #[cfg(feature = "unstable-v2")]
684        self.validate_m_values(feature)?;
685        #[cfg(feature = "unstable-v2")]
686        self.validate_nested(feature)?;
687        #[cfg(feature = "unstable-v2")]
688        self.validate_z(feature)?;
689        Ok(())
690    }
691
692    /// Check a feature's z coordinates against the layer: one per vertex on a z layer, none on a flat one.
693    #[cfg(feature = "unstable-v2")]
694    fn validate_z(&self, feature: &TileFeature) -> MltResult<()> {
695        let expected = if self.z_step.is_some() {
696            feature.vertex_count()
697        } else {
698            0
699        };
700        if feature.z.len() == expected {
701            Ok(())
702        } else {
703            Err(MltError::ZVertexCountMismatch {
704                expected,
705                actual: feature.z.len(),
706            })
707        }
708    }
709
710    /// Check a feature's nested values against the layer's columns: one value per
711    /// column, of the shape the column declared.
712    #[cfg(feature = "unstable-v2")]
713    fn validate_nested(&self, feature: &TileFeature) -> MltResult<()> {
714        if feature.nested.len() != self.nested_kinds.len() {
715            return Err(MltError::NestedColumnCountMismatch {
716                expected: self.nested_kinds.len(),
717                actual: feature.nested.len(),
718            });
719        }
720        for (index, (value, kind)) in feature.nested.iter().zip(&self.nested_kinds).enumerate() {
721            if !kind.accepts(value) {
722                return Err(MltError::NestedValueMismatch {
723                    index,
724                    name: self.nested_names[index].clone(),
725                });
726            }
727        }
728        Ok(())
729    }
730
731    /// Check a feature's m-values against the layer's columns: one entry per
732    /// column, of the column's kind, holding one value per vertex it has.
733    #[cfg(feature = "unstable-v2")]
734    fn validate_m_values(&self, feature: &TileFeature) -> MltResult<()> {
735        validate_kinds::<MValue>(&self.m_value_kinds, feature)?;
736        let vertices = feature.vertex_count();
737        for (index, m_value) in feature.m_values.iter().enumerate() {
738            if let Some(len) = m_value.count()
739                && len != vertices
740            {
741                return Err(MltError::MValueVertexCountMismatch {
742                    index,
743                    expected: vertices,
744                    actual: len,
745                });
746            }
747        }
748        Ok(())
749    }
750}
751
752impl TileFeature {
753    #[must_use]
754    pub fn new(geometry: Geometry<i32>) -> Self {
755        Self {
756            id: None,
757            geometry,
758            properties: Vec::new(),
759            #[cfg(feature = "unstable-v2")]
760            m_values: Vec::new(),
761            #[cfg(feature = "unstable-v2")]
762            nested: Vec::new(),
763            #[cfg(feature = "unstable-v2")]
764            z: Vec::new(),
765        }
766    }
767
768    #[must_use]
769    pub fn with_id(geometry: Geometry<i32>, id: u64) -> Self {
770        Self {
771            id: Some(id),
772            geometry,
773            properties: Vec::new(),
774            #[cfg(feature = "unstable-v2")]
775            m_values: Vec::new(),
776            #[cfg(feature = "unstable-v2")]
777            nested: Vec::new(),
778            #[cfg(feature = "unstable-v2")]
779            z: Vec::new(),
780        }
781    }
782
783    #[must_use]
784    pub fn id(&self) -> Option<u64> {
785        self.id
786    }
787
788    #[must_use]
789    pub fn geometry(&self) -> &Geometry<i32> {
790        &self.geometry
791    }
792
793    #[must_use]
794    pub fn properties(&self) -> &[PropValue] {
795        &self.properties
796    }
797
798    /// How many vertices this feature stores, which is how many values each of
799    /// its m-values holds.
800    ///
801    /// A polygon ring's closing vertex is not stored, so it is not counted.
802    #[cfg(feature = "unstable-v2")]
803    #[must_use]
804    pub fn vertex_count(&self) -> usize {
805        stored_vertex_count(&self.geometry)
806    }
807
808    #[cfg(feature = "unstable-v2")]
809    #[must_use]
810    pub fn m_values(&self) -> &[MValue] {
811        &self.m_values
812    }
813
814    /// One z per vertex, in the order [`Self::vertex_count`] counts them, or none in a flat layer.
815    #[cfg(feature = "unstable-v2")]
816    #[must_use]
817    pub fn z(&self) -> &[i32] {
818        &self.z
819    }
820
821    /// Set this feature's z coordinates, one per vertex it stores.
822    #[cfg(feature = "unstable-v2")]
823    pub fn set_z(&mut self, z: Vec<i32>) -> MltResult<()> {
824        let expected = self.vertex_count();
825        if z.len() != expected {
826            return Err(MltError::ZVertexCountMismatch {
827                expected,
828                actual: z.len(),
829            });
830        }
831        self.z = z;
832        Ok(())
833    }
834
835    /// Set this feature's values for one m-value column, which must hold one
836    /// value per vertex unless the feature has none at all.
837    #[cfg(feature = "unstable-v2")]
838    pub fn set_m_value(&mut self, key: MValueKey, value: MValue) -> MltResult<()> {
839        let vertices = self.vertex_count();
840        let Some(slot) = self.m_values.get_mut(key.index()) else {
841            return Err(MltError::UnknownMValueKey {
842                index: key.index(),
843                columns: self.m_values.len(),
844            });
845        };
846        let expected = slot.kind();
847        let actual = value.kind();
848        if actual != expected {
849            return Err(MltError::MValueKindMismatch {
850                index: key.index(),
851                expected,
852                actual,
853            });
854        }
855        if let Some(len) = value.count()
856            && len != vertices
857        {
858            return Err(MltError::MValueVertexCountMismatch {
859                index: key.index(),
860                expected: vertices,
861                actual: len,
862            });
863        }
864        *slot = value;
865        Ok(())
866    }
867
868    #[cfg(feature = "unstable-v2")]
869    #[must_use]
870    pub fn nested(&self) -> &[NestedValue] {
871        &self.nested
872    }
873
874    /// Set this feature's value for one nested column, which must be the kind of
875    /// value the column holds.
876    ///
877    /// Only the layer knows the column's full shape, so that is checked when the
878    /// feature is pushed.
879    #[cfg(feature = "unstable-v2")]
880    pub fn set_nested(&mut self, key: NestedKey, value: NestedValue) -> MltResult<()> {
881        let columns = self.nested.len();
882        let Some(slot) = self.nested.get_mut(key.index()) else {
883            return Err(MltError::UnknownNestedKey {
884                index: key.index(),
885                columns,
886            });
887        };
888        if !slot.same_shape(&value) {
889            return Err(MltError::NestedShapeMismatch { index: key.index() });
890        }
891        *slot = value;
892        Ok(())
893    }
894
895    pub fn set_property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<()> {
896        let Some(prop) = self.properties.get_mut(key.index()) else {
897            return Err(MltError::UnknownProperty {
898                index: key.index(),
899                count: self.properties.len(),
900            });
901        };
902        let expected = PropKind::from(&*prop);
903        let actual = PropKind::from(&value);
904        if actual != expected {
905            return Err(MltError::PropertyKindMismatch {
906                index: key.index(),
907                expected,
908                actual,
909            });
910        }
911        *prop = value;
912        Ok(())
913    }
914}
915
916pub struct TileLayerBuilder {
917    layer: TileLayer,
918}
919
920impl TileLayerBuilder {
921    pub fn add_property(
922        &mut self,
923        name: impl Into<String>,
924        kind: PropKind,
925    ) -> MltResult<PropertyKey> {
926        self.layer.add_property(name, kind)
927    }
928
929    #[cfg(feature = "unstable-v2")]
930    pub fn add_m_value(&mut self, name: impl Into<String>, kind: PropKind) -> MltResult<MValueKey> {
931        self.layer.add_m_value(name, kind)
932    }
933
934    #[cfg(feature = "unstable-v2")]
935    pub fn add_nested(
936        &mut self,
937        name: impl Into<String>,
938        kind: NestedKind,
939    ) -> MltResult<NestedKey> {
940        self.layer.add_nested(name, kind)
941    }
942
943    #[cfg(feature = "unstable-v2")]
944    pub fn set_z_step(&mut self, step: ZStep) -> MltResult<()> {
945        self.layer.set_z_step(step)
946    }
947
948    pub fn feature(&mut self, geometry: Geometry<i32>) -> TileFeatureBuilder<'_> {
949        let properties = self
950            .layer
951            .property_kinds
952            .iter()
953            .copied()
954            .map(PropValue::null)
955            .collect();
956        #[cfg(feature = "unstable-v2")]
957        let m_values = self
958            .layer
959            .m_value_kinds
960            .iter()
961            .copied()
962            .map(MValue::null)
963            .collect();
964        #[cfg(feature = "unstable-v2")]
965        let nested = self
966            .layer
967            .nested_kinds
968            .iter()
969            .map(NestedKind::null_value)
970            .collect();
971        TileFeatureBuilder {
972            layer: self,
973            feature: TileFeature {
974                id: None,
975                geometry,
976                properties,
977                #[cfg(feature = "unstable-v2")]
978                m_values,
979                #[cfg(feature = "unstable-v2")]
980                nested,
981                #[cfg(feature = "unstable-v2")]
982                z: Vec::new(),
983            },
984        }
985    }
986
987    /// Declare every property column of `layer`, in its order.
988    pub fn add_properties_like(&mut self, layer: &TileLayer) -> MltResult<Vec<PropertyKey>> {
989        (layer.property_names.iter().zip(&layer.property_kinds))
990            .map(|(name, &kind)| self.add_property(name, kind))
991            .collect()
992    }
993
994    /// Declare every m-value column of `layer`, in its order.
995    #[cfg(feature = "unstable-v2")]
996    pub fn add_m_values_like(&mut self, layer: &TileLayer) -> MltResult<Vec<MValueKey>> {
997        (layer.m_value_names.iter().zip(&layer.m_value_kinds))
998            .map(|(name, &kind)| self.add_m_value(name, kind))
999            .collect()
1000    }
1001
1002    /// Declare every nested column of `layer`, in its order.
1003    #[cfg(feature = "unstable-v2")]
1004    pub fn add_nested_like(&mut self, layer: &TileLayer) -> MltResult<Vec<NestedKey>> {
1005        (layer.nested_names.iter().zip(&layer.nested_kinds))
1006            .map(|(name, kind)| self.add_nested(name, kind.clone()))
1007            .collect()
1008    }
1009
1010    /// A feature with the geometry, id and z of `feature`, and no values yet.
1011    pub fn feature_like(&mut self, feature: &TileFeature) -> TileFeatureBuilder<'_> {
1012        let mut row = self.feature(feature.geometry.clone());
1013        row.feature.id = feature.id;
1014        #[cfg(feature = "unstable-v2")]
1015        row.feature.z.clone_from(&feature.z);
1016        row
1017    }
1018
1019    pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
1020        self.layer.push_feature(feature)
1021    }
1022
1023    #[must_use]
1024    pub fn finish(self) -> TileLayer {
1025        self.layer
1026    }
1027}
1028
1029pub struct TileFeatureBuilder<'a> {
1030    layer: &'a mut TileLayerBuilder,
1031    feature: TileFeature,
1032}
1033
1034impl TileFeatureBuilder<'_> {
1035    pub fn id(&mut self, id: Option<u64>) -> &mut Self {
1036        self.feature.id = id;
1037        self
1038    }
1039
1040    pub fn property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<&mut Self> {
1041        self.feature.set_property(key, value)?;
1042        Ok(self)
1043    }
1044
1045    #[cfg(feature = "unstable-v2")]
1046    pub fn m_value(&mut self, key: MValueKey, value: MValue) -> MltResult<&mut Self> {
1047        self.feature.set_m_value(key, value)?;
1048        Ok(self)
1049    }
1050
1051    #[cfg(feature = "unstable-v2")]
1052    pub fn nested(&mut self, key: NestedKey, value: NestedValue) -> MltResult<&mut Self> {
1053        self.feature.set_nested(key, value)?;
1054        Ok(self)
1055    }
1056
1057    #[cfg(feature = "unstable-v2")]
1058    pub fn z(&mut self, z: Vec<i32>) -> MltResult<&mut Self> {
1059        self.feature.set_z(z)?;
1060        Ok(self)
1061    }
1062
1063    pub fn finish(self) -> MltResult<()> {
1064        self.layer.push_feature(self.feature)
1065    }
1066}
1067
1068/// A single typed value for one property of one feature.
1069///
1070/// Mirrors the scalar variants of `ParsedProperty` at the per-feature
1071/// level. `SharedDict` items are flattened: each sub-field becomes its own
1072/// `PropValue::Str` entry in `TileFeature::properties`, with the
1073/// corresponding entry in `TileLayer::property_names` set to
1074/// `"prefix:suffix"`.
1075#[derive(Debug, Clone, PartialEq)]
1076pub enum PropValue {
1077    Bool(Option<bool>),
1078    I8(Option<i8>),
1079    U8(Option<u8>),
1080    I32(Option<i32>),
1081    U32(Option<u32>),
1082    I64(Option<i64>),
1083    U64(Option<u64>),
1084    F32(Option<f32>),
1085    F64(Option<f64>),
1086    Str(Option<String>),
1087}
1088
1089impl PropValue {
1090    #[must_use]
1091    pub fn kind(&self) -> PropKind {
1092        self.into()
1093    }
1094}
1095
1096/// A feature's values for one vertex-scoped column: one per vertex, or none at all.
1097///
1098/// The option is around the whole vector because a null is a feature's, not a
1099/// vertex's: a feature either measures every one of its vertices or none of them.
1100#[cfg(feature = "unstable-v2")]
1101#[derive(Debug, Clone, PartialEq)]
1102pub enum MValue {
1103    Bool(Option<Vec<bool>>),
1104    I8(Option<Vec<i8>>),
1105    U8(Option<Vec<u8>>),
1106    I32(Option<Vec<i32>>),
1107    U32(Option<Vec<u32>>),
1108    I64(Option<Vec<i64>>),
1109    U64(Option<Vec<u64>>),
1110    F32(Option<Vec<f32>>),
1111    F64(Option<Vec<f64>>),
1112    Str(Option<Vec<String>>),
1113}
1114
1115#[cfg(feature = "unstable-v2")]
1116impl MValue {
1117    #[must_use]
1118    pub fn is_null(&self) -> bool {
1119        self.count().is_none()
1120    }
1121}
1122
1123/// A borrowed, non-null per-feature property value.
1124///
1125/// Nullability is lifted to [`ColumnRef`](crate::decoder::ColumnRef): only non-null values appear in
1126/// [`FeatureRef::iter_properties`](crate::decoder::FeatureRef::iter_properties).
1127#[derive(Debug, Clone, Copy, PartialEq)]
1128pub enum PropValueRef<'a> {
1129    Bool(bool),
1130    I8(i8),
1131    U8(u8),
1132    I32(i32),
1133    U32(u32),
1134    I64(i64),
1135    U64(u64),
1136    F32(f32),
1137    F64(f64),
1138    Str(&'a str),
1139}
1140
1141impl From<bool> for PropValueRef<'_> {
1142    fn from(v: bool) -> Self {
1143        Self::Bool(v)
1144    }
1145}
1146impl From<i8> for PropValueRef<'_> {
1147    fn from(v: i8) -> Self {
1148        Self::I8(v)
1149    }
1150}
1151impl From<u8> for PropValueRef<'_> {
1152    fn from(v: u8) -> Self {
1153        Self::U8(v)
1154    }
1155}
1156impl From<i32> for PropValueRef<'_> {
1157    fn from(v: i32) -> Self {
1158        Self::I32(v)
1159    }
1160}
1161impl From<u32> for PropValueRef<'_> {
1162    fn from(v: u32) -> Self {
1163        Self::U32(v)
1164    }
1165}
1166impl From<i64> for PropValueRef<'_> {
1167    fn from(v: i64) -> Self {
1168        Self::I64(v)
1169    }
1170}
1171impl From<u64> for PropValueRef<'_> {
1172    fn from(v: u64) -> Self {
1173        Self::U64(v)
1174    }
1175}
1176impl From<f32> for PropValueRef<'_> {
1177    fn from(v: f32) -> Self {
1178        Self::F32(v)
1179    }
1180}
1181impl From<f64> for PropValueRef<'_> {
1182    fn from(v: f64) -> Self {
1183        Self::F64(v)
1184    }
1185}
1186
1187impl<'a> From<&'a str> for PropValueRef<'a> {
1188    fn from(v: &'a str) -> Self {
1189        Self::Str(v)
1190    }
1191}
1192
1193macro_rules! kind_mappings {
1194    (
1195        scalar { $($sv:ident),* $(,)? }
1196        string { $($gv:ident),* $(,)? }
1197    ) => {
1198        /// The data type of one property or m-value column.
1199        #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)]
1200        #[strum(serialize_all = "lowercase")]
1201        pub enum PropKind {
1202            $($sv,)*
1203            $($gv,)*
1204        }
1205
1206        impl From<&PropValue> for PropKind {
1207            fn from(prop: &PropValue) -> Self {
1208                match prop {
1209                    $(PropValue::$sv(_) => Self::$sv,)*
1210                    $(PropValue::$gv(_) => Self::$gv,)*
1211                }
1212            }
1213        }
1214
1215        impl PropValue {
1216            #[must_use]
1217            pub fn is_null(&self) -> bool {
1218                match self {
1219                    $(Self::$sv(v) => v.is_none(),)*
1220                    $(Self::$gv(v) => v.is_none(),)*
1221                }
1222            }
1223
1224            /// The null value of `kind`.
1225            #[must_use]
1226            pub fn null(kind: PropKind) -> Self {
1227                match kind {
1228                    $(PropKind::$sv => Self::$sv(None),)*
1229                    $(PropKind::$gv => Self::$gv(None),)*
1230                }
1231            }
1232
1233            /// The value, borrowed, or [`None`] for a null.
1234            #[inline]
1235            #[must_use]
1236            pub fn value_ref(&self) -> Option<PropValueRef<'_>> {
1237                match self {
1238                    $(Self::$sv(v) => v.map(PropValueRef::$sv),)*
1239                    $(Self::$gv(v) => v.as_deref().map(PropValueRef::$gv),)*
1240                }
1241            }
1242        }
1243
1244        impl PropValueRef<'_> {
1245            /// The [`PropKind`] of this value.
1246            #[inline]
1247            #[must_use]
1248            pub fn kind(self) -> PropKind {
1249                match self {
1250                    $(Self::$sv(_) => PropKind::$sv,)*
1251                    $(Self::$gv(_) => PropKind::$gv,)*
1252                }
1253            }
1254        }
1255
1256        impl From<PropValueRef<'_>> for PropValue {
1257            fn from(value: PropValueRef<'_>) -> Self {
1258                match value {
1259                    $(PropValueRef::$sv(v) => Self::$sv(Some(v)),)*
1260                    $(PropValueRef::$gv(v) => Self::$gv(Some(v.to_owned())),)*
1261                }
1262            }
1263        }
1264
1265        #[cfg(feature = "unstable-v2")]
1266        impl MValue {
1267            #[must_use]
1268            pub fn kind(&self) -> PropKind {
1269                match self {
1270                    $(Self::$sv(_) => PropKind::$sv,)*
1271                    $(Self::$gv(_) => PropKind::$gv,)*
1272                }
1273            }
1274
1275            /// How many values this holds, or [`None`] when the feature carries none.
1276            #[must_use]
1277            pub fn count(&self) -> Option<usize> {
1278                match self {
1279                    $(Self::$sv(v) => v.as_ref().map(Vec::len),)*
1280                    $(Self::$gv(v) => v.as_ref().map(Vec::len),)*
1281                }
1282            }
1283
1284            /// The empty value of `kind`, which is what a feature with no values holds.
1285            #[must_use]
1286            pub fn null(kind: PropKind) -> Self {
1287                match kind {
1288                    $(PropKind::$sv => Self::$sv(None),)*
1289                    $(PropKind::$gv => Self::$gv(None),)*
1290                }
1291            }
1292        }
1293    };
1294}
1295
1296with_kinds!(kind_mappings);
1297
1298impl PropKind {
1299    fn is_signed_int(self) -> bool {
1300        matches!(self, Self::I8 | Self::I32 | Self::I64)
1301    }
1302
1303    fn is_unsigned_int(self) -> bool {
1304        matches!(self, Self::U8 | Self::U32 | Self::U64)
1305    }
1306
1307    fn is_float(self) -> bool {
1308        matches!(self, Self::F32 | Self::F64)
1309    }
1310
1311    /// The kind of a column holding values of both kinds.
1312    ///
1313    /// Equal kinds stay as they are, signed integers widen to `I64`, unsigned integers to `U64`,
1314    /// signed mixed with unsigned to `I64`, floats to `F64`, integers mixed with floats to `F64`,
1315    /// and any other mix to `Str`.
1316    #[must_use]
1317    pub fn merge(self, other: Self) -> Self {
1318        let int = |k: Self| k.is_signed_int() || k.is_unsigned_int();
1319        if self == other {
1320            self
1321        } else if int(self) && int(other) {
1322            if self.is_unsigned_int() && other.is_unsigned_int() {
1323                Self::U64
1324            } else {
1325                Self::I64
1326            }
1327        } else if (self.is_float() || int(self)) && (other.is_float() || int(other)) {
1328            Self::F64
1329        } else {
1330            Self::Str
1331        }
1332    }
1333}
1334
1335impl PropValueRef<'_> {
1336    fn as_int(self) -> Option<i128> {
1337        match self {
1338            Self::I8(v) => Some(i128::from(v)),
1339            Self::U8(v) => Some(i128::from(v)),
1340            Self::I32(v) => Some(i128::from(v)),
1341            Self::U32(v) => Some(i128::from(v)),
1342            Self::I64(v) => Some(i128::from(v)),
1343            Self::U64(v) => Some(i128::from(v)),
1344            Self::Bool(_) | Self::F32(_) | Self::F64(_) | Self::Str(_) => None,
1345        }
1346    }
1347
1348    /// `self` as a value of column kind `kind`.
1349    ///
1350    /// A matching kind is kept, an integer goes to another integer kind when it is in range,
1351    /// a float or integer goes to `F64` as an `as f64` cast, and anything goes to `Str` as its text.
1352    /// Every other conversion, and an out-of-range integer, gives the null of `kind`.
1353    #[must_use]
1354    pub fn coerce(self, kind: PropKind) -> PropValue {
1355        if self.kind() == kind {
1356            return self.into();
1357        }
1358        let int = self.as_int();
1359        #[expect(
1360            clippy::cast_precision_loss,
1361            reason = "integers in a float column are taken as floats"
1362        )]
1363        match kind {
1364            PropKind::I8 => PropValue::I8(int.and_then(|v| i8::try_from(v).ok())),
1365            PropKind::U8 => PropValue::U8(int.and_then(|v| u8::try_from(v).ok())),
1366            PropKind::I32 => PropValue::I32(int.and_then(|v| i32::try_from(v).ok())),
1367            PropKind::U32 => PropValue::U32(int.and_then(|v| u32::try_from(v).ok())),
1368            PropKind::I64 => PropValue::I64(int.and_then(|v| i64::try_from(v).ok())),
1369            PropKind::U64 => PropValue::U64(int.and_then(|v| u64::try_from(v).ok())),
1370            PropKind::F64 => PropValue::F64(match self {
1371                Self::F32(v) => Some(f64::from(v)),
1372                Self::F64(v) => Some(v),
1373                Self::Bool(_) | Self::Str(_) => None,
1374                Self::I8(_)
1375                | Self::U8(_)
1376                | Self::I32(_)
1377                | Self::U32(_)
1378                | Self::I64(_)
1379                | Self::U64(_) => int.map(|v| v as f64),
1380            }),
1381            PropKind::Str => PropValue::Str(Some(match self {
1382                Self::Bool(v) => v.to_string(),
1383                Self::I8(v) => v.to_string(),
1384                Self::U8(v) => v.to_string(),
1385                Self::I32(v) => v.to_string(),
1386                Self::U32(v) => v.to_string(),
1387                Self::I64(v) => v.to_string(),
1388                Self::U64(v) => v.to_string(),
1389                Self::F32(v) => v.to_string(),
1390                Self::F64(v) => v.to_string(),
1391                Self::Str(v) => v.to_string(),
1392            })),
1393            PropKind::Bool | PropKind::F32 => PropValue::null(kind),
1394        }
1395    }
1396}
1397
1398/// How many of a ring's coordinates MLT stores, which is all of them but a closing one.
1399pub(crate) fn stored_ring_len(ring: &LineString<i32>) -> usize {
1400    stored_len(&ring.0)
1401}
1402
1403/// [`stored_ring_len`] of a ring given as its coordinates.
1404pub(crate) fn stored_len(coords: &[Coord<i32>]) -> usize {
1405    if coords.len() > 1 && coords.last() == coords.first() {
1406        coords.len() - 1
1407    } else {
1408        coords.len()
1409    }
1410}
1411
1412/// How many vertices a geometry contributes to the layer's vertex sequence, which
1413/// is what an m-value column holds one value per.
1414#[cfg(feature = "unstable-v2")]
1415pub(crate) fn stored_vertex_count(geom: &Geometry<i32>) -> usize {
1416    let polygon = |poly: &geo_types::Polygon<i32>| {
1417        std::iter::once(poly.exterior())
1418            .chain(poly.interiors())
1419            .map(stored_ring_len)
1420            .sum::<usize>()
1421    };
1422    match geom {
1423        Geometry::Point(_) => 1,
1424        Geometry::Line(_) => 2,
1425        Geometry::LineString(ls) => ls.0.len(),
1426        Geometry::Polygon(p) => polygon(p),
1427        Geometry::MultiPoint(mp) => mp.0.len(),
1428        Geometry::MultiLineString(mls) => mls.iter().map(|ls| ls.0.len()).sum(),
1429        Geometry::MultiPolygon(mp) => mp.iter().map(polygon).sum(),
1430        Geometry::Triangle(t) => polygon(&t.to_polygon()),
1431        Geometry::Rect(r) => polygon(&r.to_polygon()),
1432        Geometry::GeometryCollection(gc) => gc.iter().map(stored_vertex_count).sum(),
1433    }
1434}
1435
1436/// Check a nested column's shape: an interior root, no empty field set, and no
1437/// more levels than the wire allows.
1438#[cfg(feature = "unstable-v2")]
1439fn validate_nested_kind(name: &str, kind: &NestedKind) -> MltResult<()> {
1440    if matches!(kind, NestedKind::Leaf(_)) {
1441        return Err(MltError::NestedRootIsLeaf(name.to_string()));
1442    }
1443    if kind.has_empty_map() {
1444        return Err(MltError::EmptyStructNode);
1445    }
1446    let depth = kind.depth();
1447    if depth > MAX_NESTED_DEPTH {
1448        return Err(MltError::NestedTooDeep(depth));
1449    }
1450    Ok(())
1451}
1452
1453pub(crate) fn validate_layer_name(name: &str) -> MltResult<()> {
1454    if name.is_empty() {
1455        Err(MltError::MissingLayerName)
1456    } else {
1457        Ok(())
1458    }
1459}
1460
1461/// Reject a column name one of `taken` already holds, naming the kind of column that holds it.
1462///
1463/// A layer has one namespace of column names, so this is the only rule every named column obeys.
1464pub(crate) fn reject_taken_name<'a>(
1465    name: &str,
1466    role: ColumnRole,
1467    taken: impl IntoIterator<Item = (&'a str, ColumnRole)>,
1468) -> MltResult<()> {
1469    for (seen, taken_by) in taken {
1470        if seen == name {
1471            return Err(MltError::DuplicateColumnName {
1472                name: name.to_string(),
1473                role,
1474                taken_by,
1475            });
1476        }
1477    }
1478    Ok(())
1479}
1480
1481/// Reject a name repeated within one list of column names.
1482fn validate_unique_names(names: &[String], role: ColumnRole) -> MltResult<()> {
1483    // Linear scan, not a HashSet: column counts are small, so this skips a per-layer alloc.
1484    // Empty names are allowed; real MVT tiles contain them.
1485    for (i, name) in names.iter().enumerate() {
1486        reject_taken_name(name, role, names[..i].iter().map(|n| (n.as_str(), role)))?;
1487    }
1488    Ok(())
1489}
1490
1491/// One feature's values for one kind of column, so the checks over them are written once.
1492trait FeatureColumn: Sized {
1493    /// This kind of column's values, one per column of the layer.
1494    fn of(feature: &TileFeature) -> &[Self];
1495    fn column_kind(&self) -> PropKind;
1496    /// A feature holding a different number of values than the layer has columns.
1497    fn count_mismatch(expected: usize, actual: usize) -> MltError;
1498    /// A value of a different kind than its column.
1499    fn kind_mismatch(index: usize, expected: PropKind, actual: PropKind) -> MltError;
1500}
1501
1502impl FeatureColumn for PropValue {
1503    fn of(feature: &TileFeature) -> &[Self] {
1504        &feature.properties
1505    }
1506
1507    fn column_kind(&self) -> PropKind {
1508        PropKind::from(self)
1509    }
1510
1511    fn count_mismatch(expected: usize, actual: usize) -> MltError {
1512        MltError::PropertyLengthMismatch { expected, actual }
1513    }
1514
1515    fn kind_mismatch(index: usize, expected: PropKind, actual: PropKind) -> MltError {
1516        MltError::PropertyKindMismatch {
1517            index,
1518            expected,
1519            actual,
1520        }
1521    }
1522}
1523
1524#[cfg(feature = "unstable-v2")]
1525impl FeatureColumn for MValue {
1526    fn of(feature: &TileFeature) -> &[Self] {
1527        &feature.m_values
1528    }
1529
1530    fn column_kind(&self) -> PropKind {
1531        self.kind()
1532    }
1533
1534    fn count_mismatch(expected: usize, actual: usize) -> MltError {
1535        MltError::MValueColumnCountMismatch { expected, actual }
1536    }
1537
1538    fn kind_mismatch(index: usize, expected: PropKind, actual: PropKind) -> MltError {
1539        MltError::MValueKindMismatch {
1540            index,
1541            expected,
1542            actual,
1543        }
1544    }
1545}
1546
1547/// The kind of each column, taken from the features that carry values for it.
1548///
1549/// A column no feature carries a value for is [`PropKind::Str`].
1550fn infer_kinds<T: FeatureColumn>(
1551    names: &[String],
1552    features: &[TileFeature],
1553) -> MltResult<Vec<PropKind>> {
1554    let mut kinds = vec![None; names.len()];
1555    for feature in features {
1556        let values = T::of(feature);
1557        if values.len() != names.len() {
1558            return Err(T::count_mismatch(names.len(), values.len()));
1559        }
1560        for (index, value) in values.iter().enumerate() {
1561            let actual = value.column_kind();
1562            match kinds[index] {
1563                Some(expected) if expected != actual => {
1564                    return Err(T::kind_mismatch(index, expected, actual));
1565                }
1566                None => kinds[index] = Some(actual),
1567                _ => {}
1568            }
1569        }
1570    }
1571    Ok(kinds
1572        .into_iter()
1573        .map(|kind| kind.unwrap_or(PropKind::Str))
1574        .collect())
1575}
1576
1577/// Check a feature's values for one kind of column: one per column, of the column's kind.
1578fn validate_kinds<T: FeatureColumn>(kinds: &[PropKind], feature: &TileFeature) -> MltResult<()> {
1579    let values = T::of(feature);
1580    if values.len() != kinds.len() {
1581        return Err(T::count_mismatch(kinds.len(), values.len()));
1582    }
1583    for (index, (value, expected)) in values.iter().zip(kinds).enumerate() {
1584        let actual = value.column_kind();
1585        if actual != *expected {
1586            return Err(T::kind_mismatch(index, *expected, actual));
1587        }
1588    }
1589    Ok(())
1590}
1591
1592#[cfg(test)]
1593mod tests {
1594    use geo_types::{Geometry, Point};
1595    use rstest::rstest;
1596
1597    use super::*;
1598
1599    #[rstest]
1600    #[case::same_kind(PropKind::I32, PropKind::I32, PropKind::I32)]
1601    #[case::signed_widths(PropKind::I8, PropKind::I32, PropKind::I64)]
1602    #[case::unsigned_widths(PropKind::U8, PropKind::U32, PropKind::U64)]
1603    #[case::signed_with_unsigned(PropKind::I64, PropKind::U64, PropKind::I64)]
1604    #[case::small_signed_with_unsigned(PropKind::I8, PropKind::U8, PropKind::I64)]
1605    #[case::floats(PropKind::F32, PropKind::F64, PropKind::F64)]
1606    #[case::int_with_float(PropKind::U8, PropKind::F32, PropKind::F64)]
1607    #[case::bool_with_int(PropKind::Bool, PropKind::I64, PropKind::Str)]
1608    #[case::str_with_float(PropKind::Str, PropKind::F64, PropKind::Str)]
1609    fn merge_kinds(#[case] a: PropKind, #[case] b: PropKind, #[case] expected: PropKind) {
1610        assert_eq!(a.merge(b), expected);
1611        assert_eq!(b.merge(a), expected);
1612    }
1613
1614    #[rstest]
1615    #[case::same_kind(PropValueRef::I32(-3), PropKind::I32, PropValue::I32(Some(-3)))]
1616    #[case::widen_signed(PropValueRef::I8(-3), PropKind::I64, PropValue::I64(Some(-3)))]
1617    #[case::unsigned_into_signed(PropValueRef::U64(7), PropKind::I64, PropValue::I64(Some(7)))]
1618    #[case::u64_above_i64_max(PropValueRef::U64(u64::MAX), PropKind::I64, PropValue::I64(None))]
1619    #[case::negative_into_u64(PropValueRef::I64(-1), PropKind::U64, PropValue::U64(None))]
1620    #[case::narrowing_out_of_range(PropValueRef::I32(300), PropKind::U8, PropValue::U8(None))]
1621    #[case::f32_into_f64(PropValueRef::F32(0.5), PropKind::F64, PropValue::F64(Some(0.5)))]
1622    #[case::int_into_f64(PropValueRef::I64(-2), PropKind::F64, PropValue::F64(Some(-2.0)))]
1623    #[case::u64_into_f64(PropValueRef::U64(1 << 63), PropKind::F64, PropValue::F64(Some(9.223_372_036_854_776e18)))]
1624    #[case::bool_into_str(PropValueRef::Bool(true), PropKind::Str, PropValue::Str(Some("true".into())))]
1625    #[case::int_into_str(PropValueRef::I64(-12), PropKind::Str, PropValue::Str(Some("-12".into())))]
1626    #[case::f32_into_str(PropValueRef::F32(1.5), PropKind::Str, PropValue::Str(Some("1.5".into())))]
1627    #[case::str_into_str(PropValueRef::Str("a"), PropKind::Str, PropValue::Str(Some("a".into())))]
1628    #[case::str_into_int(PropValueRef::Str("1"), PropKind::I64, PropValue::I64(None))]
1629    #[case::f64_into_f32(PropValueRef::F64(1.0), PropKind::F32, PropValue::F32(None))]
1630    fn coerce_to_kind(
1631        #[case] value: PropValueRef<'_>,
1632        #[case] kind: PropKind,
1633        #[case] expected: PropValue,
1634    ) {
1635        assert_eq!(value.coerce(kind), expected);
1636    }
1637
1638    #[rstest]
1639    #[case::bool(PropValueRef::Bool(true), PropKind::Bool, PropValue::Bool(Some(true)))]
1640    #[case::u8(PropValueRef::U8(1), PropKind::U8, PropValue::U8(Some(1)))]
1641    #[case::str(PropValueRef::Str("x"), PropKind::Str, PropValue::Str(Some("x".into())))]
1642    fn ref_into_owned_keeps_kind(
1643        #[case] value: PropValueRef<'_>,
1644        #[case] kind: PropKind,
1645        #[case] expected: PropValue,
1646    ) {
1647        assert_eq!(value.kind(), kind);
1648        assert_eq!(PropValue::from(value), expected);
1649    }
1650
1651    fn point_feature(properties: Vec<PropValue>) -> TileFeature {
1652        TileFeature {
1653            id: None,
1654            geometry: Geometry::Point(Point::new(0, 0)),
1655            properties,
1656            #[cfg(feature = "unstable-v2")]
1657            m_values: Vec::new(),
1658            #[cfg(feature = "unstable-v2")]
1659            nested: Vec::new(),
1660            #[cfg(feature = "unstable-v2")]
1661            z: Vec::new(),
1662        }
1663    }
1664
1665    #[test]
1666    fn tile_layer_constructor_rejects_empty_name() {
1667        assert!(matches!(
1668            TileLayer::new("", 4096),
1669            Err(MltError::MissingLayerName)
1670        ));
1671    }
1672
1673    #[test]
1674    fn tile_layer_constructor_rejects_zero_extent() {
1675        assert!(matches!(
1676            TileLayer::new("layer", 0),
1677            Err(MltError::InvalidExtent(0))
1678        ));
1679    }
1680
1681    #[test]
1682    fn add_property_rejects_duplicate_names() {
1683        let mut layer = TileLayer::new("layer", 4096).unwrap();
1684        layer.add_property("name", PropKind::Str).unwrap();
1685        assert_eq!(
1686            layer
1687                .add_property("name", PropKind::Str)
1688                .unwrap_err()
1689                .to_string(),
1690            "duplicate column name name: the property column repeats the property column"
1691        );
1692    }
1693
1694    #[test]
1695    fn from_parts_allows_empty_property_name() {
1696        // Real MVT tiles contain empty keys.
1697        assert!(TileLayer::from_parts("layer", 4096, vec![String::new()], vec![]).is_ok());
1698    }
1699
1700    #[test]
1701    fn from_parts_rejects_duplicate_property_name() {
1702        assert_eq!(
1703            TileLayer::from_parts("layer", 4096, vec!["dup".into(), "dup".into()], vec![])
1704                .unwrap_err()
1705                .to_string(),
1706            "duplicate column name dup: the property column repeats the property column"
1707        );
1708    }
1709
1710    #[test]
1711    fn push_feature_validates_property_count() {
1712        let mut layer = TileLayer::new("layer", 4096).unwrap();
1713        layer.add_property("name", PropKind::Str).unwrap();
1714        assert!(matches!(
1715            layer.push_feature(point_feature(vec![])),
1716            Err(MltError::PropertyLengthMismatch {
1717                expected: 1,
1718                actual: 0
1719            })
1720        ));
1721    }
1722
1723    #[test]
1724    fn push_feature_validates_property_kind() {
1725        let mut layer = TileLayer::new("layer", 4096).unwrap();
1726        layer.add_property("flag", PropKind::Bool).unwrap();
1727        layer
1728            .push_feature(point_feature(vec![PropValue::Bool(Some(true))]))
1729            .unwrap();
1730        assert!(matches!(
1731            layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
1732            Err(MltError::PropertyKindMismatch {
1733                index: 0,
1734                expected: PropKind::Bool,
1735                actual: PropKind::I32,
1736            })
1737        ));
1738    }
1739
1740    #[test]
1741    fn declared_property_kind_is_enforced_for_first_feature() {
1742        let mut layer = TileLayer::new("layer", 4096).unwrap();
1743        layer.add_property("flag", PropKind::Bool).unwrap();
1744        assert!(matches!(
1745            layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
1746            Err(MltError::PropertyKindMismatch {
1747                index: 0,
1748                expected: PropKind::Bool,
1749                actual: PropKind::I32,
1750            })
1751        ));
1752    }
1753
1754    #[cfg(feature = "unstable-v2")]
1755    #[test]
1756    fn push_feature_validates_m_value_count() {
1757        let mut layer = TileLayer::new("layer", 4096).unwrap();
1758        layer.add_m_value("m", PropKind::I32).unwrap();
1759        assert!(matches!(
1760            layer.push_feature(point_feature(vec![])),
1761            Err(MltError::MValueColumnCountMismatch {
1762                expected: 1,
1763                actual: 0
1764            })
1765        ));
1766    }
1767
1768    #[test]
1769    fn with_id_is_new_with_that_id() {
1770        let geometry = Geometry::Point(Point::new(1, 2));
1771        let mut expected = TileFeature::new(geometry.clone());
1772        expected.id = Some(7);
1773        assert_eq!(TileFeature::with_id(geometry, 7), expected);
1774    }
1775
1776    #[cfg(feature = "unstable-v2")]
1777    #[test]
1778    fn push_feature_validates_one_value_per_vertex() {
1779        let mut layer = TileLayer::new("layer", 4096).unwrap();
1780        layer.add_m_value("m", PropKind::I32).unwrap();
1781        let mut feature = point_feature(vec![]);
1782        feature.m_values = vec![MValue::I32(Some(vec![1, 2]))];
1783        assert!(matches!(
1784            layer.push_feature(feature),
1785            Err(MltError::MValueVertexCountMismatch {
1786                index: 0,
1787                expected: 1,
1788                actual: 2,
1789            })
1790        ));
1791    }
1792
1793    #[cfg(feature = "unstable-v2")]
1794    #[test]
1795    fn a_polygon_does_not_count_its_closing_vertices() {
1796        let ring = |pts: &[(i32, i32)]| {
1797            let mut ls = LineString::new(pts.iter().map(|&(x, y)| Coord { x, y }).collect());
1798            ls.close();
1799            ls
1800        };
1801        let poly = geo_types::Polygon::new(
1802            ring(&[(0, 0), (0, 4), (4, 4), (4, 0)]),
1803            vec![ring(&[(1, 1), (1, 2), (2, 2)])],
1804        );
1805        let feature = TileFeature::new(Geometry::Polygon(poly));
1806        assert_eq!(feature.vertex_count(), 7);
1807    }
1808
1809    #[cfg(feature = "unstable-v2")]
1810    #[test]
1811    fn a_shape_counts_its_root_as_the_first_level() {
1812        let kind = NestedKind::list(NestedKind::map([("a", NestedKind::Leaf(PropKind::I32))]));
1813        assert_eq!(kind.depth(), 3);
1814    }
1815
1816    #[cfg(feature = "unstable-v2")]
1817    #[test]
1818    fn a_map_may_leave_out_a_field_but_not_add_one() {
1819        let kind = NestedKind::map([
1820            ("a", NestedKind::Leaf(PropKind::I32)),
1821            ("b", NestedKind::Leaf(PropKind::Str)),
1822        ]);
1823        assert!(kind.accepts(&NestedValue::map([(
1824            "a",
1825            NestedValue::Leaf(PropValue::I32(Some(1)))
1826        )])));
1827        assert!(!kind.accepts(&NestedValue::map([(
1828            "c",
1829            NestedValue::Leaf(PropValue::I32(Some(1)))
1830        )])));
1831        assert!(!kind.accepts(&NestedValue::map([(
1832            "a",
1833            NestedValue::Leaf(PropValue::Str(Some("x".into())))
1834        )])));
1835    }
1836
1837    #[cfg(feature = "unstable-v2")]
1838    fn list_kind() -> NestedKind {
1839        NestedKind::list(NestedKind::Leaf(PropKind::I32))
1840    }
1841
1842    #[cfg(feature = "unstable-v2")]
1843    #[test]
1844    fn add_m_value_rejects_a_name_a_property_has() {
1845        let mut layer = TileLayer::new("layer", 4096).unwrap();
1846        layer.add_property("n", PropKind::I32).unwrap();
1847        assert_eq!(
1848            layer
1849                .add_m_value("n", PropKind::I32)
1850                .unwrap_err()
1851                .to_string(),
1852            "duplicate column name n: the m-value column repeats the property column"
1853        );
1854    }
1855
1856    #[cfg(feature = "unstable-v2")]
1857    #[test]
1858    fn add_m_value_rejects_a_name_a_nested_column_has() {
1859        let mut layer = TileLayer::new("layer", 4096).unwrap();
1860        layer.add_nested("n", list_kind()).unwrap();
1861        assert_eq!(
1862            layer
1863                .add_m_value("n", PropKind::I32)
1864                .unwrap_err()
1865                .to_string(),
1866            "duplicate column name n: the m-value column repeats the nested column"
1867        );
1868    }
1869
1870    #[cfg(feature = "unstable-v2")]
1871    #[test]
1872    fn add_m_value_rejects_duplicate_names() {
1873        let mut layer = TileLayer::new("layer", 4096).unwrap();
1874        layer.add_m_value("n", PropKind::I32).unwrap();
1875        assert_eq!(
1876            layer
1877                .add_m_value("n", PropKind::I32)
1878                .unwrap_err()
1879                .to_string(),
1880            "duplicate column name n: the m-value column repeats the m-value column"
1881        );
1882    }
1883
1884    #[cfg(feature = "unstable-v2")]
1885    #[test]
1886    fn add_property_rejects_a_name_an_m_value_has() {
1887        let mut layer = TileLayer::new("layer", 4096).unwrap();
1888        layer.add_m_value("n", PropKind::I32).unwrap();
1889        assert_eq!(
1890            layer
1891                .add_property("n", PropKind::I32)
1892                .unwrap_err()
1893                .to_string(),
1894            "duplicate column name n: the property column repeats the m-value column"
1895        );
1896    }
1897
1898    #[cfg(feature = "unstable-v2")]
1899    #[test]
1900    fn add_property_rejects_a_name_a_nested_column_has() {
1901        let mut layer = TileLayer::new("layer", 4096).unwrap();
1902        layer.add_nested("n", list_kind()).unwrap();
1903        assert_eq!(
1904            layer
1905                .add_property("n", PropKind::I32)
1906                .unwrap_err()
1907                .to_string(),
1908            "duplicate column name n: the property column repeats the nested column"
1909        );
1910    }
1911
1912    #[cfg(feature = "unstable-v2")]
1913    #[test]
1914    fn add_nested_rejects_a_name_a_property_has() {
1915        let mut layer = TileLayer::new("layer", 4096).unwrap();
1916        layer.add_property("n", PropKind::I32).unwrap();
1917        assert_eq!(
1918            layer.add_nested("n", list_kind()).unwrap_err().to_string(),
1919            "duplicate column name n: the nested column repeats the property column"
1920        );
1921    }
1922
1923    #[cfg(feature = "unstable-v2")]
1924    #[test]
1925    fn add_nested_rejects_a_name_an_m_value_already_has() {
1926        let mut layer = TileLayer::new("layer", 4096).unwrap();
1927        layer.add_m_value("n", PropKind::I32).unwrap();
1928        assert_eq!(
1929            layer.add_nested("n", list_kind()).unwrap_err().to_string(),
1930            "duplicate column name n: the nested column repeats the m-value column"
1931        );
1932    }
1933
1934    #[cfg(feature = "unstable-v2")]
1935    #[test]
1936    fn add_nested_rejects_duplicate_names() {
1937        let mut layer = TileLayer::new("layer", 4096).unwrap();
1938        layer.add_nested("n", list_kind()).unwrap();
1939        assert_eq!(
1940            layer.add_nested("n", list_kind()).unwrap_err().to_string(),
1941            "duplicate column name n: the nested column repeats the nested column"
1942        );
1943    }
1944
1945    #[cfg(feature = "unstable-v2")]
1946    #[test]
1947    fn set_nested_rejects_a_value_of_another_shape() {
1948        let mut layer = TileLayer::new("layer", 4096).unwrap();
1949        let key = layer
1950            .add_nested("n", NestedKind::list(NestedKind::Leaf(PropKind::I32)))
1951            .unwrap();
1952        let mut feature = point_feature(vec![]);
1953        feature.nested = vec![NestedValue::List(None)];
1954        assert_eq!(
1955            feature
1956                .set_nested(key, NestedValue::Map(None))
1957                .unwrap_err()
1958                .to_string(),
1959            "nested column 0 was given a value of another shape"
1960        );
1961    }
1962
1963    #[test]
1964    fn builder_uses_declared_property_kind_for_defaults() {
1965        let mut builder = TileLayer::builder("layer", 4096).unwrap();
1966        let flag = builder.add_property("flag", PropKind::Bool).unwrap();
1967        let mut feature = builder.feature(Geometry::Point(Point::new(0, 0)));
1968        feature.property(flag, PropValue::Bool(Some(true))).unwrap();
1969        feature.finish().unwrap();
1970        let layer = builder.finish();
1971
1972        assert_eq!(
1973            layer.features()[0].properties()[0],
1974            PropValue::Bool(Some(true))
1975        );
1976    }
1977}