1use crate::morton::MortonCode;
8use crate::wgs84::Wgs84;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum TileIdError {
13 InvalidLevel(u32),
15 InvalidMortonNumber {
17 morton: i64,
19 level: u32,
21 max: u64,
23 },
24 ValueTooSmall(i32),
26}
27
28impl std::fmt::Display for TileIdError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 TileIdError::InvalidLevel(level) => {
32 write!(f, "Invalid level {level} (must be 0-15)")
33 }
34 TileIdError::InvalidMortonNumber { morton, level, max } => write!(
35 f,
36 "Invalid morton number {morton} for level {level} (allowed: 0-{max})"
37 ),
38 TileIdError::ValueTooSmall(value) => write!(
39 f,
40 "Invalid PackedTileId({value}): value must be >= {} or negative for level 15",
41 1u32 << 16
42 ),
43 }
44 }
45}
46
47impl std::error::Error for TileIdError {}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct PackedTileId {
58 value: u32,
60}
61
62impl PackedTileId {
63 pub fn new(value: i32) -> Result<Self, TileIdError> {
69 Self::from_i64(value as i64)
70 }
71
72 pub fn from_value(value: i32) -> Result<Self, TileIdError> {
74 Self::new(value)
75 }
76
77 pub fn from_i64(value: i64) -> Result<Self, TileIdError> {
83 let stored: u32 = if value < 0 {
84 (value + (1i64 << 32)) as u32
86 } else if value >= (1i64 << 32) {
87 (value as u64 & 0xFFFF_FFFF) as u32
88 } else {
89 value as u32
90 };
91
92 let tile = PackedTileId { value: stored };
93 tile.validate()?;
94 Ok(tile)
95 }
96
97 pub fn value(&self) -> i32 {
102 self.value as i32
106 }
107
108 pub fn from_tile_index(morton_number: u32, level: u32) -> Result<Self, TileIdError> {
113 if level > 15 {
114 return Err(TileIdError::InvalidLevel(level));
115 }
116
117 let max_morton: u64 = (1u64 << (2 * level + 1)) - 1;
118 if (morton_number as u64) > max_morton {
119 return Err(TileIdError::InvalidMortonNumber {
120 morton: morton_number as i64,
121 level,
122 max: max_morton,
123 });
124 }
125
126 let value: u32 = morton_number + (1u32 << (16 + level));
127 PackedTileId::from_i64(value as i64)
128 }
129
130 pub fn from_tile_xy(x: u32, y: u32, level: u32) -> Result<Self, TileIdError> {
136 if level > 15 {
137 return Err(TileIdError::InvalidLevel(level));
138 }
139 let max_x = (1u32 << (level + 1)) - 1;
140 let max_y = (1u32 << level) - 1;
141 if x > max_x {
142 return Err(TileIdError::InvalidMortonNumber {
143 morton: x as i64,
144 level,
145 max: max_x as u64,
146 });
147 }
148 if y > max_y {
149 return Err(TileIdError::InvalidMortonNumber {
150 morton: y as i64,
151 level,
152 max: max_y as u64,
153 });
154 }
155 Self::from_tile_index(Self::interleave_coords(x, y, level), level)
156 }
157
158 pub fn from_nds_coordinates(x: i32, y: i32, level: u32) -> Result<Self, TileIdError> {
160 Self::from_morton_and_level(MortonCode::from_nds_coordinates(x, y), level)
161 }
162
163 pub fn from_wgs84(longitude: f64, latitude: f64, level: u32) -> Result<Self, TileIdError> {
165 let (x, y) = Wgs84::new(longitude, latitude).to_nds_coordinates();
166 Self::from_nds_coordinates(x, y, level)
167 }
168
169 pub fn from_morton_and_level(morton_code: MortonCode, level: u32) -> Result<Self, TileIdError> {
175 if level > 15 {
176 return Err(TileIdError::InvalidLevel(level));
177 }
178
179 let (x_coord, y_coord) = morton_code.to_nds_coordinates();
180
181 let x_coord: u64 = if x_coord < 0 {
183 (x_coord as i64 + (1i64 << 32)) as u64
184 } else {
185 x_coord as u64
186 };
187 let y_coord: u64 = if y_coord < 0 {
188 (y_coord as i64 + (1i64 << 31)) as u64
189 } else {
190 y_coord as u64
191 };
192
193 let n_level = 31 - level;
194 let n_x = (x_coord >> n_level) as i32;
195 let n_y = (y_coord >> n_level) as i32;
196
197 let temp = MortonCode::from_nds_coordinates(n_x, n_y);
198
199 let value: u64 = temp.value() + (1u64 << (16 + level));
200 PackedTileId::from_i64(value as i64)
201 }
202
203 pub fn level(&self) -> u32 {
205 let mut level = 0u32;
206 let mut tile_id = self.value >> 16;
207 while tile_id > 1 {
208 tile_id >>= 1;
209 level += 1;
210 }
211 level
212 }
213
214 pub fn size(&self) -> i64 {
216 1i64 << (31 - self.level())
217 }
218
219 pub fn dimensions_in_meters(&self) -> (f64, f64) {
223 let (center_x, center_y) = self.center();
224 let center_wgs = Wgs84::from_nds_coordinates(center_x as i32, center_y as i32);
225 let tile_size = self.size() as i32;
226 Wgs84::nds_distance_to_meters(tile_size, tile_size, center_wgs.lat)
227 }
228
229 pub fn center(&self) -> (i64, i64) {
231 let (x, y) = self.south_west_corner();
232 let half_size = self.size() / 2;
233 (x + half_size, y + half_size)
234 }
235
236 pub fn wgs84_from_nds_coordinates(x: i64, y: i64) -> (f64, f64) {
239 (
240 (x as f64) * 360.0 / 4294967296.0,
241 (y as f64) * 180.0 / 2147483648.0,
242 )
243 }
244
245 pub fn center_wgs84(&self) -> (f64, f64) {
247 let (x, y) = self.center();
248 Self::wgs84_from_nds_coordinates(x, y)
249 }
250
251 pub fn south_west_wgs84(&self) -> (f64, f64) {
253 let (x, y) = self.south_west_corner();
254 Self::wgs84_from_nds_coordinates(x, y)
255 }
256
257 pub fn north_east_wgs84(&self) -> (f64, f64) {
259 let (x, y) = self.north_east_corner();
260 Self::wgs84_from_nds_coordinates(x, y)
261 }
262
263 pub fn wgs84_size(&self) -> (f64, f64) {
265 let tile_size = self.size() as f64;
266 (
267 tile_size * 360.0 / 4294967296.0,
268 tile_size * 180.0 / 2147483648.0,
269 )
270 }
271
272 pub fn south_west_corner(&self) -> (i64, i64) {
274 let morton_number = self.morton_number() as u64;
275 let level = self.level();
276 let shift = 63 - (2 * level + 1);
277 let (x, y) = MortonCode::new(morton_number << shift).to_nds_coordinates();
278 (x as i64, y as i64)
279 }
280
281 pub fn north_east_corner(&self) -> (i64, i64) {
285 let (x, y) = self.south_west_corner();
286 let size = self.size();
287 (x + size, y + size)
288 }
289
290 pub fn morton_number(&self) -> u32 {
292 let tile_level = self.level();
293 self.value - (1u32 << (16 + tile_level))
294 }
295
296 pub fn x(&self) -> u32 {
298 Self::deinterleave_morton(self.morton_number(), self.level()).0
299 }
300
301 pub fn y(&self) -> u32 {
303 Self::deinterleave_morton(self.morton_number(), self.level()).1
304 }
305
306 fn validate(&self) -> Result<(), TileIdError> {
308 let min_packed_tile_id: u32 = 1 << 16;
309 if self.value < min_packed_tile_id {
310 return Err(TileIdError::ValueTooSmall(self.value()));
311 }
312
313 let tile_level = self.level();
314 let morton = self.morton_number();
315 let max_morton: u64 = (1u64 << (2 * tile_level + 1)) - 1;
316
317 if (morton as u64) > max_morton {
321 return Err(TileIdError::InvalidMortonNumber {
322 morton: morton as i64,
323 level: tile_level,
324 max: max_morton,
325 });
326 }
327
328 Ok(())
329 }
330
331 fn deinterleave_morton(morton: u32, level: u32) -> (u32, u32) {
335 let mut x = 0u32;
336 let mut y = 0u32;
337 for i in 0..level {
338 if morton & (1u32 << (2 * i)) != 0 {
339 x |= 1u32 << i;
340 }
341 if morton & (1u32 << (2 * i + 1)) != 0 {
342 y |= 1u32 << i;
343 }
344 }
345 if morton & (1u32 << (2 * level)) != 0 {
346 x |= 1u32 << level;
347 }
348 (x, y)
349 }
350
351 fn interleave_coords(x: u32, y: u32, level: u32) -> u32 {
355 let mut morton = 0u32;
356 for i in 0..level {
357 if x & (1u32 << i) != 0 {
358 morton |= 1u32 << (2 * i);
359 }
360 if y & (1u32 << i) != 0 {
361 morton |= 1u32 << (2 * i + 1);
362 }
363 }
364 if x & (1u32 << level) != 0 {
365 morton |= 1u32 << (2 * level);
366 }
367 morton
368 }
369
370 fn wrapped_offset(coordinate: u32, offset: i32, modulo: u32) -> u32 {
371 ((coordinate as i64 + offset as i64).rem_euclid(modulo as i64)) as u32
372 }
373
374 pub fn neighbour(&self, offset_x: i32, offset_y: i32) -> PackedTileId {
377 let level = self.level();
378 let morton = self.morton_number();
379 let (x, y) = Self::deinterleave_morton(morton, level);
380 let x = Self::wrapped_offset(x, offset_x, 1u32 << (level + 1));
381 let y = Self::wrapped_offset(y, offset_y, 1u32 << level);
382 let new_morton = Self::interleave_coords(x, y, level);
383 PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
384 }
385
386 pub fn west_neighbour(&self) -> PackedTileId {
388 self.neighbour(-1, 0)
389 }
390
391 pub fn east_neighbour(&self) -> PackedTileId {
393 self.neighbour(1, 0)
394 }
395
396 pub fn south_neighbour(&self) -> PackedTileId {
398 self.neighbour(0, -1)
399 }
400
401 pub fn north_neighbour(&self) -> PackedTileId {
403 self.neighbour(0, 1)
404 }
405}
406
407impl std::fmt::Display for PackedTileId {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 write!(f, "PackedTileId(value={})", self.value())
410 }
411}
412
413#[inline]
419fn floor_div(a: i64, b: i64) -> i64 {
420 a.div_euclid(b)
421}
422
423pub fn get_tile_ids_for_bounding_box(
429 sw_x: i64,
430 sw_y: i64,
431 ne_x: i64,
432 ne_y: i64,
433 level: u32,
434) -> Vec<PackedTileId> {
435 let mut tile_ids = Vec::new();
436
437 let tile_size: i64 = 1 << (31 - level);
438
439 let start_tile_x = floor_div(sw_x, tile_size);
442 let start_tile_y = floor_div(sw_y, tile_size);
443 let end_tile_x = floor_div(ne_x, tile_size);
444 let end_tile_y = floor_div(ne_y, tile_size);
445
446 let mut tile_y = start_tile_y;
447 while tile_y <= end_tile_y {
448 let mut tile_x = start_tile_x;
449 while tile_x <= end_tile_x {
450 let tile_sw_x = tile_x * tile_size;
451 let tile_sw_y = tile_y * tile_size;
452
453 let morton = MortonCode::from_nds_coordinates(tile_sw_x as i32, tile_sw_y as i32);
456 let tile_id = PackedTileId::from_morton_and_level(morton, level)
457 .expect("tile within bounding box must be valid");
458 tile_ids.push(tile_id);
459
460 tile_x += 1;
461 }
462 tile_y += 1;
463 }
464
465 tile_ids
466}
467
468pub fn bounding_box_from_tile_ids(tiles: &[PackedTileId]) -> Option<(i64, i64, i64, i64)> {
476 let (first, rest) = tiles.split_first()?;
477
478 let (mut min_x, mut min_y) = first.south_west_corner();
479 let (mut max_x, mut max_y) = first.north_east_corner();
480
481 for tile in rest {
482 let (sw_x, sw_y) = tile.south_west_corner();
483 let (ne_x, ne_y) = tile.north_east_corner();
484 min_x = min_x.min(sw_x);
485 min_y = min_y.min(sw_y);
486 max_x = max_x.max(ne_x);
487 max_y = max_y.max(ne_y);
488 }
489
490 Some((min_x, min_y, max_x - 1, max_y - 1))
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497
498 #[test]
499 fn level_15_is_negative() {
500 let t = PackedTileId::from_tile_index(0, 15).unwrap();
501 assert_eq!(t.value(), -2147483648);
502 assert_eq!(t.level(), 15);
503 assert_eq!(t.morton_number(), 0);
504 }
505
506 #[test]
507 fn signed_unsigned_constructors_agree() {
508 let a = PackedTileId::from_i64(-2147483648).unwrap();
509 let b = PackedTileId::from_i64(2147483648).unwrap();
510 assert_eq!(a, b);
511 assert_eq!(a.value(), -2147483648);
512 }
513
514 #[test]
515 fn max_level15_tile() {
516 let t = PackedTileId::from_tile_index((1 << 31) - 1, 15).unwrap();
517 assert_eq!(t.value(), -1);
518 }
519
520 #[test]
521 fn level14_positive() {
522 let t = PackedTileId::from_tile_index(0, 14).unwrap();
523 assert_eq!(t.value(), 1073741824);
524 }
525
526 #[test]
527 fn invalid_level() {
528 assert!(matches!(
529 PackedTileId::from_tile_index(0, 16),
530 Err(TileIdError::InvalidLevel(16))
531 ));
532 }
533
534 #[test]
535 fn invalid_morton() {
536 assert!(PackedTileId::from_tile_index(2, 0).is_err());
538 }
539
540 #[test]
541 fn corners_and_center() {
542 let t = PackedTileId::from_tile_index(0, 0).unwrap();
543 assert_eq!(t.size(), 2147483648);
544 assert_eq!(t.south_west_corner(), (0, 0));
545 assert_eq!(t.north_east_corner(), (2147483648, 2147483648));
546 assert_eq!(t.center(), (1073741824, 1073741824));
547 }
548
549 #[test]
550 fn bbox_floor_div_negative() {
551 let tiles = get_tile_ids_for_bounding_box(-1, -1, 0, 0, 1);
553 assert!(!tiles.is_empty());
554 }
555
556 #[test]
557 fn bbox_from_single_tile() {
558 let t = PackedTileId::from_i64(131072).unwrap();
559 let bbox = bounding_box_from_tile_ids(&[t]).unwrap();
560 assert_eq!(bbox, (0, 0, 1073741823, 1073741823));
561 }
562
563 #[test]
564 fn empty_bbox_returns_none() {
565 assert!(bounding_box_from_tile_ids(&[]).is_none());
566 }
567}