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::encoder::model::CurveParams;
9use crate::tile::TileLayer;
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_mut().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_mut()
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]
78 #[must_use]
79 pub fn curve_params(&self) -> CurveParams {
80 let (min_val, max_val) = self
81 .features()
82 .iter()
83 .flat_map(|f| f.geometry().coords_iter())
84 .fold((i32::MAX, i32::MIN), |(min, max), c| {
85 (min.min(c.x).min(c.y), max.max(c.x).max(c.y))
86 });
87 hilbert_curve_params_from_bounds(min_val, max_val)
88 }
89}
90
91fn first_vertex(geom: &Geometry<i32>) -> Option<Coord<i32>> {
93 match geom {
94 Geometry::<i32>::Point(p) => Some(p.0),
95 Geometry::<i32>::Line(l) => Some(l.start),
96 Geometry::<i32>::LineString(ls) => ls.0.first().copied(),
97 Geometry::<i32>::Polygon(p) => p.exterior().0.first().copied(),
98 Geometry::<i32>::MultiPoint(mp) => mp.0.first().map(|p| p.0),
99 Geometry::<i32>::MultiLineString(mls) => mls.0.first().and_then(|ls| ls.0.first().copied()),
100 Geometry::<i32>::MultiPolygon(mp) => {
101 mp.0.first().and_then(|p| p.exterior().0.first().copied())
102 }
103 Geometry::<i32>::Triangle(t) => Some(t.v1()),
104 Geometry::<i32>::Rect(r) => Some(r.min()),
105 Geometry::<i32>::GeometryCollection(gc) => gc.0.first().and_then(first_vertex),
106 }
107}
108
109pub(crate) fn spatial_sort_likely_to_help(layer: &TileLayer) -> bool {
116 const SPATIAL_HELP_COVERAGE: f64 = 0.8;
117
118 let extent = f64::from(layer.extent().get());
119 if extent <= 0.0 || layer.features().is_empty() {
120 return true;
121 }
122
123 let (min_x, max_x, min_y, max_y) = layer
124 .features()
125 .iter()
126 .filter_map(|f| first_vertex(f.geometry()))
127 .fold(
128 (i32::MAX, i32::MIN, i32::MAX, i32::MIN),
129 |(min_x, max_x, min_y, max_y), Coord::<i32> { x, y }| {
130 (min_x.min(x), max_x.max(x), min_y.min(y), max_y.max(y))
131 },
132 );
133
134 if min_x > max_x || min_y > max_y {
135 return true;
136 }
137
138 let range_x = f64::from(max_x - min_x);
139 let range_y = f64::from(max_y - min_y);
140
141 let spread_x = range_x / extent;
142 let spread_y = range_y / extent;
143
144 !(spread_x > SPATIAL_HELP_COVERAGE && spread_y > SPATIAL_HELP_COVERAGE)
145}
146
147#[cfg(test)]
148mod tests {
149 use geo_types::{Coord, Geometry as GeoGeom, Geometry, LineString, Point, Polygon};
150
151 use crate::decoder::{GeometryType, GeometryValues, RawGeometry};
152 use crate::encoder::{
153 Codecs, Encoder, EncoderConfig, ExplicitEncoder, IntEncoder, SortStrategy, stage_tile,
154 };
155 use crate::test_helpers::{assert_empty, dec, into_layer01, parser};
156 use crate::tile::{TileFeature, TileLayer};
157 use crate::{Layer, LazyParsed};
158
159 fn pt(x: i32, y: i32) -> Geometry<i32> {
160 GeoGeom::Point(Point::new(x, y))
161 }
162
163 fn ls(coords: &[(i32, i32)]) -> Geometry<i32> {
164 GeoGeom::LineString(LineString::new(
165 coords.iter().map(|&(x, y)| Coord { x, y }).collect(),
166 ))
167 }
168
169 fn poly_square(x0: i32, y0: i32, side: i32) -> Geometry<i32> {
170 let ring = LineString::new(vec![
171 Coord { x: x0, y: y0 },
172 Coord {
173 x: x0 + side,
174 y: y0,
175 },
176 Coord {
177 x: x0 + side,
178 y: y0 + side,
179 },
180 Coord {
181 x: x0,
182 y: y0 + side,
183 },
184 Coord { x: x0, y: y0 },
185 ]);
186 GeoGeom::Polygon(Polygon::new(ring, vec![]))
187 }
188
189 fn roundtrip_geom(decoded: &GeometryValues) -> GeometryValues {
191 let mut enc = Encoder::default();
192 let mut codecs = Codecs::default();
193 decoded
194 .clone()
195 .write_to(&mut enc, &mut codecs)
196 .expect("encode failed");
197 let buf = enc.data().to_vec();
198
199 let parsed = assert_empty(RawGeometry::from_bytes(&buf, &mut parser()));
200 let mut d = dec();
201 let result = LazyParsed::Raw(parsed)
202 .into_parsed(&mut d)
203 .expect("decode failed");
204 assert!(
205 d.consumed() > 0,
206 "decoder should consume bytes after decode"
207 );
208 result
209 }
210
211 fn canonical(geoms: &[Geometry<i32>]) -> GeometryValues {
213 let mut decoded = GeometryValues::default();
214 for g in geoms {
215 decoded.push_geom(g);
216 }
217 roundtrip_geom(&decoded)
218 }
219
220 fn layer_after_sort(geoms: &[Geometry<i32>], ids: &[u64], strategy: SortStrategy) -> TileLayer {
223 let features: Vec<TileFeature> = geoms
224 .iter()
225 .zip(ids.iter())
226 .map(|(g, &id)| TileFeature {
227 id: Some(id),
228 geometry: g.clone(),
229 properties: vec![],
230 })
231 .collect();
232
233 let mut layer = TileLayer::from_parts("test", 4096, vec![], features).unwrap();
234
235 let params = layer.curve_params();
236 layer.sort(strategy, params);
237 layer
238 }
239
240 fn assert_sort_roundtrip(
242 geoms: &[Geometry<i32>],
243 ids: &[u64],
244 strategy: SortStrategy,
245 expected: &[Geometry<i32>],
246 ) {
247 let layer = layer_after_sort(geoms, ids, strategy);
248
249 let mut sorted_decoded = GeometryValues::default();
250 for f in layer.features() {
251 sorted_decoded.push_geom(f.geometry());
252 }
253
254 let after_roundtrip = roundtrip_geom(&sorted_decoded);
255 let expected_canonical = canonical(expected);
256
257 assert_eq!(
258 after_roundtrip, expected_canonical,
259 "\nsorted geometry did not match expected after encode->decode round-trip\
260 \nvector_types after sort: {:?}\
261 \nvector_types expected: {:?}",
262 sorted_decoded.vector_types, expected_canonical.vector_types,
263 );
264 }
265
266 #[test]
269 fn pure_points_id_sort_roundtrip() {
270 assert_sort_roundtrip(
271 &[pt(0, 0), pt(1, 1), pt(2, 2)],
272 &[3, 2, 1],
273 SortStrategy::Id,
274 &[pt(2, 2), pt(1, 1), pt(0, 0)],
275 );
276 }
277
278 #[test]
281 fn pure_linestrings_id_sort_roundtrip() {
282 assert_sort_roundtrip(
283 &[ls(&[(0, 0), (0, 10)]), ls(&[(5, 5), (10, 10)])],
284 &[2, 1],
285 SortStrategy::Id,
286 &[ls(&[(5, 5), (10, 10)]), ls(&[(0, 0), (0, 10)])],
287 );
288 }
289
290 #[test]
293 fn point_line_point_id_sort_to_line_point_point_roundtrip() {
294 assert_sort_roundtrip(
295 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
296 &[3, 1, 2],
297 SortStrategy::Id,
298 &[ls(&[(1, 0), (1, 5)]), pt(5, 5), pt(0, 0)],
299 );
300 }
301
302 #[test]
303 fn point_line_point_id_sort_to_point_point_line_roundtrip() {
304 assert_sort_roundtrip(
305 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
306 &[1, 3, 2],
307 SortStrategy::Id,
308 &[pt(0, 0), pt(5, 5), ls(&[(1, 0), (1, 5)])],
309 );
310 }
311
312 #[test]
315 fn point_polygon_point_id_sort_roundtrip() {
316 assert_sort_roundtrip(
317 &[pt(0, 0), poly_square(10, 10, 5), pt(5, 5)],
318 &[2, 1, 3],
319 SortStrategy::Id,
320 &[poly_square(10, 10, 5), pt(0, 0), pt(5, 5)],
321 );
322 }
323
324 #[test]
327 fn point_line_point_morton_sort_roundtrip() {
328 assert_sort_roundtrip(
329 &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)],
330 &[1, 2, 3],
331 SortStrategy::SpatialMorton,
332 &[ls(&[(0, 0), (0, 5)]), pt(1, 0), pt(2, 0)],
333 );
334 }
335
336 #[test]
339 fn id_sort_already_sorted_is_identity_roundtrip() {
340 let geoms = &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)];
341 assert_sort_roundtrip(geoms, &[1, 2, 3], SortStrategy::Id, geoms);
342 }
343
344 #[test]
347 fn id_column_co_permuted_with_geometry() {
348 let layer = layer_after_sort(
349 &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)],
350 &[3, 1, 2],
351 SortStrategy::Id,
352 );
353
354 let ids: Vec<Option<u64>> = layer.features().iter().map(TileFeature::id).collect();
355 assert_eq!(ids, vec![Some(1u64), Some(2), Some(3)]);
356
357 let geom_types: Vec<&str> = layer
359 .features()
360 .iter()
361 .map(|f| GeometryType::try_from(f.geometry()).unwrap().into())
362 .collect();
363 assert_eq!(geom_types, vec!["LineString", "Point", "Point"]);
364 }
365
366 fn build_tile_layer(geoms: &[Geometry<i32>], ids: &[Option<u64>]) -> TileLayer {
368 assert_eq!(geoms.len(), ids.len());
369 TileLayer::from_parts(
370 "test",
371 4096,
372 vec![],
373 geoms
374 .iter()
375 .zip(ids.iter())
376 .map(|(g, &id)| TileFeature {
377 id,
378 geometry: g.clone(),
379 properties: vec![],
380 })
381 .collect(),
382 )
383 .unwrap()
384 }
385
386 fn sort_encode_decode(tile: TileLayer, sort: SortStrategy) -> TileLayer {
389 let enc_cfg = EncoderConfig::default();
390 let enc = Encoder::with_explicit(enc_cfg, ExplicitEncoder::for_id(IntEncoder::varint()));
391 let mut codecs = Codecs::default();
392 let enc = stage_tile(tile, sort, false, enc_cfg.tessellate())
393 .encode_into(enc, &mut codecs)
394 .expect("encode failed");
395
396 let buf = enc.into_layer_bytes().expect("into_layer_bytes failed");
398
399 let mut p = parser();
400 let layer_back = assert_empty(Layer::from_bytes(&buf, &mut p));
401 assert!(p.reserved() > 0, "parser should reserve bytes after parse");
402
403 let layer01 = into_layer01(layer_back);
404
405 let mut d = dec();
406 let tile = layer01.into_tile(&mut d).expect("decode after sort failed");
407 assert!(
408 d.consumed() > 0,
409 "decoder should consume bytes after decode"
410 );
411 tile
412 }
413
414 fn vertices_from_source(source: &TileLayer) -> Vec<i32> {
416 let mut geom = GeometryValues::default();
417 for f in source.features() {
418 geom.push_geom(f.geometry());
419 }
420 geom.vertices().unwrap_or_default().to_vec()
421 }
422
423 #[test]
424 fn test_shared_morton_shift() {
425 let tile = build_tile_layer(&[pt(0, -10), pt(-10, 0)], &[Some(1), Some(2)]);
432 let source = sort_encode_decode(tile, SortStrategy::SpatialMorton);
433
434 let verts = vertices_from_source(&source);
435 assert_eq!(verts, vec![0, -10, -10, 0]);
436 }
437
438 #[test]
439 fn test_id_sort_nulls_first() {
440 let tile = build_tile_layer(&[pt(2, 2), pt(1, 1), pt(0, 0)], &[Some(10), None, Some(5)]);
441 let source = sort_encode_decode(tile, SortStrategy::Id);
442
443 let ids: Vec<Option<u64>> = source.features().iter().map(TileFeature::id).collect();
444 assert_eq!(ids, vec![None, Some(5), Some(10)]);
446
447 let verts = vertices_from_source(&source);
448 assert_eq!(verts, vec![1, 1, 0, 0, 2, 2]);
450 }
451
452 #[test]
453 fn test_mixed_geometry_morton_sort() {
454 let tile = build_tile_layer(
462 &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)],
463 &[Some(1), Some(2), Some(3)],
464 );
465 let source = sort_encode_decode(tile, SortStrategy::SpatialMorton);
466
467 let types: Vec<_> = source
468 .features()
469 .iter()
470 .map(|f| GeometryType::try_from(f.geometry()).unwrap())
471 .collect();
472
473 assert_eq!(
474 types,
475 vec![
476 GeometryType::LineString,
477 GeometryType::Point,
478 GeometryType::Point
479 ]
480 );
481
482 let verts = vertices_from_source(&source);
483 assert_eq!(verts, vec![0, 0, 0, 5, 1, 0, 2, 0]);
485 }
486}