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 #[cfg(feature = "unstable-v2")]
231 m_values: vec![],
232 #[cfg(feature = "unstable-v2")]
233 nested: vec![],
234 })
235 .collect();
236
237 let mut layer = TileLayer::from_parts("test", 4096, vec![], features).unwrap();
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(TileFeature::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::from_parts(
374 "test",
375 4096,
376 vec![],
377 geoms
378 .iter()
379 .zip(ids.iter())
380 .map(|(g, &id)| TileFeature {
381 id,
382 geometry: g.clone(),
383 properties: vec![],
384 #[cfg(feature = "unstable-v2")]
385 #[cfg(feature = "unstable-v2")]
386 m_values: vec![],
387 #[cfg(feature = "unstable-v2")]
388 nested: vec![],
389 })
390 .collect(),
391 )
392 .unwrap()
393 }
394
395 fn sort_encode_decode(tile: TileLayer, sort: SortStrategy) -> TileLayer {
398 let enc_cfg = EncoderConfig::default();
399 let enc = Encoder::with_explicit(enc_cfg, ExplicitEncoder::for_id(IntEncoder::varint()));
400 let mut codecs = Codecs::default();
401 let enc = stage_tile(tile, sort, false, enc_cfg.tessellate())
402 .encode_into(enc, &mut codecs)
403 .expect("encode failed");
404
405 let buf = enc.into_layer_bytes().expect("into_layer_bytes failed");
407
408 let mut p = parser();
409 let layer_back = assert_empty(Layer::from_bytes(&buf, &mut p));
410 assert!(p.reserved() > 0, "parser should reserve bytes after parse");
411
412 let layer01 = into_layer01(layer_back);
413
414 let mut d = dec();
415 let tile = layer01.into_tile(&mut d).expect("decode after sort failed");
416 assert!(
417 d.consumed() > 0,
418 "decoder should consume bytes after decode"
419 );
420 tile
421 }
422
423 fn vertices_from_source(source: &TileLayer) -> Vec<i32> {
425 let mut geom = GeometryValues::default();
426 for f in source.features() {
427 geom.push_geom(f.geometry());
428 }
429 geom.vertices().unwrap_or_default().to_vec()
430 }
431
432 #[test]
433 fn test_shared_morton_shift() {
434 let tile = build_tile_layer(&[pt(0, -10), pt(-10, 0)], &[Some(1), Some(2)]);
441 let source = sort_encode_decode(tile, SortStrategy::SpatialMorton);
442
443 let verts = vertices_from_source(&source);
444 assert_eq!(verts, vec![0, -10, -10, 0]);
445 }
446
447 #[test]
448 fn test_id_sort_nulls_first() {
449 let tile = build_tile_layer(&[pt(2, 2), pt(1, 1), pt(0, 0)], &[Some(10), None, Some(5)]);
450 let source = sort_encode_decode(tile, SortStrategy::Id);
451
452 let ids: Vec<Option<u64>> = source.features().iter().map(TileFeature::id).collect();
453 assert_eq!(ids, vec![None, Some(5), Some(10)]);
455
456 let verts = vertices_from_source(&source);
457 assert_eq!(verts, vec![1, 1, 0, 0, 2, 2]);
459 }
460
461 #[test]
462 fn test_mixed_geometry_morton_sort() {
463 let tile = build_tile_layer(
471 &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)],
472 &[Some(1), Some(2), Some(3)],
473 );
474 let source = sort_encode_decode(tile, SortStrategy::SpatialMorton);
475
476 let types: Vec<_> = source
477 .features()
478 .iter()
479 .map(|f| GeometryType::try_from(f.geometry()).unwrap())
480 .collect();
481
482 assert_eq!(
483 types,
484 vec![
485 GeometryType::LineString,
486 GeometryType::Point,
487 GeometryType::Point
488 ]
489 );
490
491 let verts = vertices_from_source(&source);
492 assert_eq!(verts, vec![0, 0, 0, 5, 1, 0, 2, 0]);
494 }
495}