Skip to main content

mlt_core/decoder/
into_tile.rs

1//! Conversion from the decoded columnar form into the row-oriented
2//! [`crate::tile`] model.
3//!
4//! [`ParsedLayer01::into_tile`] materializes one [`TileFeature`] per map
5//! feature, owning its geometry and property values outright. That is the form
6//! the optimizer, the sorting pipeline, and the converters work in.
7
8use crate::decoder::{Layer, Layer01, ParsedLayer, ParsedLayer01, ParsedProperty, PropValueRef};
9#[cfg(feature = "unstable-v2")]
10use crate::decoder::{Layer02, MValueSpans, ParsedLayer02, ParsedMValue, ParsedNested};
11use crate::errors::AsMltError as _;
12#[cfg(feature = "unstable-v2")]
13use crate::tile::{MValue, NestedKind, NestedValue};
14use crate::tile::{PropValue, TileFeature, TileLayer};
15use crate::{Decoder, Lazy, LendingIterator, MltResult};
16
17/// The parts of a [`TileLayer`] every wire version produces the same way.
18struct TileParts {
19    name: String,
20    extent: u32,
21    names: Vec<String>,
22    features: Vec<TileFeature>,
23}
24
25impl TileParts {
26    fn finish(self) -> MltResult<TileLayer> {
27        TileLayer::from_parts(self.name, self.extent, self.names, self.features)
28    }
29}
30
31impl ParsedLayer01<'_> {
32    /// Decode and convert into a row-oriented [`TileLayer`], charging every
33    /// heap allocation against `dec`.
34    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<TileLayer> {
35        self.collect_parts(dec)?.finish()
36    }
37
38    /// One [`TileFeature`] per map feature, carrying only the columns every version
39    /// has. Borrows, so a version that adds its own can fill them in afterwards.
40    fn collect_parts(&self, dec: &mut Decoder) -> MltResult<TileParts> {
41        let name = self.name().to_string();
42        let extent = self.extent().get();
43        let names: Vec<String> = self.iterate_prop_names().map(|n| n.to_string()).collect();
44        let col_nulls = typed_nulls(&self.properties);
45        let mut features = dec.alloc::<TileFeature>(self.feature_count())?;
46        let mut feat_iter = self.iter_features();
47        while let Some(feat) = feat_iter.next() {
48            let feat = feat?;
49            let mut values = dec.alloc::<PropValue>(names.len())?;
50            for (col_idx, value) in feat.iter_all_properties().enumerate() {
51                values.push(match value {
52                    Some(v) => prop_value_from_ref(v),
53                    None => col_nulls[col_idx].clone(),
54                });
55            }
56
57            charge_str_props(dec, &values)?;
58
59            features.push(TileFeature {
60                id: feat.id(),
61                geometry: feat.geometry().clone(),
62                properties: values,
63                #[cfg(feature = "unstable-v2")]
64                m_values: Vec::new(),
65                #[cfg(feature = "unstable-v2")]
66                nested: Vec::new(),
67                #[cfg(feature = "unstable-v2")]
68                z: Vec::new(),
69            });
70        }
71        Ok(TileParts {
72            name,
73            extent,
74            names,
75            features,
76        })
77    }
78}
79
80impl Layer01<'_> {
81    /// Decode and convert into a row-oriented [`TileLayer`]
82    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<TileLayer> {
83        self.decode_all(dec)?.into_tile(dec)
84    }
85}
86
87#[cfg(feature = "unstable-v2")]
88impl ParsedLayer02<'_> {
89    /// Decode and convert into a row-oriented [`TileLayer`], adding the m-value
90    /// and nested columns only v2 carries.
91    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<TileLayer> {
92        let m_names: Vec<String> = self.m_values.iter().map(|m| m.name().to_string()).collect();
93        let nested_names: Vec<String> = self.nested.iter().map(|n| n.name().to_string()).collect();
94        let nested_kinds: Vec<NestedKind> = self.nested.iter().map(ParsedNested::kind).collect();
95        // One walk per m-value column, stepped alongside the features. Cutting the
96        // columns up front instead would cost a span per feature per column, which is
97        // memory the tile declares rather than memory it carries.
98        let mut m_spans = dec.alloc::<MValueSpans>(self.m_values.len())?;
99        m_spans.extend(self.m_values.iter().map(|m| m.spans(&self.layer.geometry)));
100
101        let geometry = &self.layer.geometry;
102        let mut parts = self.layer.collect_parts(dec)?;
103        for (index, feature) in parts.features.iter_mut().enumerate() {
104            feature.m_values = m_values_of(&self.m_values, &mut m_spans, dec)?;
105            feature.nested = nested_of(&self.nested, index, dec)?;
106            let run = geometry.z_run(index)?;
107            let mut z = dec.alloc::<i32>(run.len())?;
108            for value in run {
109                z.push(value?);
110            }
111            feature.z = z;
112        }
113        parts
114            .finish()?
115            .with_m_value_names(m_names)?
116            .with_nested(nested_names, nested_kinds)?
117            .with_z_step(geometry.z_step())
118    }
119}
120
121#[cfg(feature = "unstable-v2")]
122impl Layer02<'_> {
123    /// Decode and convert into a row-oriented [`TileLayer`]
124    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<TileLayer> {
125        self.decode_all(dec)?.into_tile(dec)
126    }
127}
128
129impl ParsedLayer<'_> {
130    /// Convert into a row-oriented [`TileLayer`] whatever the tag, or `None` for a
131    /// tag this build does not know. Keeps every version's columns.
132    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<Option<TileLayer>> {
133        match self {
134            Self::Tag01(l) => l.into_tile(dec).map(Some),
135            #[cfg(feature = "unstable-v2")]
136            Self::Tag02(l) => l.into_tile(dec).map(Some),
137            Self::Unknown(_) => Ok(None),
138        }
139    }
140}
141
142impl Layer<'_, Lazy> {
143    /// Decode every column, then convert into a row-oriented [`TileLayer`], or
144    /// `None` for a tag this build does not know.
145    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<Option<TileLayer>> {
146        self.decode_all(dec)?.into_tile(dec)
147    }
148}
149
150/// The m-values of the next feature, one per column, charged against `dec`.
151///
152/// Every column's walk is stepped once per feature, which is what pairs a feature
153/// with its run.
154#[cfg(feature = "unstable-v2")]
155fn m_values_of(
156    columns: &[ParsedMValue<'_>],
157    spans: &mut [MValueSpans<'_>],
158    dec: &mut Decoder,
159) -> MltResult<Vec<MValue>> {
160    let mut values = dec.alloc::<MValue>(columns.len())?;
161    for (column, spans) in columns.iter().zip(spans) {
162        let span = spans.next().transpose()?.flatten();
163        if let Some(span) = &span {
164            dec.consume(u32::try_from(column.values().row_bytes(span)).or_overflow()?)?;
165        }
166        values.push(column.values().row(column.name(), span)?);
167    }
168    Ok(values)
169}
170
171/// The nested values of feature `index`, one per column, charged against `dec`.
172#[cfg(feature = "unstable-v2")]
173fn nested_of(
174    columns: &[ParsedNested<'_>],
175    index: usize,
176    dec: &mut Decoder,
177) -> MltResult<Vec<NestedValue>> {
178    let mut values = dec.alloc::<NestedValue>(columns.len())?;
179    for column in columns {
180        let value = column.row(index)?;
181        dec.consume(u32::try_from(value.heap_bytes()).or_overflow()?)?;
182        values.push(value);
183    }
184    Ok(values)
185}
186
187/// Convert a [`PropValueRef`] (as yielded by [`crate::FeatureRef::iter_all_properties`])
188/// into an owned [`PropValue`].
189fn prop_value_from_ref(value: PropValueRef<'_>) -> PropValue {
190    match value {
191        PropValueRef::Bool(v) => PropValue::Bool(Some(v)),
192        PropValueRef::I8(v) => PropValue::I8(Some(v)),
193        PropValueRef::U8(v) => PropValue::U8(Some(v)),
194        PropValueRef::I32(v) => PropValue::I32(Some(v)),
195        PropValueRef::U32(v) => PropValue::U32(Some(v)),
196        PropValueRef::I64(v) => PropValue::I64(Some(v)),
197        PropValueRef::U64(v) => PropValue::U64(Some(v)),
198        PropValueRef::F32(v) => PropValue::F32(Some(v)),
199        PropValueRef::F64(v) => PropValue::F64(Some(v)),
200        PropValueRef::Str(s) => PropValue::Str(Some(s.to_string())),
201    }
202}
203
204/// Build a flat list of typed null [`PropValue`]s, one per logical column position
205/// as yielded by [`crate::FeatureRef::iter_all_properties`].
206///
207/// Each scalar column contributes one entry with its specific null variant (e.g.
208/// `PropValue::Bool(None)`).  A `SharedDict` column expands to one `PropValue::Str(None)`
209/// entry per sub-item.
210fn typed_nulls(properties: &[ParsedProperty<'_>]) -> Vec<PropValue> {
211    use ParsedProperty as PP;
212    use PropValue as PV;
213    let mut nulls = Vec::new();
214    for prop in properties {
215        match prop {
216            PP::Bool(_) => nulls.push(PV::Bool(None)),
217            PP::I8(_) => nulls.push(PV::I8(None)),
218            PP::U8(_) => nulls.push(PV::U8(None)),
219            PP::I32(_) => nulls.push(PV::I32(None)),
220            PP::U32(_) => nulls.push(PV::U32(None)),
221            PP::I64(_) => nulls.push(PV::I64(None)),
222            PP::U64(_) => nulls.push(PV::U64(None)),
223            PP::F32(_) => nulls.push(PV::F32(None)),
224            PP::F64(_) => nulls.push(PV::F64(None)),
225            PP::Str(_) => nulls.push(PV::Str(None)),
226            PP::SharedDict(d) => {
227                for _ in &d.items {
228                    nulls.push(PV::Str(None));
229                }
230            }
231        }
232    }
233    nulls
234}
235
236/// Charge `dec` for the heap bytes of owned `String` values inside `PropValue::Str`.
237fn charge_str_props(dec: &mut Decoder, props: &[PropValue]) -> MltResult<()> {
238    let str_bytes = props
239        .iter()
240        .filter_map(|p| {
241            if let PropValue::Str(Some(s)) = p {
242                Some(s.len())
243            } else {
244                None
245            }
246        })
247        .try_fold(0u32, |acc, n| {
248            acc.checked_add(u32::try_from(n).or_overflow()?)
249                .or_overflow()
250        })?;
251    if str_bytes > 0 {
252        dec.consume(str_bytes)?;
253    }
254    Ok(())
255}