1use crate::codecs::hilbert::{hilbert_curve_params_from_bounds, hilbert_sort_key};
4use crate::codecs::morton::morton_sort_key;
5use crate::encoder::model::CurveParams;
6use crate::encoder::source::{LayerSource, Order};
7use crate::tile::TileLayer;
8
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, strum::EnumIter, strum::EnumCount)]
15pub enum SortStrategy {
16 #[default]
20 Unsorted,
21
22 SpatialMorton,
29
30 SpatialHilbert,
34
35 Id,
37}
38
39impl TileLayer {
40 #[hotpath::measure]
50 pub fn sort(&mut self, strategy: SortStrategy, params: CurveParams) {
51 if strategy == SortStrategy::Unsorted {
52 return;
53 }
54 let order = sort_order(self, strategy, params);
55 let mut features: Vec<_> = std::mem::take(&mut self.features)
56 .into_iter()
57 .map(Some)
58 .collect();
59 self.features = order
60 .iter()
61 .map(|f| features[f].take().expect("each feature appears once"))
62 .collect();
63 }
64
65 #[must_use]
69 pub fn curve_params(&self) -> CurveParams {
70 curve_params(self)
71 }
72}
73
74#[hotpath::measure]
76pub(crate) fn sort_order(
77 source: &impl LayerSource,
78 strategy: SortStrategy,
79 params: CurveParams,
80) -> Order {
81 let len = source.feature_count();
82 match strategy {
83 SortStrategy::Unsorted => Order::Stored(len),
84 SortStrategy::SpatialMorton | SortStrategy::SpatialHilbert => {
85 let curve_key = if strategy == SortStrategy::SpatialMorton {
86 morton_sort_key
87 } else {
88 hilbert_sort_key
89 };
90 Order::Permuted(permutation(len, |f| {
91 source
92 .first_coord(f)
93 .map_or(u64::MAX, |c| u64::from(curve_key(c, params)))
94 }))
95 }
96 SortStrategy::Id => Order::Permuted(permutation(len, |f| source.id(f))),
97 }
98}
99
100fn permutation<K: Ord>(len: usize, key: impl Fn(usize) -> K) -> Vec<u32> {
102 let mut keyed: Vec<(K, u32)> = (0..len)
103 .map(|f| (key(f), u32::try_from(f).expect("feature count fits in u32")))
104 .collect();
105 keyed.sort_unstable();
106 keyed.into_iter().map(|(_, f)| f).collect()
107}
108
109#[hotpath::measure]
111pub(crate) fn curve_params(source: &impl LayerSource) -> CurveParams {
112 let (min_val, max_val) = source.coords().fold((i32::MAX, i32::MIN), |(min, max), c| {
113 (min.min(c.x).min(c.y), max.max(c.x).max(c.y))
114 });
115 hilbert_curve_params_from_bounds(min_val, max_val)
116}
117
118#[cfg(test)]
119mod tests {
120 use geo_types::{
121 Coord, Geometry as GeoGeom, Geometry, GeometryCollection, Line, LineString,
122 MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, Rect, Triangle,
123 };
124 use rstest::rstest;
125
126 use crate::decoder::{GeometryType, GeometryValues, RawGeometry};
127 use crate::encoder::model::CurveParams;
128 use crate::encoder::{
129 Codecs, Encoder, EncoderConfig, ExplicitEncoder, IntEncoder, SortStrategy, stage_tile,
130 };
131 use crate::test_helpers::{assert_empty, dec, into_layer01, parser};
132 use crate::tile::{TileFeature, TileLayer};
133 use crate::{Layer, LazyParsed};
134
135 fn pt(x: i32, y: i32) -> Geometry<i32> {
136 GeoGeom::Point(Point::new(x, y))
137 }
138
139 fn ls(coords: &[(i32, i32)]) -> Geometry<i32> {
140 GeoGeom::LineString(LineString::new(
141 coords.iter().map(|&(x, y)| Coord { x, y }).collect(),
142 ))
143 }
144
145 fn poly_square(x0: i32, y0: i32, side: i32) -> Geometry<i32> {
146 let ring = LineString::new(vec![
147 Coord { x: x0, y: y0 },
148 Coord {
149 x: x0 + side,
150 y: y0,
151 },
152 Coord {
153 x: x0 + side,
154 y: y0 + side,
155 },
156 Coord {
157 x: x0,
158 y: y0 + side,
159 },
160 Coord { x: x0, y: y0 },
161 ]);
162 GeoGeom::Polygon(Polygon::new(ring, vec![]))
163 }
164
165 fn roundtrip_geom(decoded: &GeometryValues) -> GeometryValues {
167 let mut enc = Encoder::default();
168 let mut codecs = Codecs::default();
169 decoded
170 .clone()
171 .write_to(&mut enc, &mut codecs)
172 .expect("encode failed");
173 let buf = enc.data().to_vec();
174
175 let parsed = assert_empty(RawGeometry::from_bytes(&buf, &mut parser()));
176 let mut d = dec();
177 let result = LazyParsed::Raw(parsed)
178 .into_parsed(&mut d)
179 .expect("decode failed");
180 assert!(
181 d.consumed() > 0,
182 "decoder should consume bytes after decode"
183 );
184 result
185 }
186
187 fn canonical(geoms: &[Geometry<i32>]) -> GeometryValues {
189 let mut decoded = GeometryValues::default();
190 for g in geoms {
191 decoded.push_geom(g);
192 }
193 roundtrip_geom(&decoded)
194 }
195
196 fn layer_after_sort(geoms: &[Geometry<i32>], ids: &[u64], strategy: SortStrategy) -> TileLayer {
199 let features: Vec<TileFeature> = geoms
200 .iter()
201 .zip(ids.iter())
202 .map(|(g, &id)| TileFeature {
203 id: Some(id),
204 geometry: g.clone(),
205 properties: vec![],
206 #[cfg(feature = "unstable-v2")]
207 m_values: vec![],
208 #[cfg(feature = "unstable-v2")]
209 nested: vec![],
210 #[cfg(feature = "unstable-v2")]
211 z: Vec::new(),
212 })
213 .collect();
214
215 let mut layer = TileLayer::from_parts("test", 4096, vec![], features).unwrap();
216
217 let params = layer.curve_params();
218 layer.sort(strategy, params);
219 layer
220 }
221
222 fn assert_sort_roundtrip(
224 geoms: &[Geometry<i32>],
225 ids: &[u64],
226 strategy: SortStrategy,
227 expected: &[Geometry<i32>],
228 ) {
229 let layer = layer_after_sort(geoms, ids, strategy);
230
231 let mut sorted_decoded = GeometryValues::default();
232 for f in layer.features() {
233 sorted_decoded.push_geom(f.geometry());
234 }
235
236 let after_roundtrip = roundtrip_geom(&sorted_decoded);
237 let expected_canonical = canonical(expected);
238
239 assert_eq!(
240 after_roundtrip, expected_canonical,
241 "\nsorted geometry did not match expected after encode->decode round-trip\
242 \nvector_types after sort: {:?}\
243 \nvector_types expected: {:?}",
244 sorted_decoded.vector_types, expected_canonical.vector_types,
245 );
246 }
247
248 #[test]
251 fn pure_points_id_sort_roundtrip() {
252 assert_sort_roundtrip(
253 &[pt(0, 0), pt(1, 1), pt(2, 2)],
254 &[3, 2, 1],
255 SortStrategy::Id,
256 &[pt(2, 2), pt(1, 1), pt(0, 0)],
257 );
258 }
259
260 #[test]
263 fn pure_linestrings_id_sort_roundtrip() {
264 assert_sort_roundtrip(
265 &[ls(&[(0, 0), (0, 10)]), ls(&[(5, 5), (10, 10)])],
266 &[2, 1],
267 SortStrategy::Id,
268 &[ls(&[(5, 5), (10, 10)]), ls(&[(0, 0), (0, 10)])],
269 );
270 }
271
272 #[test]
275 fn point_line_point_id_sort_to_line_point_point_roundtrip() {
276 assert_sort_roundtrip(
277 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
278 &[3, 1, 2],
279 SortStrategy::Id,
280 &[ls(&[(1, 0), (1, 5)]), pt(5, 5), pt(0, 0)],
281 );
282 }
283
284 #[test]
285 fn point_line_point_id_sort_to_point_point_line_roundtrip() {
286 assert_sort_roundtrip(
287 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
288 &[1, 3, 2],
289 SortStrategy::Id,
290 &[pt(0, 0), pt(5, 5), ls(&[(1, 0), (1, 5)])],
291 );
292 }
293
294 #[test]
297 fn point_polygon_point_id_sort_roundtrip() {
298 assert_sort_roundtrip(
299 &[pt(0, 0), poly_square(10, 10, 5), pt(5, 5)],
300 &[2, 1, 3],
301 SortStrategy::Id,
302 &[poly_square(10, 10, 5), pt(0, 0), pt(5, 5)],
303 );
304 }
305
306 #[test]
309 fn point_line_point_morton_sort_roundtrip() {
310 assert_sort_roundtrip(
311 &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)],
312 &[1, 2, 3],
313 SortStrategy::SpatialMorton,
314 &[ls(&[(0, 0), (0, 5)]), pt(1, 0), pt(2, 0)],
315 );
316 }
317
318 #[test]
321 fn id_sort_already_sorted_is_identity_roundtrip() {
322 let geoms = &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)];
323 assert_sort_roundtrip(geoms, &[1, 2, 3], SortStrategy::Id, geoms);
324 }
325
326 #[test]
329 fn id_column_co_permuted_with_geometry() {
330 let layer = layer_after_sort(
331 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
332 &[3, 1, 2],
333 SortStrategy::Id,
334 );
335
336 let ids: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
337 assert_eq!(ids, vec![Some(1u64), Some(2), Some(3)]);
338
339 let geom_types: Vec<&str> = layer
341 .features()
342 .iter()
343 .map(|f| GeometryType::try_from(f.geometry()).unwrap().into())
344 .collect();
345 assert_eq!(geom_types, vec!["LineString", "Point", "Point"]);
346 }
347
348 fn build_tile_layer(geoms: &[Geometry<i32>], ids: &[Option<u64>]) -> TileLayer {
350 assert_eq!(geoms.len(), ids.len());
351 TileLayer::from_parts(
352 "test",
353 4096,
354 vec![],
355 geoms
356 .iter()
357 .zip(ids.iter())
358 .map(|(g, &id)| TileFeature {
359 id,
360 geometry: g.clone(),
361 properties: vec![],
362 #[cfg(feature = "unstable-v2")]
363 m_values: vec![],
364 #[cfg(feature = "unstable-v2")]
365 nested: vec![],
366 #[cfg(feature = "unstable-v2")]
367 z: Vec::new(),
368 })
369 .collect(),
370 )
371 .unwrap()
372 }
373
374 fn sort_encode_decode(tile: &TileLayer, sort: SortStrategy) -> TileLayer {
377 let enc_cfg = EncoderConfig::default();
378 let enc = Encoder::with_explicit(enc_cfg, ExplicitEncoder::for_id(IntEncoder::varint()));
379 let mut codecs = Codecs::default();
380 let enc = stage_tile(tile, sort, false, enc_cfg.tessellate())
381 .encode_into(enc, &mut codecs)
382 .expect("encode failed");
383
384 let buf = enc.into_layer_bytes().expect("into_layer_bytes failed");
386
387 let mut p = parser();
388 let layer_back = assert_empty(Layer::from_bytes(&buf, &mut p));
389 assert!(p.reserved() > 0, "parser should reserve bytes after parse");
390
391 let layer01 = into_layer01(layer_back);
392
393 let mut d = dec();
394 let tile = layer01.into_tile(&mut d).expect("decode after sort failed");
395 assert!(
396 d.consumed() > 0,
397 "decoder should consume bytes after decode"
398 );
399 tile
400 }
401
402 fn encode_decode(tile: &TileLayer, cfg: EncoderConfig) -> TileLayer {
403 let buf = tile.encode(cfg).expect("encode failed");
404 let layer = assert_empty(Layer::from_bytes(&buf, &mut parser()));
405 into_layer01(layer)
406 .into_tile(&mut dec())
407 .expect("decode failed")
408 }
409
410 fn scattered_points(count: u32) -> Vec<Geometry<i32>> {
411 (0..count)
412 .map(|i| {
413 let x = i32::try_from(i.wrapping_mul(2_654_435_761) % 4096).unwrap();
414 let y = i32::try_from(i.wrapping_mul(40_503) % 4096).unwrap();
415 pt(x, y)
416 })
417 .collect()
418 }
419
420 #[test]
421 fn default_config_keeps_the_source_order() {
422 let geoms = scattered_points(600);
423 let ids: Vec<Option<u64>> = (0..600).rev().map(Some).collect();
424 let decoded = encode_decode(&build_tile_layer(&geoms, &ids), EncoderConfig::default());
425
426 let decoded_ids: Vec<Option<u64>> =
427 decoded.features().iter().map(TileFeature::id).collect();
428 assert_eq!(decoded_ids, ids);
429 }
430
431 #[test]
432 fn hilbert_sort_reaches_a_layer_spread_over_the_whole_extent() {
433 let geoms = scattered_points(600);
434 let ids = vec![None; geoms.len()];
435 let mut expected = build_tile_layer(&geoms, &ids);
436 let params = expected.curve_params();
437 expected.sort(SortStrategy::SpatialHilbert, params);
438
439 let decoded = encode_decode(
440 &build_tile_layer(&geoms, &ids),
441 EncoderConfig::default().with_spatial_hilbert_sort(true),
442 );
443
444 assert_eq!(
445 vertices_from_source(&decoded),
446 vertices_from_source(&expected)
447 );
448 }
449
450 fn vertices_from_source(source: &TileLayer) -> Vec<i32> {
452 let mut geom = GeometryValues::default();
453 for f in source.features() {
454 geom.push_geom(f.geometry());
455 }
456 geom.vertices().unwrap_or_default().to_vec()
457 }
458
459 #[test]
460 fn test_shared_morton_shift() {
461 let tile = build_tile_layer(&[pt(0, -10), pt(-10, 0)], &[Some(1), Some(2)]);
468 let source = sort_encode_decode(&tile, SortStrategy::SpatialMorton);
469
470 let verts = vertices_from_source(&source);
471 assert_eq!(verts, vec![0, -10, -10, 0]);
472 }
473
474 #[test]
475 fn test_id_sort_nulls_first() {
476 let tile = build_tile_layer(&[pt(2, 2), pt(1, 1), pt(0, 0)], &[Some(10), None, Some(5)]);
477 let source = sort_encode_decode(&tile, SortStrategy::Id);
478
479 let ids: Vec<Option<u64>> = source.features().iter().map(TileFeature::id).collect();
480 assert_eq!(ids, vec![None, Some(5), Some(10)]);
482
483 let verts = vertices_from_source(&source);
484 assert_eq!(verts, vec![1, 1, 0, 0, 2, 2]);
486 }
487
488 #[test]
489 fn test_mixed_geometry_morton_sort() {
490 let tile = build_tile_layer(
498 &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)],
499 &[Some(1), Some(2), Some(3)],
500 );
501 let source = sort_encode_decode(&tile, SortStrategy::SpatialMorton);
502
503 let types: Vec<_> = source
504 .features()
505 .iter()
506 .map(|f| GeometryType::try_from(f.geometry()).unwrap())
507 .collect();
508
509 assert_eq!(
510 types,
511 vec![
512 GeometryType::LineString,
513 GeometryType::Point,
514 GeometryType::Point
515 ]
516 );
517
518 let verts = vertices_from_source(&source);
519 assert_eq!(verts, vec![0, 0, 0, 5, 1, 0, 2, 0]);
521 }
522
523 #[test]
524 fn id_sort_separates_the_two_largest_ids() {
525 let layer = layer_after_sort(
526 &[pt(0, 0), pt(1, 1)],
527 &[u64::MAX, u64::MAX - 1],
528 SortStrategy::Id,
529 );
530
531 let ids: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
532 assert_eq!(ids, vec![Some(u64::MAX - 1), Some(u64::MAX)]);
533 }
534
535 #[test]
536 fn id_sort_puts_the_missing_id_before_zero() {
537 let layer = build_tile_layer(&[pt(0, 0), pt(1, 1)], &[Some(0), None]);
538 let mut layer = layer;
539 layer.sort(SortStrategy::Id, CurveParams { shift: 0, bits: 1 });
540
541 let ids: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
542 assert_eq!(ids, vec![None, Some(0)]);
543 }
544
545 #[test]
546 fn spatial_sort_reads_the_first_vertex_of_every_geometry_kind() {
547 let at = |k: i32| Coord { x: k, y: k };
548 let geoms = [
549 GeoGeom::Rect(Rect::new(at(0), at(20))),
550 GeoGeom::Triangle(Triangle::new(at(1), at(11), at(21))),
551 GeoGeom::MultiPolygon(MultiPolygon(vec![Polygon::new(
552 LineString(vec![at(2), at(12), at(22), at(2)]),
553 vec![],
554 )])),
555 GeoGeom::MultiLineString(MultiLineString(vec![LineString(vec![at(3), at(13)])])),
556 GeoGeom::MultiPoint(MultiPoint(vec![Point::from(at(4)), Point::from(at(14))])),
557 GeoGeom::Polygon(Polygon::new(
558 LineString(vec![at(5), at(15), at(25), at(5)]),
559 vec![],
560 )),
561 GeoGeom::LineString(LineString(vec![at(6), at(16)])),
562 GeoGeom::Line(Line::new(at(7), at(17))),
563 GeoGeom::Point(Point::from(at(8))),
564 GeoGeom::GeometryCollection(GeometryCollection(vec![GeoGeom::Point(Point::from(at(
565 9,
566 )))])),
567 GeoGeom::LineString(LineString(vec![])),
568 ];
569 let ids: Vec<Option<u64>> = (0..u64::try_from(geoms.len()).unwrap()).map(Some).collect();
570
571 let mut layer = build_tile_layer(&geoms, &ids);
572 let params = layer.curve_params();
573 layer.sort(SortStrategy::SpatialMorton, params);
576
577 let sorted: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
578 assert_eq!(sorted, ids);
579 }
580
581 #[test]
582 fn curve_params_of_a_vertexless_layer_is_the_degenerate_grid() {
583 let layer = build_tile_layer(&[GeoGeom::LineString(LineString(vec![]))], &[None]);
584 assert_eq!(layer.curve_params(), CurveParams { shift: 0, bits: 1 });
585 }
586
587 #[rstest]
588 #[case::morton(SortStrategy::SpatialMorton, &[0, 1, 2, 3])]
589 #[case::hilbert(SortStrategy::SpatialHilbert, &[0, 2, 3, 1])]
590 fn spatial_sort_of_the_unit_square_follows_the_chosen_curve(
591 #[case] strategy: SortStrategy,
592 #[case] expected: &[u64],
593 ) {
594 let geoms = [pt(0, 0), pt(1, 0), pt(0, 1), pt(1, 1)];
595 let ids: Vec<Option<u64>> = (0..4).map(Some).collect();
596
597 let mut layer = build_tile_layer(&geoms, &ids);
598 let params = layer.curve_params();
599 assert_eq!(params, CurveParams { shift: 0, bits: 1 });
600 layer.sort(strategy, params);
601
602 let sorted: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
603 assert_eq!(
604 sorted,
605 expected.iter().copied().map(Some).collect::<Vec<_>>()
606 );
607 }
608
609 #[test]
610 fn unsorted_leaves_the_feature_order_alone() {
611 let geoms = [pt(9, 9), pt(0, 0), pt(5, 5)];
612 let ids = [Some(3u64), Some(1), Some(2)];
613
614 let mut layer = build_tile_layer(&geoms, &ids);
615 let params = layer.curve_params();
616 layer.sort(SortStrategy::Unsorted, params);
617
618 let sorted: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
619 assert_eq!(sorted, ids.to_vec());
620 }
621
622 #[test]
623 fn every_vertexless_geometry_kind_sorts_after_the_one_feature_with_a_vertex() {
624 let empty_ring = || Polygon::new(LineString(vec![]), vec![]);
625 let geoms = [
626 GeoGeom::LineString(LineString(vec![])),
627 GeoGeom::Polygon(empty_ring()),
628 GeoGeom::MultiPoint(MultiPoint(vec![])),
629 GeoGeom::MultiLineString(MultiLineString(vec![])),
630 GeoGeom::MultiLineString(MultiLineString(vec![LineString(vec![])])),
631 GeoGeom::MultiPolygon(MultiPolygon(vec![])),
632 GeoGeom::MultiPolygon(MultiPolygon(vec![empty_ring()])),
633 GeoGeom::GeometryCollection(GeometryCollection(vec![])),
634 GeoGeom::GeometryCollection(GeometryCollection(vec![GeoGeom::LineString(LineString(
635 vec![],
636 ))])),
637 pt(1, 1),
638 ];
639 let ids: Vec<Option<u64>> = (0..u64::try_from(geoms.len()).unwrap()).map(Some).collect();
640
641 let mut layer = build_tile_layer(&geoms, &ids);
642 let params = layer.curve_params();
643 layer.sort(SortStrategy::SpatialMorton, params);
644
645 let sorted: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
646 assert_eq!(
647 sorted,
648 vec![
649 Some(9),
650 Some(0),
651 Some(1),
652 Some(2),
653 Some(3),
654 Some(4),
655 Some(5),
656 Some(6),
657 Some(7),
658 Some(8)
659 ]
660 );
661 }
662}