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_i64(value: i64) -> Result<Self, TileIdError> {
78 let stored: u32 = if value < 0 {
79 (value + (1i64 << 32)) as u32
81 } else if value >= (1i64 << 32) {
82 (value as u64 & 0xFFFF_FFFF) as u32
83 } else {
84 value as u32
85 };
86
87 let tile = PackedTileId { value: stored };
88 tile.validate()?;
89 Ok(tile)
90 }
91
92 pub fn value(&self) -> i32 {
97 self.value as i32
101 }
102
103 pub fn from_tile_index(morton_number: u32, level: u32) -> Result<Self, TileIdError> {
108 if level > 15 {
109 return Err(TileIdError::InvalidLevel(level));
110 }
111
112 let max_morton: u64 = (1u64 << (2 * level + 1)) - 1;
113 if (morton_number as u64) > max_morton {
114 return Err(TileIdError::InvalidMortonNumber {
115 morton: morton_number as i64,
116 level,
117 max: max_morton,
118 });
119 }
120
121 let value: u32 = morton_number + (1u32 << (16 + level));
122 PackedTileId::from_i64(value as i64)
123 }
124
125 pub fn from_morton_and_level(morton_code: MortonCode, level: u32) -> Result<Self, TileIdError> {
131 if level > 15 {
132 return Err(TileIdError::InvalidLevel(level));
133 }
134
135 let (x_coord, y_coord) = morton_code.to_nds_coordinates();
136
137 let x_coord: u64 = if x_coord < 0 {
139 (x_coord as i64 + (1i64 << 32)) as u64
140 } else {
141 x_coord as u64
142 };
143 let y_coord: u64 = if y_coord < 0 {
144 (y_coord as i64 + (1i64 << 31)) as u64
145 } else {
146 y_coord as u64
147 };
148
149 let n_level = 31 - level;
150 let n_x = (x_coord >> n_level) as i32;
151 let n_y = (y_coord >> n_level) as i32;
152
153 let temp = MortonCode::from_nds_coordinates(n_x, n_y);
154
155 let value: u64 = temp.value() + (1u64 << (16 + level));
156 PackedTileId::from_i64(value as i64)
157 }
158
159 pub fn level(&self) -> u32 {
161 let mut level = 0u32;
162 let mut tile_id = self.value >> 16;
163 while tile_id > 1 {
164 tile_id >>= 1;
165 level += 1;
166 }
167 level
168 }
169
170 pub fn size(&self) -> i64 {
172 1i64 << (31 - self.level())
173 }
174
175 pub fn dimensions_in_meters(&self) -> (f64, f64) {
179 let (center_x, center_y) = self.center();
180 let center_wgs = Wgs84::from_nds_coordinates(center_x as i32, center_y as i32);
181 let tile_size = self.size() as i32;
182 Wgs84::nds_distance_to_meters(tile_size, tile_size, center_wgs.lat)
183 }
184
185 pub fn center(&self) -> (i64, i64) {
187 let (x, y) = self.south_west_corner();
188 let half_size = self.size() / 2;
189 (x + half_size, y + half_size)
190 }
191
192 pub fn south_west_corner(&self) -> (i64, i64) {
194 let morton_number = self.morton_number() as u64;
195 let level = self.level();
196 let shift = 63 - (2 * level + 1);
197 let (x, y) = MortonCode::new(morton_number << shift).to_nds_coordinates();
198 (x as i64, y as i64)
199 }
200
201 pub fn north_east_corner(&self) -> (i64, i64) {
205 let (x, y) = self.south_west_corner();
206 let size = self.size();
207 (x + size, y + size)
208 }
209
210 pub fn morton_number(&self) -> u32 {
212 let tile_level = self.level();
213 self.value - (1u32 << (16 + tile_level))
214 }
215
216 fn validate(&self) -> Result<(), TileIdError> {
218 let min_packed_tile_id: u32 = 1 << 16;
219 if self.value < min_packed_tile_id {
220 return Err(TileIdError::ValueTooSmall(self.value()));
221 }
222
223 let tile_level = self.level();
224 let morton = self.morton_number();
225 let max_morton: u64 = (1u64 << (2 * tile_level + 1)) - 1;
226
227 if (morton as u64) > max_morton {
231 return Err(TileIdError::InvalidMortonNumber {
232 morton: morton as i64,
233 level: tile_level,
234 max: max_morton,
235 });
236 }
237
238 Ok(())
239 }
240
241 fn deinterleave_morton(morton: u32, level: u32) -> (u32, u32) {
245 let mut x = 0u32;
246 let mut y = 0u32;
247 for i in 0..level {
248 if morton & (1u32 << (2 * i)) != 0 {
249 x |= 1u32 << i;
250 }
251 if morton & (1u32 << (2 * i + 1)) != 0 {
252 y |= 1u32 << i;
253 }
254 }
255 if morton & (1u32 << (2 * level)) != 0 {
256 x |= 1u32 << level;
257 }
258 (x, y)
259 }
260
261 fn interleave_coords(x: u32, y: u32, level: u32) -> u32 {
265 let mut morton = 0u32;
266 for i in 0..level {
267 if x & (1u32 << i) != 0 {
268 morton |= 1u32 << (2 * i);
269 }
270 if y & (1u32 << i) != 0 {
271 morton |= 1u32 << (2 * i + 1);
272 }
273 }
274 if x & (1u32 << level) != 0 {
275 morton |= 1u32 << (2 * level);
276 }
277 morton
278 }
279
280 pub fn west_neighbour(&self) -> PackedTileId {
282 let level = self.level();
283 let morton = self.morton_number();
284 let (mut x, y) = Self::deinterleave_morton(morton, level);
285 let max_x = (1u32 << (level + 1)) - 1;
286 x = x.wrapping_sub(1) & max_x;
287 let new_morton = Self::interleave_coords(x, y, level);
288 PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
289 }
290
291 pub fn east_neighbour(&self) -> PackedTileId {
293 let level = self.level();
294 let morton = self.morton_number();
295 let (mut x, y) = Self::deinterleave_morton(morton, level);
296 let max_x = (1u32 << (level + 1)) - 1;
297 x = x.wrapping_add(1) & max_x;
298 let new_morton = Self::interleave_coords(x, y, level);
299 PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
300 }
301
302 pub fn south_neighbour(&self) -> PackedTileId {
304 let level = self.level();
305 let morton = self.morton_number();
306 let (x, mut y) = Self::deinterleave_morton(morton, level);
307 let max_y = (1u32 << level) - 1;
308 y = y.wrapping_sub(1) & max_y;
309 let new_morton = Self::interleave_coords(x, y, level);
310 PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
311 }
312
313 pub fn north_neighbour(&self) -> PackedTileId {
315 let level = self.level();
316 let morton = self.morton_number();
317 let (x, mut y) = Self::deinterleave_morton(morton, level);
318 let max_y = (1u32 << level) - 1;
319 y = y.wrapping_add(1) & max_y;
320 let new_morton = Self::interleave_coords(x, y, level);
321 PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
322 }
323}
324
325impl std::fmt::Display for PackedTileId {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 write!(f, "PackedTileId(value={})", self.value())
328 }
329}
330
331#[inline]
337fn floor_div(a: i64, b: i64) -> i64 {
338 a.div_euclid(b)
339}
340
341pub fn get_tile_ids_for_bounding_box(
347 sw_x: i64,
348 sw_y: i64,
349 ne_x: i64,
350 ne_y: i64,
351 level: u32,
352) -> Vec<PackedTileId> {
353 let mut tile_ids = Vec::new();
354
355 let tile_size: i64 = 1 << (31 - level);
356
357 let start_tile_x = floor_div(sw_x, tile_size);
360 let start_tile_y = floor_div(sw_y, tile_size);
361 let end_tile_x = floor_div(ne_x, tile_size);
362 let end_tile_y = floor_div(ne_y, tile_size);
363
364 let mut tile_y = start_tile_y;
365 while tile_y <= end_tile_y {
366 let mut tile_x = start_tile_x;
367 while tile_x <= end_tile_x {
368 let tile_sw_x = tile_x * tile_size;
369 let tile_sw_y = tile_y * tile_size;
370
371 let morton = MortonCode::from_nds_coordinates(tile_sw_x as i32, tile_sw_y as i32);
374 let tile_id = PackedTileId::from_morton_and_level(morton, level)
375 .expect("tile within bounding box must be valid");
376 tile_ids.push(tile_id);
377
378 tile_x += 1;
379 }
380 tile_y += 1;
381 }
382
383 tile_ids
384}
385
386pub fn bounding_box_from_tile_ids(tiles: &[PackedTileId]) -> Option<(i64, i64, i64, i64)> {
394 let (first, rest) = tiles.split_first()?;
395
396 let (mut min_x, mut min_y) = first.south_west_corner();
397 let (mut max_x, mut max_y) = first.north_east_corner();
398
399 for tile in rest {
400 let (sw_x, sw_y) = tile.south_west_corner();
401 let (ne_x, ne_y) = tile.north_east_corner();
402 min_x = min_x.min(sw_x);
403 min_y = min_y.min(sw_y);
404 max_x = max_x.max(ne_x);
405 max_y = max_y.max(ne_y);
406 }
407
408 Some((min_x, min_y, max_x - 1, max_y - 1))
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn level_15_is_negative() {
418 let t = PackedTileId::from_tile_index(0, 15).unwrap();
419 assert_eq!(t.value(), -2147483648);
420 assert_eq!(t.level(), 15);
421 assert_eq!(t.morton_number(), 0);
422 }
423
424 #[test]
425 fn signed_unsigned_constructors_agree() {
426 let a = PackedTileId::from_i64(-2147483648).unwrap();
427 let b = PackedTileId::from_i64(2147483648).unwrap();
428 assert_eq!(a, b);
429 assert_eq!(a.value(), -2147483648);
430 }
431
432 #[test]
433 fn max_level15_tile() {
434 let t = PackedTileId::from_tile_index((1 << 31) - 1, 15).unwrap();
435 assert_eq!(t.value(), -1);
436 }
437
438 #[test]
439 fn level14_positive() {
440 let t = PackedTileId::from_tile_index(0, 14).unwrap();
441 assert_eq!(t.value(), 1073741824);
442 }
443
444 #[test]
445 fn invalid_level() {
446 assert!(matches!(
447 PackedTileId::from_tile_index(0, 16),
448 Err(TileIdError::InvalidLevel(16))
449 ));
450 }
451
452 #[test]
453 fn invalid_morton() {
454 assert!(PackedTileId::from_tile_index(2, 0).is_err());
456 }
457
458 #[test]
459 fn corners_and_center() {
460 let t = PackedTileId::from_tile_index(0, 0).unwrap();
461 assert_eq!(t.size(), 2147483648);
462 assert_eq!(t.south_west_corner(), (0, 0));
463 assert_eq!(t.north_east_corner(), (2147483648, 2147483648));
464 assert_eq!(t.center(), (1073741824, 1073741824));
465 }
466
467 #[test]
468 fn bbox_floor_div_negative() {
469 let tiles = get_tile_ids_for_bounding_box(-1, -1, 0, 0, 1);
471 assert!(!tiles.is_empty());
472 }
473
474 #[test]
475 fn bbox_from_single_tile() {
476 let t = PackedTileId::from_i64(131072).unwrap();
477 let bbox = bounding_box_from_tile_ids(&[t]).unwrap();
478 assert_eq!(bbox, (0, 0, 1073741823, 1073741823));
479 }
480
481 #[test]
482 fn empty_bbox_returns_none() {
483 assert!(bounding_box_from_tile_ids(&[]).is_none());
484 }
485}