Skip to main content

mlt_core/encoder/geometry/
encode01.rs

1//! Geometry section writer for tag `0x01` (v1) layers.
2
3use super::model::VertexBufferType;
4use super::streams::{
5    dict_may_be_beneficial, encode_hilbert_vertex_streams, encode_level1_length_stream,
6    encode_level1_without_ring_buffer_length_stream, encode_level2_length_stream,
7    encode_morton_vertex_streams, encode_ring_lengths_for_mixed, encode_root_length_stream,
8    encode_vec2_vertex_stream, normalize_geometry_offsets, normalize_part_offsets_for_rings,
9    seed_curve_caches, write_geo_u32_stream,
10};
11use crate::decoder::GeometryType::{LineString, Point, Polygon};
12use crate::decoder::{
13    ColumnType, GeometryType, GeometryValues, LengthType, Levels, LogicalEncoding, OffsetType,
14    StreamType, VertexLogical, decode_topology,
15};
16use crate::encoder::model::StreamCtx;
17use crate::encoder::{Codecs, Encoder};
18use crate::{Decoder, MltError, MltResult};
19
20impl GeometryValues {
21    /// Write the geometry column to `enc`.
22    #[hotpath::measure]
23    pub fn write_to(self, enc: &mut Encoder, codecs: &mut Codecs) -> MltResult<()> {
24        let Self {
25            vector_types,
26            geometry_offsets,
27            part_offsets,
28            ring_offsets,
29            index_buffer,
30            triangle_offsets,
31            vertices,
32            // The layer writer rejects a geometry with z coordinates before it gets here.
33            #[cfg(feature = "unstable-v2")]
34                z_step: _,
35        } = self;
36
37        // Flatten every Option<Vec> -> Vec  (empty == not present).
38        // vertices: None means no coordinate data (e.g. empty layer).
39        let geom_offsets = geometry_offsets.unwrap_or_default();
40        let part_offsets = part_offsets.unwrap_or_default();
41        let ring_offsets = ring_offsets.unwrap_or_default();
42        let index_buffer = index_buffer.unwrap_or_default();
43        let triangle_offsets = triangle_offsets.unwrap_or_default();
44        let vertices = vertices.unwrap_or_default();
45
46        // An empty index buffer means no polygon tessellated into a triangle, so both
47        // streams are dropped, as the v2 writer does. The triangle counts alone say
48        // nothing a reader could use.
49        let (triangle_offsets, index_buffer) = if index_buffer.is_empty() {
50            (Vec::new(), Vec::new())
51        } else {
52            (triangle_offsets, index_buffer)
53        };
54
55        seed_curve_caches(enc, &vertices);
56
57        let meta: Vec<u32> = vector_types.iter().map(|t| *t as u32).collect();
58
59        let part_offsets = if geom_offsets.is_empty()
60            && !ring_offsets.is_empty()
61            && !part_offsets.is_empty()
62            && part_offsets.len() != vector_types.len() + 1
63        {
64            // Normalize part_offsets when there are no geometry offsets but ring offsets exist.
65            normalize_part_offsets_for_rings(&vector_types, &part_offsets, &ring_offsets)
66        } else {
67            part_offsets
68        };
69
70        // Write column type to meta; reserve exactly 1 byte for stream count
71        // (geometry never exceeds ~8 streams, always fits in a single varint byte).
72        enc.write_column_type(ColumnType::Geometry)?;
73        let stream_count_pos = enc.data().len();
74        enc.data_mut().push(0); // placeholder - patched below
75        let mut n: u8 = 0;
76
77        // Meta stream - always written, even for a zero-feature layer.
78        let ctx = StreamCtx::geom(StreamType::Length(LengthType::VarBinary), "meta");
79        codecs.write_int_stream(&meta, &ctx, enc)?;
80        n += 1;
81
82        // Topology: compute each length stream and write it immediately.
83        let mut seen = Levels::default();
84        if !geom_offsets.is_empty() {
85            let geom_offsets = if geom_offsets.len() == vector_types.len() + 1 {
86                geom_offsets
87            } else {
88                normalize_geometry_offsets(&vector_types, &geom_offsets)
89            };
90            let data = encode_root_length_stream(&vector_types, &geom_offsets, Polygon);
91            let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries");
92            n += write_length(data, ctx, enc, codecs, &mut seen.geometries)?;
93
94            // part_offsets is intentionally kept sparse here (polygon-only cumulative
95            // ring counts). encode_level1/2_length_stream navigate it with a running
96            // part_idx counter that advances only for Polygon/LineString types, which
97            // matches the sparse layout. Densifying via normalize_part_offsets_for_rings
98            // would insert Point slots and corrupt the counter arithmetic.
99            if !part_offsets.is_empty() {
100                if ring_offsets.is_empty() {
101                    // geom -> parts only (no rings).
102                    let data = encode_level1_without_ring_buffer_length_stream(
103                        &vector_types,
104                        &geom_offsets,
105                        &part_offsets,
106                    );
107                    let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings");
108                    n += write_length(data, ctx, enc, codecs, &mut seen.parts)?;
109                } else {
110                    // Full topology: geom -> parts -> rings.
111                    // LineStrings contribute to rings here, not to parts.
112                    let data = encode_level1_length_stream(
113                        &vector_types,
114                        &geom_offsets,
115                        &part_offsets,
116                        false,
117                    );
118                    let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "rings");
119                    n += write_length(data, ctx, enc, codecs, &mut seen.parts)?;
120
121                    let data = encode_level2_length_stream(
122                        &vector_types,
123                        &geom_offsets,
124                        &part_offsets,
125                        &ring_offsets,
126                    );
127                    let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "rings2");
128                    n += write_length(data, ctx, enc, codecs, &mut seen.rings)?;
129                }
130            }
131        } else if !part_offsets.is_empty() {
132            if ring_offsets.is_empty() {
133                let data = encode_root_length_stream(&vector_types, &part_offsets, Point);
134                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings");
135                n += write_length(data, ctx, enc, codecs, &mut seen.parts)?;
136            } else {
137                // No Multi* types; parts -> rings (Polygon / mixed Point+Polygon).
138                // Java writes an empty GEOMETRIES stream here for tessellated polygons; only do
139                // so when explicitly forced (e.g. to preserve byte-for-byte Java compatibility).
140                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries");
141                n += write_length(Vec::new(), ctx, enc, codecs, &mut seen.geometries)?;
142
143                let data = encode_root_length_stream(&vector_types, &part_offsets, LineString);
144                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "parts");
145                n += write_length(data, ctx, enc, codecs, &mut seen.parts)?;
146
147                // part_offs is a dense N+1 array (one slot per geometry incl. Points);
148                // ring_offs stores vertex offsets per slot.  The dense-aware helper skips
149                // Point slots by index rather than a running counter.
150                let has_line_string = vector_types
151                    .iter()
152                    .copied()
153                    .any(GeometryType::is_linestring);
154                let data = encode_ring_lengths_for_mixed(
155                    &vector_types,
156                    &part_offsets,
157                    &ring_offsets,
158                    has_line_string,
159                );
160                let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "parts_ring");
161                n += write_length(data, ctx, enc, codecs, &mut seen.rings)?;
162            }
163        }
164
165        let (triangles, index_buffer) =
166            feature_relative_triangles(vector_types, seen, triangle_offsets, index_buffer)?;
167        let ctx = StreamCtx::geom(StreamType::Length(LengthType::Triangles), "triangles");
168        n += write_geo_u32_stream(&triangles, ctx, enc, codecs)?;
169        let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Index), "triangles_indexes");
170        n += write_geo_u32_stream(&index_buffer, ctx, enc, codecs)?;
171
172        if let Some(forced) = enc.override_vertex_buffer_type() {
173            n += match forced {
174                VertexBufferType::Vec2 => encode_vec2_vertex_stream(&vertices, enc, codecs)?,
175                VertexBufferType::Morton => encode_morton_vertex_streams(&vertices, enc, codecs)?,
176                VertexBufferType::Hilbert => encode_hilbert_vertex_streams(&vertices, enc, codecs)?,
177                VertexBufferType::Rans => {
178                    return Err(MltError::UnsupportedLogicalEncoding(
179                        LogicalEncoding::Vertex(VertexLogical::Rans),
180                        "v1, which has no rANS vertex coding",
181                    ));
182                }
183            };
184        } else if dict_may_be_beneficial(&vertices, enc) {
185            // Morton fits (the gate above ensures it), so race all three.
186            let mut winner_size: usize = usize::MAX;
187            let mut winner_stream_cnt: u8 = 0;
188            let mut alt = enc.try_alternatives();
189            alt.with(|e| {
190                let ds = e.data().len();
191                let ms = e.meta().len();
192                winner_stream_cnt = encode_vec2_vertex_stream(&vertices, e, codecs)?;
193                winner_size = (e.data().len() - ds) + (e.meta().len() - ms);
194                Ok(())
195            })?;
196            alt.with(|e| {
197                let ds = e.data().len();
198                let ms = e.meta().len();
199                let cnt = encode_hilbert_vertex_streams(&vertices, e, codecs)?;
200                let size = (e.data().len() - ds) + (e.meta().len() - ms);
201                if size < winner_size {
202                    winner_stream_cnt = cnt;
203                    winner_size = size;
204                }
205                Ok(())
206            })?;
207            alt.with(|e| {
208                let ds = e.data().len();
209                let ms = e.meta().len();
210                let cnt = encode_morton_vertex_streams(&vertices, e, codecs)?;
211                let size = (e.data().len() - ds) + (e.meta().len() - ms);
212                if size < winner_size {
213                    winner_stream_cnt = cnt;
214                }
215                Ok(())
216            })?;
217            drop(alt);
218            n += winner_stream_cnt;
219        } else {
220            n += encode_vec2_vertex_stream(&vertices, enc, codecs)?;
221        }
222
223        // Patch the reserved stream-count byte.
224        debug_assert!(n <= 127, "geometry stream count must fit in one byte");
225        enc.data_mut()[stream_count_pos] = n;
226        Ok(())
227    }
228}
229
230/// Write a length stream, recording it in `seen` if it reached the tile.
231fn write_length(
232    data: Vec<u32>,
233    ctx: StreamCtx,
234    enc: &mut Encoder,
235    codecs: &mut Codecs,
236    seen: &mut Option<Vec<u32>>,
237) -> MltResult<u8> {
238    let n = write_geo_u32_stream(&data, ctx, enc, codecs)?;
239    if n != 0 {
240        *seen = Some(data);
241    }
242    Ok(n)
243}
244
245/// The triangle counts and per-feature indices v1 stores for the layer-wide ones.
246fn feature_relative_triangles(
247    vector_types: Vec<GeometryType>,
248    seen: Levels,
249    triangle_offsets: Vec<u32>,
250    index_buffer: Vec<u32>,
251) -> MltResult<(Vec<u32>, Vec<u32>)> {
252    if index_buffer.is_empty() {
253        return Ok((Vec::new(), Vec::new()));
254    }
255    let triangles = triangle_offsets.windows(2).map(|w| w[1] - w[0]).collect();
256    let mut dec = Decoder::default();
257    let offsets = decode_topology(&vector_types, seen, &mut dec)?;
258    let mut dense = GeometryValues {
259        vector_types,
260        geometry_offsets: offsets.geometries,
261        part_offsets: offsets.parts,
262        ring_offsets: offsets.rings,
263        index_buffer: Some(index_buffer),
264        triangle_offsets: Some(triangle_offsets),
265        vertices: None,
266        #[cfg(feature = "unstable-v2")]
267        z_step: None,
268    };
269    dense.rebase_indices_to_feature()?;
270    Ok((triangles, dense.index_buffer.unwrap_or_default()))
271}