1use crate::projection::{from_pixels, to_pixels, TILE_SIZE};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Tile {
8 pub x: u32,
9 pub y: u32,
10 pub z: u8,
11}
12
13impl Tile {
14 pub fn from_lon_lat(lon: f64, lat: f64, z: u8) -> Self {
16 let (px, py) = to_pixels(lon, lat, z);
17 let n = 2u32.pow(z as u32);
18 let size = TILE_SIZE as f64;
19 Tile {
20 x: (px / size).floor() as u32 % n,
21 y: (py / size).floor() as u32 % n,
22 z,
23 }
24 }
25
26 pub fn bounds(&self) -> (f64, f64, f64, f64) {
28 let size = TILE_SIZE as f64;
29 let (w, n) = from_pixels(self.x as f64 * size, self.y as f64 * size, self.z);
30 let (e, s) = from_pixels(
31 (self.x + 1) as f64 * size,
32 (self.y + 1) as f64 * size,
33 self.z,
34 );
35 (w, s, e, n)
36 }
37
38 pub fn quadkey(&self) -> String {
40 let mut key = String::with_capacity(self.z as usize);
41 for i in (1..=self.z).rev() {
42 let mut digit = 0;
43 let mask = 1 << (i - 1);
44 if self.x & mask != 0 {
45 digit += 1;
46 }
47 if self.y & mask != 0 {
48 digit += 2;
49 }
50 key.push(char::from_digit(digit, 10).unwrap());
51 }
52 key
53 }
54
55 pub fn children(&self) -> [Tile; 4] {
57 let z = self.z + 1;
58 let (x, y) = (self.x * 2, self.y * 2);
59 [
60 Tile { x, y, z },
61 Tile { x: x + 1, y, z },
62 Tile { x, y: y + 1, z },
63 Tile {
64 x: x + 1,
65 y: y + 1,
66 z,
67 },
68 ]
69 }
70
71 pub fn parent(&self) -> Option<Tile> {
73 if self.z == 0 {
74 None
75 } else {
76 Some(Tile {
77 x: self.x / 2,
78 y: self.y / 2,
79 z: self.z - 1,
80 })
81 }
82 }
83}
84
85pub fn tiles_in_bounds(west: f64, south: f64, east: f64, north: f64, z: u8) -> Vec<Tile> {
88 let nw = Tile::from_lon_lat(west, north, z);
89 let se = Tile::from_lon_lat(east, south, z);
90 let mut out = Vec::new();
91 for x in nw.x..=se.x {
92 for y in nw.y..=se.y {
93 out.push(Tile { x, y, z });
94 }
95 }
96 out
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn hanoi_tile_at_z14() {
105 let t = Tile::from_lon_lat(105.85, 21.02, 14);
106 assert_eq!(t.z, 14);
107 let (w, s, e, n) = t.bounds();
108 assert!(w <= 105.85 && 105.85 <= e);
109 assert!(s <= 21.02 && 21.02 <= n);
110 }
111
112 #[test]
113 fn quadkey_roundtrip_shape() {
114 let t = Tile { x: 3, y: 5, z: 3 };
115 assert_eq!(t.quadkey().len(), 3);
116 assert_eq!(t.quadkey(), "213");
117 }
118
119 #[test]
120 fn parent_children_inverse() {
121 let t = Tile::from_lon_lat(105.85, 21.02, 10);
122 let kids = t.children();
123 assert_eq!(kids.len(), 4);
124 for k in kids {
125 assert_eq!(k.parent(), Some(t));
126 }
127 assert_eq!(Tile { x: 0, y: 0, z: 0 }.parent(), None);
128 }
129
130 #[test]
131 fn bounds_cover_expected_tiles() {
132 let tiles = tiles_in_bounds(105.8, 21.0, 105.9, 21.05, 14);
133 assert!(!tiles.is_empty());
134 assert!(tiles.len() < 100);
135 }
136}