use crate::projection::{from_pixels, to_pixels, TILE_SIZE};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Tile {
pub x: u32,
pub y: u32,
pub z: u8,
}
impl Tile {
pub fn from_lon_lat(lon: f64, lat: f64, z: u8) -> Self {
let (px, py) = to_pixels(lon, lat, z);
let n = 2u32.pow(z as u32);
let size = TILE_SIZE as f64;
Tile {
x: (px / size).floor() as u32 % n,
y: (py / size).floor() as u32 % n,
z,
}
}
pub fn bounds(&self) -> (f64, f64, f64, f64) {
let size = TILE_SIZE as f64;
let (w, n) = from_pixels(self.x as f64 * size, self.y as f64 * size, self.z);
let (e, s) = from_pixels(
(self.x + 1) as f64 * size,
(self.y + 1) as f64 * size,
self.z,
);
(w, s, e, n)
}
pub fn quadkey(&self) -> String {
let mut key = String::with_capacity(self.z as usize);
for i in (1..=self.z).rev() {
let mut digit = 0;
let mask = 1 << (i - 1);
if self.x & mask != 0 {
digit += 1;
}
if self.y & mask != 0 {
digit += 2;
}
key.push(char::from_digit(digit, 10).unwrap());
}
key
}
pub fn children(&self) -> [Tile; 4] {
let z = self.z + 1;
let (x, y) = (self.x * 2, self.y * 2);
[
Tile { x, y, z },
Tile { x: x + 1, y, z },
Tile { x, y: y + 1, z },
Tile {
x: x + 1,
y: y + 1,
z,
},
]
}
pub fn parent(&self) -> Option<Tile> {
if self.z == 0 {
None
} else {
Some(Tile {
x: self.x / 2,
y: self.y / 2,
z: self.z - 1,
})
}
}
}
pub fn tiles_in_bounds(west: f64, south: f64, east: f64, north: f64, z: u8) -> Vec<Tile> {
let nw = Tile::from_lon_lat(west, north, z);
let se = Tile::from_lon_lat(east, south, z);
let mut out = Vec::new();
for x in nw.x..=se.x {
for y in nw.y..=se.y {
out.push(Tile { x, y, z });
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hanoi_tile_at_z14() {
let t = Tile::from_lon_lat(105.85, 21.02, 14);
assert_eq!(t.z, 14);
let (w, s, e, n) = t.bounds();
assert!(w <= 105.85 && 105.85 <= e);
assert!(s <= 21.02 && 21.02 <= n);
}
#[test]
fn quadkey_roundtrip_shape() {
let t = Tile { x: 3, y: 5, z: 3 };
assert_eq!(t.quadkey().len(), 3);
assert_eq!(t.quadkey(), "213");
}
#[test]
fn parent_children_inverse() {
let t = Tile::from_lon_lat(105.85, 21.02, 10);
let kids = t.children();
assert_eq!(kids.len(), 4);
for k in kids {
assert_eq!(k.parent(), Some(t));
}
assert_eq!(Tile { x: 0, y: 0, z: 0 }.parent(), None);
}
#[test]
fn bounds_cover_expected_tiles() {
let tiles = tiles_in_bounds(105.8, 21.0, 105.9, 21.05, 14);
assert!(!tiles.is_empty());
assert!(tiles.len() < 100);
}
}