1use geo::CoordsIter as _;
4use geo_types::{Coord, Geometry};
5
6use crate::codecs::hilbert::{hilbert_curve_params_from_bounds, hilbert_sort_key};
7use crate::codecs::morton::morton_sort_key;
8use crate::decoder::TileLayer;
9use crate::encoder::model::CurveParams;
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, strum::EnumIter, strum::EnumCount)]
17pub enum SortStrategy {
18 #[default]
22 Unsorted,
23
24 SpatialMorton,
31
32 SpatialHilbert,
36
37 Id,
39}
40
41impl TileLayer {
42 #[hotpath::measure]
52 pub fn sort(&mut self, strategy: SortStrategy, params: CurveParams) {
53 match strategy {
54 SortStrategy::SpatialMorton | SortStrategy::SpatialHilbert => {
55 let curve_key = if let SortStrategy::SpatialMorton = strategy {
56 morton_sort_key
57 } else {
58 hilbert_sort_key
59 };
60 self.features.sort_by_cached_key(|f| {
61 first_vertex(&f.geometry).map_or(u64::MAX, |c| u64::from(curve_key(c, params)))
62 });
63 }
64 SortStrategy::Id => {
65 self.features
66 .sort_by_cached_key(|f| f.id.map_or(0, |v| v.saturating_add(1)));
67 }
68 SortStrategy::Unsorted => {
69 }
71 }
72 }
73
74 #[hotpath::measure]
80 #[must_use]
81 pub fn curve_params(&self) -> CurveParams {
82 let (min_val, max_val) = self
83 .features
84 .iter()
85 .flat_map(|f| f.geometry.coords_iter())
86 .fold((i32::MAX, i32::MIN), |(min, max), c| {
87 (min.min(c.x).min(c.y), max.max(c.x).max(c.y))
88 });
89 hilbert_curve_params_from_bounds(min_val, max_val)
90 }
91}
92
93fn first_vertex(geom: &Geometry<i32>) -> Option<Coord<i32>> {
95 match geom {
96 Geometry::<i32>::Point(p) => Some(p.0),
97 Geometry::<i32>::Line(l) => Some(l.start),
98 Geometry::<i32>::LineString(ls) => ls.0.first().copied(),
99 Geometry::<i32>::Polygon(p) => p.exterior().0.first().copied(),
100 Geometry::<i32>::MultiPoint(mp) => mp.0.first().map(|p| p.0),
101 Geometry::<i32>::MultiLineString(mls) => mls.0.first().and_then(|ls| ls.0.first().copied()),
102 Geometry::<i32>::MultiPolygon(mp) => {
103 mp.0.first().and_then(|p| p.exterior().0.first().copied())
104 }
105 Geometry::<i32>::Triangle(t) => Some(t.v1()),
106 Geometry::<i32>::Rect(r) => Some(r.min()),
107 Geometry::<i32>::GeometryCollection(gc) => gc.0.first().and_then(first_vertex),
108 }
109}
110
111pub(crate) fn spatial_sort_likely_to_help(layer: &TileLayer) -> bool {
118 const SPATIAL_HELP_COVERAGE: f64 = 0.8;
119
120 let extent = f64::from(layer.extent);
121 if extent <= 0.0 || layer.features.is_empty() {
122 return true;
123 }
124
125 let (min_x, max_x, min_y, max_y) = layer
126 .features
127 .iter()
128 .filter_map(|f| first_vertex(&f.geometry))
129 .fold(
130 (i32::MAX, i32::MIN, i32::MAX, i32::MIN),
131 |(min_x, max_x, min_y, max_y), Coord::<i32> { x, y }| {
132 (min_x.min(x), max_x.max(x), min_y.min(y), max_y.max(y))
133 },
134 );
135
136 if min_x > max_x || min_y > max_y {
137 return true;
138 }
139
140 let range_x = f64::from(max_x - min_x);
141 let range_y = f64::from(max_y - min_y);
142
143 let spread_x = range_x / extent;
144 let spread_y = range_y / extent;
145
146 !(spread_x > SPATIAL_HELP_COVERAGE && spread_y > SPATIAL_HELP_COVERAGE)
147}
148
149#[cfg(test)]
150mod tests {
151 use geo_types::{Coord, Geometry as GeoGeom, Geometry, LineString, Point, Polygon};
152
153 use crate::decoder::{GeometryType, GeometryValues, RawGeometry, TileFeature, TileLayer};
154 use crate::encoder::{Codecs, Encoder, ExplicitEncoder, IntEncoder, SortStrategy, stage_tile};
155 use crate::test_helpers::{assert_empty, dec, into_layer01, parser};
156 use crate::{Layer, LazyParsed};
157
158 fn pt(x: i32, y: i32) -> Geometry<i32> {
159 GeoGeom::Point(Point::new(x, y))
160 }
161
162 fn ls(coords: &[(i32, i32)]) -> Geometry<i32> {
163 GeoGeom::LineString(LineString::new(
164 coords.iter().map(|&(x, y)| Coord { x, y }).collect(),
165 ))
166 }
167
168 fn poly_square(x0: i32, y0: i32, side: i32) -> Geometry<i32> {
169 let ring = LineString::new(vec![
170 Coord { x: x0, y: y0 },
171 Coord {
172 x: x0 + side,
173 y: y0,
174 },
175 Coord {
176 x: x0 + side,
177 y: y0 + side,
178 },
179 Coord {
180 x: x0,
181 y: y0 + side,
182 },
183 Coord { x: x0, y: y0 },
184 ]);
185 GeoGeom::Polygon(Polygon::new(ring, vec![]))
186 }
187
188 fn roundtrip_geom(decoded: &GeometryValues) -> GeometryValues {
190 let mut enc = Encoder::default();
191 let mut codecs = Codecs::default();
192 decoded
193 .clone()
194 .write_to(&mut enc, &mut codecs)
195 .expect("encode failed");
196 let buf = enc.data;
197
198 let parsed = assert_empty(RawGeometry::from_bytes(&buf, &mut parser()));
199 let mut d = dec();
200 let result = LazyParsed::Raw(parsed)
201 .into_parsed(&mut d)
202 .expect("decode failed");
203 assert!(
204 d.consumed() > 0,
205 "decoder should consume bytes after decode"
206 );
207 result
208 }
209
210 fn canonical(geoms: &[Geometry<i32>]) -> GeometryValues {
212 let mut decoded = GeometryValues::default();
213 for g in geoms {
214 decoded.push_geom(g);
215 }
216 roundtrip_geom(&decoded)
217 }
218
219 fn layer_after_sort(geoms: &[Geometry<i32>], ids: &[u64], strategy: SortStrategy) -> TileLayer {
222 let features: Vec<TileFeature> = geoms
223 .iter()
224 .zip(ids.iter())
225 .map(|(g, &id)| TileFeature {
226 id: Some(id),
227 geometry: g.clone(),
228 properties: vec![],
229 })
230 .collect();
231
232 let mut layer = TileLayer {
233 name: "test".to_string(),
234 extent: 4096,
235 property_names: vec![],
236 features,
237 };
238
239 let params = layer.curve_params();
240 layer.sort(strategy, params);
241 layer
242 }
243
244 fn assert_sort_roundtrip(
246 geoms: &[Geometry<i32>],
247 ids: &[u64],
248 strategy: SortStrategy,
249 expected: &[Geometry<i32>],
250 ) {
251 let layer = layer_after_sort(geoms, ids, strategy);
252
253 let mut sorted_decoded = GeometryValues::default();
254 for f in &layer.features {
255 sorted_decoded.push_geom(&f.geometry);
256 }
257
258 let after_roundtrip = roundtrip_geom(&sorted_decoded);
259 let expected_canonical = canonical(expected);
260
261 assert_eq!(
262 after_roundtrip, expected_canonical,
263 "\nsorted geometry did not match expected after encode→decode round-trip\
264 \nvector_types after sort: {:?}\
265 \nvector_types expected: {:?}",
266 sorted_decoded.vector_types, expected_canonical.vector_types,
267 );
268 }
269
270 #[test]
273 fn pure_points_id_sort_roundtrip() {
274 assert_sort_roundtrip(
275 &[pt(0, 0), pt(1, 1), pt(2, 2)],
276 &[3, 2, 1],
277 SortStrategy::Id,
278 &[pt(2, 2), pt(1, 1), pt(0, 0)],
279 );
280 }
281
282 #[test]
285 fn pure_linestrings_id_sort_roundtrip() {
286 assert_sort_roundtrip(
287 &[ls(&[(0, 0), (0, 10)]), ls(&[(5, 5), (10, 10)])],
288 &[2, 1],
289 SortStrategy::Id,
290 &[ls(&[(5, 5), (10, 10)]), ls(&[(0, 0), (0, 10)])],
291 );
292 }
293
294 #[test]
297 fn point_line_point_id_sort_to_line_point_point_roundtrip() {
298 assert_sort_roundtrip(
299 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
300 &[3, 1, 2],
301 SortStrategy::Id,
302 &[ls(&[(1, 0), (1, 5)]), pt(5, 5), pt(0, 0)],
303 );
304 }
305
306 #[test]
307 fn point_line_point_id_sort_to_point_point_line_roundtrip() {
308 assert_sort_roundtrip(
309 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
310 &[1, 3, 2],
311 SortStrategy::Id,
312 &[pt(0, 0), pt(5, 5), ls(&[(1, 0), (1, 5)])],
313 );
314 }
315
316 #[test]
319 fn point_polygon_point_id_sort_roundtrip() {
320 assert_sort_roundtrip(
321 &[pt(0, 0), poly_square(10, 10, 5), pt(5, 5)],
322 &[2, 1, 3],
323 SortStrategy::Id,
324 &[poly_square(10, 10, 5), pt(0, 0), pt(5, 5)],
325 );
326 }
327
328 #[test]
331 fn point_line_point_morton_sort_roundtrip() {
332 assert_sort_roundtrip(
333 &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)],
334 &[1, 2, 3],
335 SortStrategy::SpatialMorton,
336 &[ls(&[(0, 0), (0, 5)]), pt(1, 0), pt(2, 0)],
337 );
338 }
339
340 #[test]
343 fn id_sort_already_sorted_is_identity_roundtrip() {
344 let geoms = &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)];
345 assert_sort_roundtrip(geoms, &[1, 2, 3], SortStrategy::Id, geoms);
346 }
347
348 #[test]
351 fn id_column_co_permuted_with_geometry() {
352 let layer = layer_after_sort(
353 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
354 &[3, 1, 2],
355 SortStrategy::Id,
356 );
357
358 let ids: Vec<Option<u64>> = layer.features.iter().map(|f| f.id).collect();
359 assert_eq!(ids, vec![Some(1u64), Some(2), Some(3)]);
360
361 let geom_types: Vec<&str> = layer
363 .features
364 .iter()
365 .map(|f| GeometryType::try_from(&f.geometry).unwrap().into())
366 .collect();
367 assert_eq!(geom_types, vec!["LineString", "Point", "Point"]);
368 }
369
370 fn build_tile_layer(geoms: &[Geometry<i32>], ids: &[Option<u64>]) -> TileLayer {
372 assert_eq!(geoms.len(), ids.len());
373 TileLayer {
374 name: "test".to_string(),
375 extent: 4096,
376 property_names: vec![],
377 features: geoms
378 .iter()
379 .zip(ids.iter())
380 .map(|(g, &id)| TileFeature {
381 id,
382 geometry: g.clone(),
383 properties: vec![],
384 })
385 .collect(),
386 }
387 }
388
389 fn sort_encode_decode(tile: TileLayer, sort: SortStrategy) -> TileLayer {
392 let enc_cfg = Encoder::default().cfg;
393 let enc = Encoder::with_explicit(enc_cfg, ExplicitEncoder::for_id(IntEncoder::varint()));
394 let mut codecs = Codecs::default();
395 let enc = stage_tile(tile, sort, false, enc_cfg.tessellate)
396 .encode_into(enc, &mut codecs)
397 .expect("encode failed");
398
399 let buf = enc.into_layer_bytes().expect("into_layer_bytes failed");
401
402 let mut p = parser();
403 let layer_back = assert_empty(Layer::from_bytes(&buf, &mut p));
404 assert!(p.reserved() > 0, "parser should reserve bytes after parse");
405
406 let layer01 = into_layer01(layer_back);
407
408 let mut d = dec();
409 let tile = layer01.into_tile(&mut d).expect("decode after sort failed");
410 assert!(
411 d.consumed() > 0,
412 "decoder should consume bytes after decode"
413 );
414 tile
415 }
416
417 fn vertices_from_source(source: &TileLayer) -> Vec<i32> {
419 let mut geom = GeometryValues::default();
420 for f in &source.features {
421 geom.push_geom(&f.geometry);
422 }
423 geom.vertices().unwrap_or_default().to_vec()
424 }
425
426 #[test]
427 fn test_shared_morton_shift() {
428 let tile = build_tile_layer(&[pt(0, -10), pt(-10, 0)], &[Some(1), Some(2)]);
435 let source = sort_encode_decode(tile, SortStrategy::SpatialMorton);
436
437 let verts = vertices_from_source(&source);
438 assert_eq!(verts, vec![0, -10, -10, 0]);
439 }
440
441 #[test]
442 fn test_id_sort_nulls_first() {
443 let tile = build_tile_layer(&[pt(2, 2), pt(1, 1), pt(0, 0)], &[Some(10), None, Some(5)]);
444 let source = sort_encode_decode(tile, SortStrategy::Id);
445
446 let ids: Vec<Option<u64>> = source.features.iter().map(|f| f.id).collect();
447 assert_eq!(ids, vec![None, Some(5), Some(10)]);
449
450 let verts = vertices_from_source(&source);
451 assert_eq!(verts, vec![1, 1, 0, 0, 2, 2]);
453 }
454
455 #[test]
456 fn test_mixed_geometry_morton_sort() {
457 let tile = build_tile_layer(
465 &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)],
466 &[Some(1), Some(2), Some(3)],
467 );
468 let source = sort_encode_decode(tile, SortStrategy::SpatialMorton);
469
470 let types: Vec<_> = source
471 .features
472 .iter()
473 .map(|f| GeometryType::try_from(&f.geometry).unwrap())
474 .collect();
475
476 assert_eq!(
477 types,
478 vec![
479 GeometryType::LineString,
480 GeometryType::Point,
481 GeometryType::Point
482 ]
483 );
484
485 let verts = vertices_from_source(&source);
486 assert_eq!(verts, vec![0, 0, 0, 5, 1, 0, 2, 0]);
488 }
489}