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::{Layer01, ParsedLayer01, ParsedProperty, PropValueRef};
9#[cfg(feature = "unstable-v2")]
10use crate::decoder::{MValueSpans, 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, LendingIterator, MltResult};
16
17impl ParsedLayer01<'_> {
18    /// Decode and convert into a row-oriented [`TileLayer`], charging every
19    /// heap allocation against `dec`.
20    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<TileLayer> {
21        // Extract owned/copied fields before borrowing self for the feature iterator.
22        let name = self.name().to_string();
23        let extent = self.extent().get();
24        let names: Vec<String> = self.iterate_prop_names().map(|n| n.to_string()).collect();
25        let col_nulls = typed_nulls(&self.properties);
26        #[cfg(feature = "unstable-v2")]
27        let m_names: Vec<String> = self.m_values.iter().map(|m| m.name().to_string()).collect();
28        #[cfg(feature = "unstable-v2")]
29        let nested_names: Vec<String> = self.nested.iter().map(|n| n.name().to_string()).collect();
30        #[cfg(feature = "unstable-v2")]
31        let nested_kinds: Vec<NestedKind> = self.nested.iter().map(ParsedNested::kind).collect();
32        // One walk per m-value column, stepped alongside the features. Cutting the
33        // columns up front instead would cost a span per feature per column, which is
34        // memory the tile declares rather than memory it carries.
35        #[cfg(feature = "unstable-v2")]
36        let mut m_spans = dec.alloc::<MValueSpans>(self.m_values.len())?;
37        #[cfg(feature = "unstable-v2")]
38        m_spans.extend(self.m_values.iter().map(|m| m.spans(&self.geometry)));
39        let mut features = dec.alloc::<TileFeature>(self.feature_count())?;
40        #[cfg(feature = "unstable-v2")]
41        let mut index = 0_usize;
42        let mut feat_iter = self.iter_features();
43        while let Some(feat) = feat_iter.next() {
44            let feat = feat?;
45            let mut values = dec.alloc::<PropValue>(names.len())?;
46            for (col_idx, value) in feat.iter_all_properties().enumerate() {
47                values.push(match value {
48                    Some(v) => prop_value_from_ref(v),
49                    None => col_nulls[col_idx].clone(),
50                });
51            }
52
53            charge_str_props(dec, &values)?;
54
55            features.push(TileFeature {
56                id: feat.id(),
57                geometry: feat.geometry().clone(),
58                properties: values,
59                #[cfg(feature = "unstable-v2")]
60                m_values: m_values_of(&self.m_values, &mut m_spans, dec)?,
61                #[cfg(feature = "unstable-v2")]
62                nested: nested_of(&self.nested, index, dec)?,
63            });
64            #[cfg(feature = "unstable-v2")]
65            {
66                index += 1;
67            }
68        }
69
70        let layer = TileLayer::from_parts(name, extent, names, features)?;
71        #[cfg(feature = "unstable-v2")]
72        let layer = layer
73            .with_m_value_names(m_names)?
74            .with_nested(nested_names, nested_kinds)?;
75        Ok(layer)
76    }
77}
78
79impl Layer01<'_> {
80    /// Decode and convert into a row-oriented [`TileLayer`]
81    pub fn into_tile(self, dec: &mut Decoder) -> MltResult<TileLayer> {
82        self.decode_all(dec)?.into_tile(dec)
83    }
84}
85
86/// The m-values of the next feature, one per column, charged against `dec`.
87///
88/// Every column's walk is stepped once per feature, which is what pairs a feature
89/// with its run.
90#[cfg(feature = "unstable-v2")]
91fn m_values_of(
92    columns: &[ParsedMValue<'_>],
93    spans: &mut [MValueSpans<'_>],
94    dec: &mut Decoder,
95) -> MltResult<Vec<MValue>> {
96    let mut values = dec.alloc::<MValue>(columns.len())?;
97    for (column, spans) in columns.iter().zip(spans) {
98        let span = spans.next().transpose()?.flatten();
99        if let Some(span) = &span {
100            dec.consume(u32::try_from(column.values().row_bytes(span)).or_overflow()?)?;
101        }
102        values.push(column.values().row(column.name(), span)?);
103    }
104    Ok(values)
105}
106
107/// The nested values of feature `index`, one per column, charged against `dec`.
108#[cfg(feature = "unstable-v2")]
109fn nested_of(
110    columns: &[ParsedNested<'_>],
111    index: usize,
112    dec: &mut Decoder,
113) -> MltResult<Vec<NestedValue>> {
114    let mut values = dec.alloc::<NestedValue>(columns.len())?;
115    for column in columns {
116        let value = column.row(index)?;
117        dec.consume(u32::try_from(value.heap_bytes()).or_overflow()?)?;
118        values.push(value);
119    }
120    Ok(values)
121}
122
123/// Convert a [`PropValueRef`] (as yielded by [`crate::FeatureRef::iter_all_properties`])
124/// into an owned [`PropValue`].
125fn prop_value_from_ref(value: PropValueRef<'_>) -> PropValue {
126    match value {
127        PropValueRef::Bool(v) => PropValue::Bool(Some(v)),
128        PropValueRef::I8(v) => PropValue::I8(Some(v)),
129        PropValueRef::U8(v) => PropValue::U8(Some(v)),
130        PropValueRef::I32(v) => PropValue::I32(Some(v)),
131        PropValueRef::U32(v) => PropValue::U32(Some(v)),
132        PropValueRef::I64(v) => PropValue::I64(Some(v)),
133        PropValueRef::U64(v) => PropValue::U64(Some(v)),
134        PropValueRef::F32(v) => PropValue::F32(Some(v)),
135        PropValueRef::F64(v) => PropValue::F64(Some(v)),
136        PropValueRef::Str(s) => PropValue::Str(Some(s.to_string())),
137    }
138}
139
140/// Build a flat list of typed null [`PropValue`]s, one per logical column position
141/// as yielded by [`crate::FeatureRef::iter_all_properties`].
142///
143/// Each scalar column contributes one entry with its specific null variant (e.g.
144/// `PropValue::Bool(None)`).  A `SharedDict` column expands to one `PropValue::Str(None)`
145/// entry per sub-item.
146fn typed_nulls(properties: &[ParsedProperty<'_>]) -> Vec<PropValue> {
147    use ParsedProperty as PP;
148    use PropValue as PV;
149    let mut nulls = Vec::new();
150    for prop in properties {
151        match prop {
152            PP::Bool(_) => nulls.push(PV::Bool(None)),
153            PP::I8(_) => nulls.push(PV::I8(None)),
154            PP::U8(_) => nulls.push(PV::U8(None)),
155            PP::I32(_) => nulls.push(PV::I32(None)),
156            PP::U32(_) => nulls.push(PV::U32(None)),
157            PP::I64(_) => nulls.push(PV::I64(None)),
158            PP::U64(_) => nulls.push(PV::U64(None)),
159            PP::F32(_) => nulls.push(PV::F32(None)),
160            PP::F64(_) => nulls.push(PV::F64(None)),
161            PP::Str(_) => nulls.push(PV::Str(None)),
162            PP::SharedDict(d) => {
163                for _ in &d.items {
164                    nulls.push(PV::Str(None));
165                }
166            }
167        }
168    }
169    nulls
170}
171
172/// Charge `dec` for the heap bytes of owned `String` values inside `PropValue::Str`.
173fn charge_str_props(dec: &mut Decoder, props: &[PropValue]) -> MltResult<()> {
174    let str_bytes = props
175        .iter()
176        .filter_map(|p| {
177            if let PropValue::Str(Some(s)) = p {
178                Some(s.len())
179            } else {
180                None
181            }
182        })
183        .try_fold(0u32, |acc, n| {
184            acc.checked_add(u32::try_from(n).or_overflow()?)
185                .or_overflow()
186        })?;
187    if str_bytes > 0 {
188        dec.consume(str_bytes)?;
189    }
190    Ok(())
191}