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