1use std::collections::HashMap;
2use std::mem;
3
4use geo_types::Coord;
5use probabilistic_collections::SipHasherBuilder;
6use probabilistic_collections::hyperloglog::HyperLogLog;
7use usize_cast::{FromUsize as _, IntoUsize as _};
8
9use super::model::VertexBufferType;
10use crate::MltResult;
11use crate::codecs::hilbert::hilbert_sort_key;
12use crate::codecs::zigzag::encode_componentwise_delta_vec2s;
13use crate::decoder::GeometryType::{LineString, Point, Polygon};
14use crate::decoder::{
15 ColumnType, DictionaryType, GeometryType, GeometryValues, LengthType, LogicalEncoding, Morton,
16 OffsetType, PhysicalEncoding, StreamMeta, StreamType,
17};
18use crate::encoder::model::{CurveParams, StreamCtx};
19use crate::encoder::{Codecs, Encoder, PhysicalCodecs, write_stream_payload};
20
21#[hotpath::measure]
29fn build_morton_dict(vertices: &[i32], meta: Morton) -> MltResult<(Vec<u32>, Vec<u32>)> {
30 let codes: Vec<u32> = vertices
31 .as_chunks::<2>()
32 .0
33 .iter()
34 .map(|&[x, y]| meta.encode_morton(x, y))
35 .collect::<Result<_, _>>()?;
36
37 let mut dict = codes.clone();
38 dict.sort_unstable();
39 dict.dedup();
40
41 #[expect(
42 clippy::cast_possible_truncation,
43 reason = "dict.len() <= u32::MAX (deduped u32 codes)"
44 )]
45 let code_to_idx: HashMap<u32, u32> = dict
46 .iter()
47 .enumerate()
48 .map(|(i, &c)| (c, i as u32))
49 .collect();
50 let offsets: Vec<u32> = codes.iter().map(|code| code_to_idx[code]).collect();
51
52 Ok((dict, offsets))
53}
54
55#[hotpath::measure]
66fn build_hilbert_dict(
67 vertices: &[i32],
68 params: CurveParams,
69 offsets: &mut Vec<u32>,
70 indexed: &mut Vec<u64>,
71 dict_xy: &mut Vec<i32>,
72 remap: &mut HashMap<u32, u32>,
73) {
74 offsets.clear();
75 indexed.clear();
76 dict_xy.clear();
77 remap.clear();
78
79 let coord_count = vertices.len() / 2;
80 if coord_count == 0 {
81 return;
82 }
83 offsets.reserve(coord_count);
84 indexed.reserve(coord_count);
85 dict_xy.reserve(coord_count * 2);
86 remap.reserve(coord_count);
87
88 for (i, &[x, y]) in vertices.as_chunks::<2>().0.iter().enumerate() {
89 let k = hilbert_sort_key(Coord { x, y }, params);
90 offsets.push(k);
91 let packed = (u64::from(k) << 32) | u64::from_usize(i);
94 indexed.push(packed);
95 }
96 indexed.sort_unstable();
97
98 let mut last_key: Option<u32> = None;
99 for &packed in &*indexed {
100 let key = (packed >> 32) as u32;
101 let src_idx = ((packed & 0xFFFF_FFFF) as u32).into_usize();
102 if last_key != Some(key) {
103 #[expect(
104 clippy::cast_possible_truncation,
105 reason = "dict.len() <= coord_count <= u32::MAX"
106 )]
107 let slot = (dict_xy.len() / 2) as u32;
108 dict_xy.push(vertices[src_idx * 2]);
109 dict_xy.push(vertices[src_idx * 2 + 1]);
110 remap.insert(key, slot);
111 last_key = Some(key);
112 }
113 }
114
115 for k in offsets.iter_mut() {
116 *k = remap[k];
117 }
118}
119
120#[inline]
125fn extend_offsets(lengths: &mut Vec<u32>, offsets: &[u32]) -> usize {
126 lengths.extend(offsets.windows(2).map(|w| w[1] - w[0]));
127 offsets.len() - 1
128}
129
130fn encode_root_length_stream(
140 geom_types: &[GeometryType],
141 geom_offsets: &[u32],
142 buffer_id: GeometryType,
143) -> Vec<u32> {
144 if geom_offsets.len() == geom_types.len() + 1 {
145 geom_types
147 .iter()
148 .zip(geom_offsets.windows(2))
149 .filter(|&(&t, _)| t > buffer_id)
150 .map(|(_, w)| w[1] - w[0])
151 .collect()
152 } else {
153 geom_types
155 .iter()
156 .filter(|&&t| t > buffer_id)
157 .zip(geom_offsets.windows(2))
158 .map(|(_, w)| w[1] - w[0])
159 .collect()
160 }
161}
162
163fn encode_level1_length_stream(
165 geom_types: &[GeometryType],
166 geom_offsets: &[u32],
167 part_offsets: &[u32],
168 is_line_string_present: bool,
169) -> Vec<u32> {
170 let mut lengths = Vec::new();
171 let mut part_idx = 0;
172
173 for (i, &geom_type) in geom_types.iter().enumerate() {
174 if geom_type.is_polygon() || (is_line_string_present && geom_type.is_linestring()) {
175 let n = (geom_offsets[i + 1] - geom_offsets[i]).into_usize();
176 part_idx += extend_offsets(&mut lengths, &part_offsets[part_idx..=part_idx + n]);
177 }
178 }
181
182 lengths
183}
184
185fn encode_ring_lengths_for_mixed(
193 geom_types: &[GeometryType],
194 part_offsets: &[u32],
195 ring_offsets: &[u32],
196 has_line_string: bool,
197) -> Vec<u32> {
198 let mut lengths = Vec::new();
199 for (i, &geom_type) in geom_types.iter().enumerate() {
200 if geom_type.is_polygon() || (has_line_string && geom_type.is_linestring()) {
201 let s = part_offsets[i].into_usize();
202 let e = part_offsets[i + 1].into_usize();
203 extend_offsets(&mut lengths, &ring_offsets[s..=e]);
204 }
205 }
206 lengths
207}
208
209fn encode_level2_length_stream(
215 geom_types: &[GeometryType],
216 geom_offsets: &[u32],
217 part_offsets: &[u32],
218 ring_offsets: &[u32],
219) -> Vec<u32> {
220 let mut lengths = Vec::new();
221 let mut part_idx = 0;
222 let mut ring_idx = 0;
223
224 for (i, &geom_type) in geom_types.iter().enumerate() {
225 let count = (geom_offsets[i + 1] - geom_offsets[i]).into_usize();
226
227 if geom_type.is_polygon() {
231 for _ in 0..count {
233 let n = (part_offsets[part_idx + 1] - part_offsets[part_idx]).into_usize();
234 ring_idx += extend_offsets(&mut lengths, &ring_offsets[ring_idx..=ring_idx + n]);
235 part_idx += 1;
236 }
237 } else if geom_type.is_linestring() {
238 ring_idx += extend_offsets(&mut lengths, &ring_offsets[ring_idx..=ring_idx + count]);
240 }
241 }
243
244 lengths
245}
246
247fn encode_level1_without_ring_buffer_length_stream(
254 geom_types: &[GeometryType],
255 geom_offsets: &[u32],
256 part_offsets: &[u32],
257) -> Vec<u32> {
258 let mut lengths = Vec::new();
259 let mut part_idx = 0;
260
261 for (i, &geom_type) in geom_types.iter().enumerate() {
262 if geom_type.is_linestring() {
263 let n = (geom_offsets[i + 1] - geom_offsets[i]).into_usize();
264 part_idx += extend_offsets(&mut lengths, &part_offsets[part_idx..=part_idx + n]);
265 }
266 }
268
269 lengths
270}
271
272fn normalize_geometry_offsets(vector_types: &[GeometryType], geom_offsets: &[u32]) -> Vec<u32> {
274 let mut normalized = Vec::with_capacity(vector_types.len() + 1);
275 let mut offset = 0_u32;
276 let mut sparse_idx = 0_usize; for &geom_type in vector_types {
279 normalized.push(offset);
280
281 if geom_type.is_multi() {
282 if sparse_idx + 1 < geom_offsets.len() {
284 let start = geom_offsets[sparse_idx];
285 let end = geom_offsets[sparse_idx + 1];
286 offset += end - start;
287 sparse_idx += 1;
288 }
289 } else {
290 offset += 1;
292 }
293 }
294
295 normalized.push(offset);
296 normalized
297}
298
299fn normalize_part_offsets_for_rings(
311 vector_types: &[GeometryType],
312 part_offsets: &[u32],
313 ring_offsets: &[u32],
314) -> Vec<u32> {
315 let mut normalized = Vec::with_capacity(vector_types.len() + 1);
316 let mut ring_idx = 0_u32;
317 let mut part_idx = 0_usize;
318
319 for &geom_type in vector_types {
320 normalized.push(ring_idx);
321
322 if geom_type == Point {
323 } else if geom_type.is_linestring() {
325 ring_idx += 1;
327 } else if geom_type.is_polygon() && part_idx + 1 < part_offsets.len() {
328 let ring_count = part_offsets[part_idx + 1] - part_offsets[part_idx];
330 ring_idx += ring_count;
331 part_idx += 1;
332 }
333 }
335
336 debug_assert_eq!(
338 ring_idx.into_usize(),
339 ring_offsets.len().saturating_sub(1),
340 "ring index mismatch after normalization"
341 );
342 normalized.push(ring_idx);
343 normalized
344}
345
346#[hotpath::measure]
355fn dict_may_be_beneficial(vertices: &[i32], enc: &Encoder) -> bool {
356 const MAXIMUM_UNIQUENESS_THRESHOLD_FOR_DICT: f64 = 0.66;
357
358 let coord_count = vertices.len() / 2;
359 if coord_count == 0 || enc.morton_cache.is_none() {
360 return false;
361 }
362
363 let mut hll = HyperLogLog::<Coord<i32>>::with_hasher(0.03, SipHasherBuilder::from_seed(0, 0));
364 for &[x, y] in vertices.as_chunks::<2>().0 {
365 hll.insert(&Coord::<i32> { x, y });
366 }
367 #[expect(clippy::cast_precision_loss)]
368 let estimated_unique = hll.len().clamp(0.0, coord_count as f64);
369 #[expect(clippy::cast_precision_loss)]
370 let uniqueness_ratio = estimated_unique / coord_count as f64;
371 uniqueness_ratio < MAXIMUM_UNIQUENESS_THRESHOLD_FOR_DICT
372}
373
374fn get_morton(enc: &Encoder) -> Morton {
378 enc.morton_cache.expect(
379 "morton_cache populated by StagedLayer::encode_into; gated by dict_may_be_beneficial",
380 )
381}
382
383fn get_hilbert_params(enc: &Encoder) -> CurveParams {
385 enc.hilbert_cache
386 .expect("hilbert_cache populated by StagedLayer::encode_into")
387}
388
389fn encode_vec2_vertex_stream(
392 vertices: &[i32],
393 enc: &mut Encoder,
394 codecs: &mut Codecs,
395) -> MltResult<u8> {
396 let delta = encode_componentwise_delta_vec2s(vertices, &mut codecs.logical.u32_tmp);
397 let ctx = StreamCtx::geom(StreamType::Data(DictionaryType::Vertex), "vertex");
398 let logical = LogicalEncoding::ComponentwiseDelta;
399 write_geo_precomputed_stream(delta, ctx, logical, enc, &mut codecs.physical)
400}
401
402fn encode_morton_vertex_streams(
405 vertices: &[i32],
406 enc: &mut Encoder,
407 codecs: &mut Codecs,
408) -> MltResult<u8> {
409 let morton = get_morton(enc);
410 let (dict, offsets) = build_morton_dict(vertices, morton)?;
411 let mut n: u8 = 0;
412
413 let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Vertex), "vertex_offsets");
414 n += write_geo_u32_stream(&offsets, ctx, enc, codecs)?;
415
416 let delta = encode_morton_deltas(&dict, &mut codecs.logical.u32_tmp);
417 let ctx = StreamCtx::geom(StreamType::Data(DictionaryType::Morton), "vertex");
418 let logical = LogicalEncoding::MortonDelta(morton);
419 n += write_geo_precomputed_stream(delta, ctx, logical, enc, &mut codecs.physical)?;
420 Ok(n)
421}
422
423fn encode_hilbert_vertex_streams(
427 vertices: &[i32],
428 enc: &mut Encoder,
429 codecs: &mut Codecs,
430) -> MltResult<u8> {
431 let params = get_hilbert_params(enc);
432 let mut n: u8 = 0;
433
434 let mut offsets = mem::take(&mut codecs.logical.hilbert_offsets);
437 let mut indexed = mem::take(&mut codecs.logical.hilbert_indexed);
438 let mut dict_xy = mem::take(&mut codecs.logical.hilbert_dict_xy);
439 let mut remap = mem::take(&mut codecs.logical.hilbert_remap);
440
441 build_hilbert_dict(
442 vertices,
443 params,
444 &mut offsets,
445 &mut indexed,
446 &mut dict_xy,
447 &mut remap,
448 );
449 codecs.logical.hilbert_indexed = indexed;
452 codecs.logical.hilbert_remap = remap;
453
454 let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Vertex), "vertex_offsets");
455 n += write_geo_u32_stream(&offsets, ctx, enc, codecs)?;
456
457 encode_componentwise_delta_vec2s(&dict_xy, &mut offsets);
460 let ctx = StreamCtx::geom(StreamType::Data(DictionaryType::Vertex), "vertex");
461 let logical = LogicalEncoding::ComponentwiseDelta;
462 n += write_geo_precomputed_stream(&offsets, ctx, logical, enc, &mut codecs.physical)?;
463
464 codecs.logical.hilbert_offsets = offsets;
465 codecs.logical.hilbert_dict_xy = dict_xy;
466 Ok(n)
467}
468
469fn write_geo_u32_stream(
475 data: &[u32],
476 ctx: StreamCtx,
477 enc: &mut Encoder,
478 codecs: &mut Codecs,
479) -> MltResult<u8> {
480 Ok(if data.is_empty() && !enc.force_stream(&ctx) {
481 0
482 } else {
483 codecs.write_int_stream(data, &ctx, enc)?;
484 1
485 })
486}
487
488fn write_geo_precomputed_stream(
493 data: &[u32],
494 ctx: StreamCtx,
495 logical: LogicalEncoding,
496 enc: &mut Encoder,
497 physical: &mut PhysicalCodecs,
498) -> MltResult<u8> {
499 use PhysicalEncoding as PE;
500
501 Ok(if data.is_empty() && !enc.force_stream(&ctx) {
502 0
503 } else {
504 if let Some(int_enc) = enc.override_int_enc(&ctx) {
505 physical.write_encoded_as::<[u32]>(&ctx, enc, logical, data, int_enc.physical)?;
506 } else if data.is_empty() {
507 let meta = StreamMeta::new2(ctx.stream_type, logical, PE::None, 0)?;
508 write_stream_payload(enc, meta, false, &[])?;
509 } else {
510 let allow_fastpfor = enc.config().allow_fastpfor();
511 let mut alt = enc.try_alternatives();
512 if allow_fastpfor {
513 alt.with(|enc| {
514 let vals = physical.fastpfor(data)?;
515 let meta =
516 StreamMeta::new2(ctx.stream_type, logical, PE::FastPFor256, data.len())?;
517 write_stream_payload(enc, meta, false, vals)
518 })?;
519 }
520 alt.with(|enc| {
521 let vals = physical.varint(data);
522 let meta = StreamMeta::new2(ctx.stream_type, logical, PE::VarInt, data.len())?;
523 write_stream_payload(enc, meta, false, vals)
524 })?;
525 }
526 1
527 })
528}
529
530impl GeometryValues {
531 #[hotpath::measure]
533 pub fn write_to(self, enc: &mut Encoder, codecs: &mut Codecs) -> MltResult<()> {
534 let Self {
535 vector_types,
536 geometry_offsets,
537 part_offsets,
538 ring_offsets,
539 index_buffer,
540 triangles,
541 vertices,
542 } = self;
543
544 let geom_offsets = geometry_offsets.unwrap_or_default();
549 let part_offsets = part_offsets.unwrap_or_default();
550 let ring_offsets = ring_offsets.unwrap_or_default();
551 let index_buffer = index_buffer.unwrap_or_default();
552 let triangles = triangles.unwrap_or_default();
553 let vertices = vertices.unwrap_or_default();
554
555 if enc.hilbert_cache.is_none() {
559 enc.hilbert_cache = Some(CurveParams::from_vertices(&vertices));
560 }
561 if enc.morton_cache.is_none() {
562 let p = enc.hilbert_cache.expect("populated above");
563 enc.morton_cache = Morton::new(p.bits, p.shift).ok();
564 }
565
566 let meta: Vec<u32> = vector_types.iter().map(|t| *t as u32).collect();
567
568 let part_offsets = if geom_offsets.is_empty()
569 && !ring_offsets.is_empty()
570 && !part_offsets.is_empty()
571 && part_offsets.len() != vector_types.len() + 1
572 {
573 normalize_part_offsets_for_rings(&vector_types, &part_offsets, &ring_offsets)
575 } else {
576 part_offsets
577 };
578
579 enc.write_column_type(ColumnType::Geometry)?;
582 let stream_count_pos = enc.data().len();
583 enc.data_mut().push(0); let mut n: u8 = 0;
585
586 let ctx = StreamCtx::geom(StreamType::Length(LengthType::VarBinary), "meta");
588 codecs.write_int_stream(&meta, &ctx, enc)?;
589 n += 1;
590
591 if !geom_offsets.is_empty() {
593 let geom_offsets = if geom_offsets.len() == vector_types.len() + 1 {
594 geom_offsets
595 } else {
596 normalize_geometry_offsets(&vector_types, &geom_offsets)
597 };
598 let data = encode_root_length_stream(&vector_types, &geom_offsets, Polygon);
599 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries");
600 n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
601
602 if !part_offsets.is_empty() {
608 if ring_offsets.is_empty() {
609 let data = encode_level1_without_ring_buffer_length_stream(
611 &vector_types,
612 &geom_offsets,
613 &part_offsets,
614 );
615 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings");
616 n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
617 } else {
618 let data = encode_level1_length_stream(
621 &vector_types,
622 &geom_offsets,
623 &part_offsets,
624 false,
625 );
626 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "rings");
627 n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
628
629 let data = encode_level2_length_stream(
630 &vector_types,
631 &geom_offsets,
632 &part_offsets,
633 &ring_offsets,
634 );
635 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "rings2");
636 n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
637 }
638 }
639 } else if !part_offsets.is_empty() {
640 if ring_offsets.is_empty() {
641 let data = encode_root_length_stream(&vector_types, &part_offsets, Point);
642 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings");
643 n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
644 } else {
645 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries");
649 n += write_geo_u32_stream(&[], ctx, enc, codecs)?;
650
651 let data = encode_root_length_stream(&vector_types, &part_offsets, LineString);
652 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "parts");
653 n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
654
655 let has_line_string = vector_types
659 .iter()
660 .copied()
661 .any(GeometryType::is_linestring);
662 let data = encode_ring_lengths_for_mixed(
663 &vector_types,
664 &part_offsets,
665 &ring_offsets,
666 has_line_string,
667 );
668 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "parts_ring");
669 n += write_geo_u32_stream(&data, ctx, enc, codecs)?;
670 }
671 }
672
673 let ctx = StreamCtx::geom(StreamType::Length(LengthType::Triangles), "triangles");
674 n += write_geo_u32_stream(&triangles, ctx, enc, codecs)?;
675 let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Index), "triangles_indexes");
676 n += write_geo_u32_stream(&index_buffer, ctx, enc, codecs)?;
677
678 if let Some(forced) = enc.override_vertex_buffer_type() {
679 n += match forced {
680 VertexBufferType::Vec2 => encode_vec2_vertex_stream(&vertices, enc, codecs)?,
681 VertexBufferType::Morton => encode_morton_vertex_streams(&vertices, enc, codecs)?,
682 VertexBufferType::Hilbert => encode_hilbert_vertex_streams(&vertices, enc, codecs)?,
683 };
684 } else if dict_may_be_beneficial(&vertices, enc) {
685 let mut winner_size: usize = usize::MAX;
687 let mut winner_stream_cnt: u8 = 0;
688 let mut alt = enc.try_alternatives();
689 alt.with(|e| {
690 let ds = e.data().len();
691 let ms = e.meta().len();
692 winner_stream_cnt = encode_vec2_vertex_stream(&vertices, e, codecs)?;
693 winner_size = (e.data().len() - ds) + (e.meta().len() - ms);
694 Ok(())
695 })?;
696 alt.with(|e| {
697 let ds = e.data().len();
698 let ms = e.meta().len();
699 let cnt = encode_hilbert_vertex_streams(&vertices, e, codecs)?;
700 let size = (e.data().len() - ds) + (e.meta().len() - ms);
701 if size < winner_size {
702 winner_stream_cnt = cnt;
703 winner_size = size;
704 }
705 Ok(())
706 })?;
707 alt.with(|e| {
708 let ds = e.data().len();
709 let ms = e.meta().len();
710 let cnt = encode_morton_vertex_streams(&vertices, e, codecs)?;
711 let size = (e.data().len() - ds) + (e.meta().len() - ms);
712 if size < winner_size {
713 winner_stream_cnt = cnt;
714 }
715 Ok(())
716 })?;
717 drop(alt);
718 n += winner_stream_cnt;
719 } else {
720 n += encode_vec2_vertex_stream(&vertices, enc, codecs)?;
721 }
722
723 debug_assert!(n <= 127, "geometry stream count must fit in one byte");
725 enc.data_mut()[stream_count_pos] = n;
726 Ok(())
727 }
728}
729
730fn encode_morton_deltas<'a>(codes: &[u32], buffer: &'a mut Vec<u32>) -> &'a mut Vec<u32> {
731 buffer.clear();
732 if let Some(&first) = codes.first() {
733 buffer.reserve(codes.len());
734 buffer.extend(std::iter::once(first).chain(codes.windows(2).map(|w| w[1] - w[0])));
735 }
736 buffer
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 #[test]
744 fn test_build_morton_dict() {
745 let meta = Morton { bits: 4, shift: 0 };
746 let vertices = [1, 2, 3, 4, 1, 2, 0, 0];
748 let (dict, offsets) = build_morton_dict(&vertices, meta).unwrap();
749
750 assert!(
751 dict.windows(2).all(|w| w[0] < w[1]),
752 "dict not sorted/unique"
753 );
754 assert_eq!(offsets.len(), 4, "offsets length == number of vertex pairs");
755 assert_eq!(offsets[0], offsets[2], "duplicate (1,2) should share index");
756 assert!(offsets.iter().all(|&o| o.into_usize() < dict.len()));
757 }
758
759 #[test]
760 fn test_encode_root_length_stream() {
761 let types = vec![Polygon];
763 let offsets = vec![0, 1]; let lengths = encode_root_length_stream(&types, &offsets, Polygon);
766 assert_eq!(lengths, [] as [u32; 0]);
768
769 let types = vec![GeometryType::MultiPolygon];
771 let offsets = vec![0, 2]; let lengths = encode_root_length_stream(&types, &offsets, Polygon);
774 assert_eq!(lengths, vec![2]);
775 }
776}