Skip to main content

mlt_core/frames/v01/geometry/
encode.rs

1use std::collections::BTreeSet;
2
3use crate::codecs::morton::{encode_morton, morton_deltas, z_order_params};
4use crate::codecs::zigzag::encode_componentwise_delta_vec2s;
5use crate::errors::AsMltError as _;
6use crate::utils::AsUsize as _;
7use crate::v01::{
8    DictionaryType, EncodedGeometry, EncodedStream, GeometryType, GeometryValues, IntEncoder,
9    IntEncoding, LengthType, LogicalEncoding, MortonMeta, OffsetType, PhysicalEncoder, StreamMeta,
10    StreamType, VertexBufferType,
11};
12use crate::{MltError, MltResult};
13
14/// Encode vertex buffer using componentwise delta encoding
15fn encode_vertex_buffer(vertices: &[i32], physical: PhysicalEncoder) -> MltResult<EncodedStream> {
16    // Componentwise delta encoding: delta X and Y separately
17    let physical_u32 = encode_componentwise_delta_vec2s(vertices);
18    let num_values = u32::try_from(physical_u32.len())?;
19    let (data, physical_encoding) = physical.encode_u32s(physical_u32)?;
20    Ok(EncodedStream {
21        meta: StreamMeta::new(
22            StreamType::Data(DictionaryType::Vertex),
23            IntEncoding::new(LogicalEncoding::ComponentwiseDelta, physical_encoding),
24            num_values,
25        ),
26        data,
27    })
28}
29
30/// Encode a Morton vertex dictionary stream.
31///
32/// `codes` must be the sorted unique Morton codes for the dictionary.
33/// They are delta-encoded before physical encoding.
34fn encode_morton_vertex_buffer(
35    codes: &[u32],
36    meta: MortonMeta,
37    physical: PhysicalEncoder,
38) -> MltResult<EncodedStream> {
39    let deltas = morton_deltas(codes);
40    let num_values = u32::try_from(deltas.len())?;
41    let (data, physical_encoding) = physical.encode_u32s(deltas)?;
42    Ok(EncodedStream {
43        meta: StreamMeta::new(
44            StreamType::Data(DictionaryType::Morton),
45            IntEncoding::new(LogicalEncoding::MortonDelta(meta), physical_encoding),
46            num_values,
47        ),
48        data,
49    })
50}
51
52/// Compute `ZOrderCurve` parameters from the vertex value range.
53///
54/// Returns `(num_bits, coordinate_shift)` matching Java's `SpaceFillingCurve`.
55/// Build a sorted unique Morton dictionary and per-vertex offset indices from a flat
56/// `[x0, y0, x1, y1, …]` vertex slice.
57///
58/// Returns `(sorted_unique_codes, per_vertex_offsets)`.
59fn build_morton_dict(vertices: &[i32], meta: MortonMeta) -> Result<(Vec<u32>, Vec<u32>), MltError> {
60    let codes: Vec<u32> = vertices
61        .chunks_exact(2)
62        .map(|c| encode_morton(c[0], c[1], meta))
63        .collect::<Result<_, _>>()?;
64
65    let dict: Vec<u32> = codes
66        .iter()
67        .copied()
68        .collect::<BTreeSet<_>>()
69        .into_iter()
70        .collect();
71
72    let offsets: Vec<u32> = codes
73        .iter()
74        .map(|&code| u32::try_from(dict.partition_point(|&c| c < code)).or_overflow())
75        .collect::<Result<_, _>>()?;
76
77    Ok((dict, offsets))
78}
79
80/// Convert geometry offsets to length stream for encoding.
81/// This is the inverse of `decode_root_length_stream`.
82///
83/// The offset array can be either:
84/// - Sparse: entries only for geometries that need them (types > `buffer_id`), N+1 entries for N matching geoms
85/// - Dense (normalized): N+1 entries for N geometry types, indexed by geometry position
86///
87/// If dense `(len == geometry_types.len() + 1)`, use geometry index directly.
88/// If sparse, use sequential indexing for matching geometry types.
89fn encode_root_length_stream(
90    geometry_types: &[GeometryType],
91    geometry_offsets: &[u32],
92    buffer_id: GeometryType,
93) -> Vec<u32> {
94    let mut lengths = Vec::new();
95
96    if geometry_offsets.len() == geometry_types.len() + 1 {
97        // Dense array: use geometry index directly
98        for (i, &geom_type) in geometry_types.iter().enumerate() {
99            if geom_type > buffer_id {
100                let start = geometry_offsets[i];
101                let end = geometry_offsets[i + 1];
102                lengths.push(end - start);
103            }
104        }
105    } else {
106        // Sparse array: sequential indexing for matching types
107        let mut offset_idx = 0;
108        for &geom_type in geometry_types {
109            if geom_type > buffer_id {
110                let start = geometry_offsets[offset_idx];
111                let end = geometry_offsets[offset_idx + 1];
112                lengths.push(end - start);
113                offset_idx += 1;
114            }
115        }
116    }
117
118    lengths
119}
120
121/// Convert part offsets to length stream for level 1 encoding.
122/// This is the inverse of `decode_level1_length_stream`.
123///
124/// The `geometry_offsets` array is expected to be an N+1 element array for N geometries.
125/// For mixed types, it should be normalized first.
126fn encode_level1_length_stream(
127    geometry_types: &[GeometryType],
128    geometry_offsets: &[u32],
129    part_offsets: &[u32],
130    is_line_string_present: bool,
131) -> Vec<u32> {
132    let mut lengths = Vec::new();
133    let mut part_idx = 0;
134
135    for (i, &geom_type) in geometry_types.iter().enumerate() {
136        let num_geoms = geometry_offsets[i + 1] - geometry_offsets[i];
137
138        let needs_length =
139            geom_type.is_polygon() || (is_line_string_present && geom_type.is_linestring());
140
141        if needs_length {
142            for _ in 0..num_geoms {
143                let start = part_offsets[part_idx];
144                let end = part_offsets[part_idx + 1];
145                lengths.push(end - start);
146                part_idx += 1;
147            }
148        }
149        // Note: Point/MultiPoint don't have entries in the sparse part_offsets used
150        // at this call site, so part_idx must not advance for non-length types here.
151    }
152
153    lengths
154}
155
156/// Compute ring vertex-count lengths for the no-geometry-offsets + has-ring-offsets case.
157///
158/// In this branch `part_offsets` is a **dense** N+1 array (one slot per geometry,
159/// including Points) and `ring_offsets` holds the vertex offsets for every slot.
160/// Using the geometry index directly as the ring-slot index avoids the
161/// running-counter misalignment that `encode_level1_length_stream` would produce
162/// when non-length types (Points) occupy slots that a sparse counter skips.
163fn encode_ring_lengths_for_mixed(
164    geometry_types: &[GeometryType],
165    part_offsets: &[u32],
166    ring_offsets: &[u32],
167    is_line_string_present: bool,
168) -> Vec<u32> {
169    let mut lengths = Vec::new();
170    for (i, &geom_type) in geometry_types.iter().enumerate() {
171        let needs_length =
172            geom_type.is_polygon() || (is_line_string_present && geom_type.is_linestring());
173        if needs_length {
174            let slot_start = part_offsets[i].as_usize();
175            let slot_end = part_offsets[i + 1].as_usize();
176            for slot in slot_start..slot_end {
177                lengths.push(ring_offsets[slot + 1] - ring_offsets[slot]);
178            }
179        }
180    }
181    lengths
182}
183
184/// Convert ring offsets to length stream for level 2 encoding.
185/// This is the inverse of `decode_level2_length_stream`.
186///
187/// The `geometry_offsets` array is expected to be an N+1 element array for N geometries.
188/// The `part_offsets` array tracks ring counts cumulatively.
189fn encode_level2_length_stream(
190    geometry_types: &[GeometryType],
191    geometry_offsets: &[u32],
192    part_offsets: &[u32],
193    ring_offsets: &[u32],
194) -> Vec<u32> {
195    let mut lengths = Vec::new();
196    let mut part_idx = 0;
197    let mut ring_idx = 0;
198
199    for (i, &geom_type) in geometry_types.iter().enumerate() {
200        let num_geoms = geometry_offsets[i + 1] - geometry_offsets[i];
201
202        // Only Polygon and MultiPolygon have ring data in level 2
203        // LineStrings with Polygon present add their vertex counts directly to ring_offsets,
204        // but they don't have parts (ring count per linestring is always 1 implicitly)
205        if geom_type.is_polygon() {
206            // Polygon/MultiPolygon: iterate through sub-polygons, each has parts (ring counts)
207            for _ in 0..num_geoms {
208                let num_parts = part_offsets[part_idx + 1] - part_offsets[part_idx];
209                part_idx += 1;
210                for _ in 0..num_parts {
211                    let start = ring_offsets[ring_idx];
212                    let end = ring_offsets[ring_idx + 1];
213                    lengths.push(end - start);
214                    ring_idx += 1;
215                }
216            }
217        } else if geom_type.is_linestring() {
218            // LineStrings contribute to ring_offsets directly (vertex counts)
219            // Each linestring is implicitly 1 "ring" in terms of vertex counts
220            for _ in 0..num_geoms {
221                let start = ring_offsets[ring_idx];
222                let end = ring_offsets[ring_idx + 1];
223                lengths.push(end - start);
224                ring_idx += 1;
225            }
226        }
227        // Note: Point/MultiPoint don't contribute to ring_offsets
228    }
229
230    lengths
231}
232
233/// Convert part offsets without ring buffer to length stream.
234/// This is the inverse of `decode_level1_without_ring_buffer_length_stream`.
235///
236/// The `geometry_offsets` array is expected to be an N+1 element array for N geometries.
237fn encode_level1_without_ring_buffer_length_stream(
238    geometry_types: &[GeometryType],
239    geometry_offsets: &[u32],
240    part_offsets: &[u32],
241) -> Vec<u32> {
242    let mut lengths = Vec::new();
243    let mut part_idx = 0;
244
245    for (i, &geom_type) in geometry_types.iter().enumerate() {
246        let num_geoms = (geometry_offsets[i + 1] - geometry_offsets[i]).as_usize();
247
248        if geom_type.is_linestring() || geom_type.is_polygon() {
249            for _ in 0..num_geoms {
250                let start = part_offsets[part_idx];
251                let end = part_offsets[part_idx + 1];
252                lengths.push(end - start);
253                part_idx += 1;
254            }
255        }
256        // Note: Point/MultiPoint don't contribute to part_offsets, so don't advance part_idx
257    }
258
259    lengths
260}
261
262/// Normalize `geometry_offsets` for mixed geometry types.
263/// When only Multi* geometries contribute to `geometry_offsets`, this expands it to have
264/// one entry per geometry plus a trailing entry.
265///
266/// The sparse `geometry_offsets` is a cumulative count array for Multi* types only:
267///   `[0, count_of_first_multi, count_of_first_multi + count_of_second_multi, ...]`
268///
269/// Non-Multi types are not represented in `geometry_offsets` but each has an implicit
270/// count of 1. The normalized output will have N+1 entries for N geometry types.
271fn normalize_geometry_offsets(vector_types: &[GeometryType], geometry_offsets: &[u32]) -> Vec<u32> {
272    // Check if already normalized (has N+1 entries for N geometry types)
273    if geometry_offsets.len() == vector_types.len() + 1 {
274        return geometry_offsets.to_vec();
275    }
276
277    let mut normalized = Vec::with_capacity(vector_types.len() + 1);
278    let mut current_offset = 0_u32;
279    let mut sparse_idx = 0_usize; // Index into sparse geometry_offsets
280
281    for &geom_type in vector_types {
282        normalized.push(current_offset);
283
284        if geom_type.is_multi() {
285            // Multi* types get their count from the sparse array
286            if sparse_idx + 1 < geometry_offsets.len() {
287                let start = geometry_offsets[sparse_idx];
288                let end = geometry_offsets[sparse_idx + 1];
289                current_offset += end - start;
290                sparse_idx += 1;
291            }
292        } else {
293            // Non-Multi types have implicit count of 1
294            current_offset += 1;
295        }
296    }
297
298    normalized.push(current_offset);
299    normalized
300}
301
302/// Normalize `part_offsets` for ring-based indexing (Polygon mixed with `Point`/`LineString`).
303/// Returns an offset array where `offset[i+1]` - `offset[i]` = number of ring length entries
304/// that geometry `i` contributes to the rings stream.
305///
306/// When a Polygon is present:
307/// - Point: 0 entries (doesn't contribute to rings)
308/// - `LineString`: 1 entry (its vertex count goes to rings)
309/// - Polygon: `ring_count` entries (each ring's vertex count)
310fn normalize_part_offsets_for_rings(
311    vector_types: &[GeometryType],
312    part_offsets: &[u32],
313    ring_offsets: &[u32],
314) -> Vec<u32> {
315    let num_rings = if ring_offsets.is_empty() {
316        0
317    } else {
318        u32::try_from(ring_offsets.len() - 1).expect("ring count overflow")
319    };
320
321    // Check if already normalized (has N+1 entries for N geometry types)
322    if part_offsets.len() == vector_types.len() + 1 {
323        return part_offsets.to_vec();
324    }
325
326    // Build normalized offset array for ring-based indexing
327    let mut normalized = Vec::with_capacity(vector_types.len() + 1);
328    let mut ring_idx = 0_u32;
329    let mut part_idx = 0_usize;
330
331    for &geom_type in vector_types {
332        normalized.push(ring_idx);
333
334        if geom_type == GeometryType::Point {
335            // Point doesn't contribute to ring_offsets
336        } else if geom_type.is_linestring() {
337            // LineString contributes 1 entry to ring_offsets (its vertex count)
338            ring_idx += 1;
339        } else if geom_type.is_polygon() {
340            // Polygon contributes ring_count entries (vertex count for each ring)
341            if part_idx + 1 < part_offsets.len() {
342                let ring_count = part_offsets[part_idx + 1] - part_offsets[part_idx];
343                ring_idx += ring_count;
344                part_idx += 1;
345            }
346        }
347        // MultiPolygon is handled through geometry_offsets
348    }
349
350    normalized.push(ring_idx.min(num_rings));
351    normalized
352}
353
354/// Main geometry encoding function.
355///
356/// `on_stream`, when provided, is called with the [`StreamType`] and raw
357/// `u32` payload of every stream that is about to be encoded.  This lets
358/// callers profile the data for encoder selection without duplicating any
359/// topology logic.
360#[expect(clippy::type_complexity)]
361pub fn encode_geometry(
362    decoded: &GeometryValues,
363    encoder: &GeometryEncoder,
364    mut on_stream: Option<&mut dyn FnMut(StreamType, &[u32])>,
365) -> MltResult<EncodedGeometry> {
366    let GeometryValues {
367        vector_types,
368        geometry_offsets,
369        part_offsets,
370        ring_offsets,
371        index_buffer,
372        triangles,
373        vertices,
374    } = decoded;
375
376    // Normalize part_offsets if needed for mixed geometry types with rings
377    // When we have rings, we need to expand part_offsets to have N+1 entries
378    // When we don't have rings, part_offsets is already in the correct sparse format
379    let normalized_parts = if geometry_offsets.is_none() && ring_offsets.is_some() {
380        if let Some(part_offs) = part_offsets {
381            if let Some(ring_offs) = ring_offsets {
382                // Mixed with rings (e.g., Point + Polygon) - normalize for ring-based indexing
383                Some(normalize_part_offsets_for_rings(
384                    vector_types,
385                    part_offs,
386                    ring_offs,
387                ))
388            } else {
389                part_offsets.clone()
390            }
391        } else {
392            None
393        }
394    } else {
395        part_offsets.clone()
396    };
397    let part_offsets = &normalized_parts;
398
399    // Normalize geometry_offsets for mixed geometry types
400    let normalized_geom_offs = geometry_offsets
401        .as_ref()
402        .map(|g| normalize_geometry_offsets(vector_types, g));
403    let geometry_offsets = &normalized_geom_offs;
404
405    // Encode geometry types (meta stream)
406    let meta = {
407        let vector_types_u32: Vec<u32> = vector_types.iter().map(|t| *t as u32).collect();
408        if let Some(cb) = &mut on_stream {
409            cb(StreamType::Length(LengthType::VarBinary), &vector_types_u32);
410        }
411        EncodedStream::encode_u32s_of_type(
412            &vector_types_u32,
413            encoder.meta,
414            StreamType::Length(LengthType::VarBinary),
415        )?
416    };
417
418    let mut items = Vec::new();
419    let has_linestrings = vector_types
420        .iter()
421        .copied()
422        .any(GeometryType::is_linestring);
423    let has_tessellation = triangles.is_some();
424
425    // Encode topology streams based on geometry structure
426    if let Some(geom_offs) = geometry_offsets {
427        // Encode geometry lengths (NumGeometries)
428        let lengths = encode_root_length_stream(vector_types, geom_offs, GeometryType::Polygon);
429        if !lengths.is_empty() || has_tessellation {
430            // For tessellated polygons with outlines, Java always includes this stream
431            // even when empty
432            if let Some(cb) = &mut on_stream {
433                cb(StreamType::Length(LengthType::Geometries), &lengths);
434            }
435            items.push(EncodedStream::encode_u32s_of_type(
436                &lengths,
437                encoder.geometries,
438                StreamType::Length(LengthType::Geometries),
439            )?);
440        }
441
442        if let Some(part_offs) = part_offsets {
443            if let Some(ring_offs) = ring_offsets {
444                // Full topology: geom -> parts -> rings
445                // When rings are present (Polygon in layer), LineStrings contribute to rings, not parts.
446                // So is_line_string_present should be false for the parts stream.
447                let part_lengths = encode_level1_length_stream(
448                    vector_types,
449                    geom_offs,
450                    part_offs,
451                    false, // LineStrings contribute to rings, not parts
452                );
453                if !part_lengths.is_empty() {
454                    if let Some(cb) = &mut on_stream {
455                        cb(StreamType::Length(LengthType::Parts), &part_lengths);
456                    }
457                    items.push(EncodedStream::encode_u32s_of_type(
458                        &part_lengths,
459                        encoder.rings,
460                        StreamType::Length(LengthType::Parts),
461                    )?);
462                }
463
464                let ring_lengths =
465                    encode_level2_length_stream(vector_types, geom_offs, part_offs, ring_offs);
466                if !ring_lengths.is_empty() {
467                    if let Some(cb) = &mut on_stream {
468                        cb(StreamType::Length(LengthType::Rings), &ring_lengths);
469                    }
470                    items.push(EncodedStream::encode_u32s_of_type(
471                        &ring_lengths,
472                        encoder.rings2,
473                        StreamType::Length(LengthType::Rings),
474                    )?);
475                }
476            } else {
477                // Only geom -> parts (no rings)
478                let part_lengths = encode_level1_without_ring_buffer_length_stream(
479                    vector_types,
480                    geom_offs,
481                    part_offs,
482                );
483                if !part_lengths.is_empty() {
484                    if let Some(cb) = &mut on_stream {
485                        cb(StreamType::Length(LengthType::Parts), &part_lengths);
486                    }
487                    items.push(EncodedStream::encode_u32s_of_type(
488                        &part_lengths,
489                        encoder.no_rings,
490                        StreamType::Length(LengthType::Parts),
491                    )?);
492                }
493            }
494        }
495    } else if let Some(part_offs) = part_offsets {
496        // No geometry offsets (no Multi* types), encode from parts
497        if let Some(ring_offs) = ring_offsets {
498            // parts -> rings (e.g., Polygon without Multi, or mixed Point + Polygon)
499
500            // For tessellated polygons with outlines, Java includes an empty geometries stream
501            if has_tessellation {
502                if let Some(cb) = &mut on_stream {
503                    cb(StreamType::Length(LengthType::Geometries), &[]);
504                }
505                items.push(EncodedStream::encode_u32s_of_type(
506                    &[],
507                    encoder.geometries,
508                    StreamType::Length(LengthType::Geometries),
509                )?);
510            }
511
512            let part_lengths =
513                encode_root_length_stream(vector_types, part_offs, GeometryType::LineString);
514            if !part_lengths.is_empty() {
515                if let Some(cb) = &mut on_stream {
516                    cb(StreamType::Length(LengthType::Parts), &part_lengths);
517                }
518                items.push(EncodedStream::encode_u32s_of_type(
519                    &part_lengths,
520                    encoder.parts,
521                    StreamType::Length(LengthType::Parts),
522                )?);
523            }
524
525            // Ring lengths: part_offs is a dense N+1 array (one slot per geometry,
526            // including Points).  ring_offs stores vertex offsets for each slot.
527            // Use the dense-aware helper so Point slots are correctly skipped by
528            // index rather than by a running counter that ignores non-length types.
529            let ring_lengths =
530                encode_ring_lengths_for_mixed(vector_types, part_offs, ring_offs, has_linestrings);
531            if !ring_lengths.is_empty() {
532                if let Some(cb) = &mut on_stream {
533                    cb(StreamType::Length(LengthType::Rings), &ring_lengths);
534                }
535                items.push(EncodedStream::encode_u32s_of_type(
536                    &ring_lengths,
537                    encoder.parts_ring,
538                    StreamType::Length(LengthType::Rings),
539                )?);
540            }
541        } else {
542            // Only parts (e.g., LineString)
543            let lengths = encode_root_length_stream(vector_types, part_offs, GeometryType::Point);
544            if !lengths.is_empty() {
545                if let Some(cb) = &mut on_stream {
546                    cb(StreamType::Length(LengthType::Parts), &lengths);
547                }
548                items.push(EncodedStream::encode_u32s_of_type(
549                    &lengths,
550                    encoder.only_parts,
551                    StreamType::Length(LengthType::Parts),
552                )?);
553            }
554        }
555    }
556
557    // Encode triangles stream if present (for pre-tessellated polygons)
558    if let Some(tris) = triangles {
559        if let Some(cb) = &mut on_stream {
560            cb(StreamType::Length(LengthType::Triangles), tris);
561        }
562        items.push(EncodedStream::encode_u32s_of_type(
563            tris,
564            encoder.triangles,
565            StreamType::Length(LengthType::Triangles),
566        )?);
567    }
568
569    // Encode index buffer if present (for pre-tessellated polygons)
570    if let Some(idx_buf) = index_buffer {
571        if let Some(cb) = &mut on_stream {
572            cb(StreamType::Offset(OffsetType::Index), idx_buf);
573        }
574        items.push(EncodedStream::encode_u32s_of_type(
575            idx_buf,
576            encoder.triangles_indexes,
577            StreamType::Offset(OffsetType::Index),
578        )?);
579    }
580
581    // Encode vertex buffer (and dictionary offsets when Morton encoding is active)
582    if let Some(verts) = vertices {
583        match encoder.vertex_buffer_type {
584            VertexBufferType::Vec2 => {
585                if let Some(cb) = &mut on_stream {
586                    let delta = encode_componentwise_delta_vec2s(verts);
587                    cb(StreamType::Data(DictionaryType::Vertex), &delta);
588                }
589                items.push(encode_vertex_buffer(verts, encoder.vertex.physical)?);
590            }
591            VertexBufferType::Morton => {
592                let morton_meta = z_order_params(verts)?;
593                let (dict, offsets) = build_morton_dict(verts, morton_meta)?;
594                if let Some(cb) = &mut on_stream {
595                    cb(StreamType::Offset(OffsetType::Vertex), &offsets);
596                    cb(
597                        StreamType::Data(DictionaryType::Morton),
598                        &morton_deltas(&dict),
599                    );
600                }
601                items.push(EncodedStream::encode_u32s_of_type(
602                    &offsets,
603                    encoder.vertex_offsets,
604                    StreamType::Offset(OffsetType::Vertex),
605                )?);
606                items.push(encode_morton_vertex_buffer(
607                    &dict,
608                    morton_meta,
609                    encoder.vertex.physical,
610                )?);
611            }
612        }
613    }
614
615    Ok(EncodedGeometry { meta, items })
616}
617
618/// How to encode Geometry
619#[derive(Debug, Clone, Copy)]
620#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
621#[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))]
622pub struct GeometryEncoder {
623    /// Encoding settings for the geometry types (meta) stream.
624    pub meta: IntEncoder,
625
626    /// Encoding for the geometry length stream.
627    pub geometries: IntEncoder,
628
629    /// Encoding for parts length stream when rings are present.
630    pub rings: IntEncoder,
631    /// Encoding for ring vertex-count stream.
632    pub rings2: IntEncoder,
633    /// Encoding for parts length stream when rings are not present.
634    pub no_rings: IntEncoder,
635
636    /// Encoding for parts length stream (with rings) when `geometry_offsets` absent.
637    pub parts: IntEncoder,
638    /// Encoding for ring lengths when `geometry_offsets` absent.
639    pub parts_ring: IntEncoder,
640
641    /// Encoding for parts-only stream (e.g. `LineString`, no rings).
642    pub only_parts: IntEncoder,
643
644    /// Encoding for triangles count stream (pre-tessellated polygons).
645    pub triangles: IntEncoder,
646    /// Encoding for triangle index buffer (pre-tessellated polygons).
647    pub triangles_indexes: IntEncoder,
648
649    /// Encoding for the vertex data stream (logical encoding is always `ComponentwiseDelta` if `vertex_buffer_type==Vec2`).
650    pub vertex: IntEncoder,
651    /// Encoding for vertex offsets (used when `vertex_buffer_type` is not `Vec2`).
652    pub vertex_offsets: IntEncoder,
653
654    /// How the vertex buffer should be encoded.
655    // vertex_buffer_type is pinned to Vec2 in the Arbitrary impl below.
656    // Morton encoding requires coordinates in a bounded range and is tested via dedicated tests only.
657    #[cfg_attr(test, proptest(value = "VertexBufferType::Vec2"))]
658    #[cfg_attr(all(not(test), feature = "arbitrary"), arbitrary(value = VertexBufferType::Vec2))]
659    pub vertex_buffer_type: VertexBufferType,
660}
661
662impl GeometryEncoder {
663    /// Use the provided encoder for all streams.
664    #[must_use]
665    pub fn all(encoder: IntEncoder) -> Self {
666        Self {
667            meta: encoder,
668            geometries: encoder,
669            rings: encoder,
670            rings2: encoder,
671            no_rings: encoder,
672            parts: encoder,
673            parts_ring: encoder,
674            only_parts: encoder,
675            triangles: encoder,
676            triangles_indexes: encoder,
677            vertex: encoder,
678            vertex_offsets: encoder,
679            vertex_buffer_type: VertexBufferType::Vec2,
680        }
681    }
682
683    /// Set encoding for the geometry types (meta) stream.
684    pub fn meta(&mut self, e: IntEncoder) -> &mut Self {
685        self.meta = e;
686        self
687    }
688
689    /// Set encoding for the geometry length stream.
690    pub fn geometries(&mut self, e: IntEncoder) -> &mut Self {
691        self.geometries = e;
692        self
693    }
694
695    /// Set encoding for parts length stream when rings are present.
696    pub fn rings(&mut self, e: IntEncoder) -> &mut Self {
697        self.rings = e;
698        self
699    }
700
701    /// Set encoding for ring vertex-count stream.
702    pub fn rings2(&mut self, e: IntEncoder) -> &mut Self {
703        self.rings2 = e;
704        self
705    }
706
707    /// Set encoding for parts length stream when rings are not present.
708    pub fn no_rings(&mut self, e: IntEncoder) -> &mut Self {
709        self.no_rings = e;
710        self
711    }
712
713    /// Set encoding for parts length stream (with rings) when `geometry_offsets` absent.
714    pub fn parts(&mut self, e: IntEncoder) -> &mut Self {
715        self.parts = e;
716        self
717    }
718
719    /// Set encoding for ring lengths when `geometry_offsets` absent.
720    pub fn parts_ring(&mut self, e: IntEncoder) -> &mut Self {
721        self.parts_ring = e;
722        self
723    }
724
725    /// Set encoding for parts-only stream (e.g. `LineString`, no rings).
726    pub fn only_parts(&mut self, e: IntEncoder) -> &mut Self {
727        self.only_parts = e;
728        self
729    }
730
731    /// Set encoding for triangles count stream (pre-tessellated polygons).
732    pub fn triangles(&mut self, e: IntEncoder) -> &mut Self {
733        self.triangles = e;
734        self
735    }
736
737    /// Set encoding for triangle index buffer (pre-tessellated polygons).
738    pub fn triangles_indexes(&mut self, e: IntEncoder) -> &mut Self {
739        self.triangles_indexes = e;
740        self
741    }
742
743    /// Set encoding for the vertex data stream.
744    pub fn vertex(&mut self, e: IntEncoder) -> &mut Self {
745        self.vertex = e;
746        self
747    }
748
749    /// Set encoding for vertex offsets (dictionary encoding).
750    pub fn vertex_offsets(&mut self, e: IntEncoder) -> &mut Self {
751        self.vertex_offsets = e;
752        self
753    }
754
755    /// Set the vertex buffer encoding type.
756    pub fn vertex_buffer_type(&mut self, t: VertexBufferType) -> &mut Self {
757        self.vertex_buffer_type = t;
758        self
759    }
760}
761
762#[cfg(test)]
763mod tests {
764    use super::*;
765
766    #[test]
767    fn test_encode_root_length_stream() {
768        // Single Polygon geometry (no Multi)
769        let types = vec![GeometryType::Polygon];
770        let offsets = vec![0, 1]; // One polygon
771
772        let lengths = encode_root_length_stream(&types, &offsets, GeometryType::Polygon);
773        // Polygon == buffer_id, so no length encoded
774        assert!(lengths.is_empty());
775
776        // MultiPolygon needs length encoded
777        let types = vec![GeometryType::MultiPolygon];
778        let offsets = vec![0, 2]; // MultiPolygon with 2 polygons
779
780        let lengths = encode_root_length_stream(&types, &offsets, GeometryType::Polygon);
781        assert_eq!(lengths, vec![2]);
782    }
783}