use crate::morton::MortonCode;
use crate::wgs84::Wgs84;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TileIdError {
InvalidLevel(u32),
InvalidMortonNumber {
morton: i64,
level: u32,
max: u64,
},
ValueTooSmall(i32),
}
impl std::fmt::Display for TileIdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TileIdError::InvalidLevel(level) => {
write!(f, "Invalid level {level} (must be 0-15)")
}
TileIdError::InvalidMortonNumber { morton, level, max } => write!(
f,
"Invalid morton number {morton} for level {level} (allowed: 0-{max})"
),
TileIdError::ValueTooSmall(value) => write!(
f,
"Invalid PackedTileId({value}): value must be >= {} or negative for level 15",
1u32 << 16
),
}
}
}
impl std::error::Error for TileIdError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PackedTileId {
value: u32,
}
impl PackedTileId {
pub fn new(value: i32) -> Result<Self, TileIdError> {
Self::from_i64(value as i64)
}
pub fn from_i64(value: i64) -> Result<Self, TileIdError> {
let stored: u32 = if value < 0 {
(value + (1i64 << 32)) as u32
} else if value >= (1i64 << 32) {
(value as u64 & 0xFFFF_FFFF) as u32
} else {
value as u32
};
let tile = PackedTileId { value: stored };
tile.validate()?;
Ok(tile)
}
pub fn value(&self) -> i32 {
self.value as i32
}
pub fn from_tile_index(morton_number: u32, level: u32) -> Result<Self, TileIdError> {
if level > 15 {
return Err(TileIdError::InvalidLevel(level));
}
let max_morton: u64 = (1u64 << (2 * level + 1)) - 1;
if (morton_number as u64) > max_morton {
return Err(TileIdError::InvalidMortonNumber {
morton: morton_number as i64,
level,
max: max_morton,
});
}
let value: u32 = morton_number + (1u32 << (16 + level));
PackedTileId::from_i64(value as i64)
}
pub fn from_morton_and_level(morton_code: MortonCode, level: u32) -> Result<Self, TileIdError> {
if level > 15 {
return Err(TileIdError::InvalidLevel(level));
}
let (x_coord, y_coord) = morton_code.to_nds_coordinates();
let x_coord: u64 = if x_coord < 0 {
(x_coord as i64 + (1i64 << 32)) as u64
} else {
x_coord as u64
};
let y_coord: u64 = if y_coord < 0 {
(y_coord as i64 + (1i64 << 31)) as u64
} else {
y_coord as u64
};
let n_level = 31 - level;
let n_x = (x_coord >> n_level) as i32;
let n_y = (y_coord >> n_level) as i32;
let temp = MortonCode::from_nds_coordinates(n_x, n_y);
let value: u64 = temp.value() + (1u64 << (16 + level));
PackedTileId::from_i64(value as i64)
}
pub fn level(&self) -> u32 {
let mut level = 0u32;
let mut tile_id = self.value >> 16;
while tile_id > 1 {
tile_id >>= 1;
level += 1;
}
level
}
pub fn size(&self) -> i64 {
1i64 << (31 - self.level())
}
pub fn dimensions_in_meters(&self) -> (f64, f64) {
let (center_x, center_y) = self.center();
let center_wgs = Wgs84::from_nds_coordinates(center_x as i32, center_y as i32);
let tile_size = self.size() as i32;
Wgs84::nds_distance_to_meters(tile_size, tile_size, center_wgs.lat)
}
pub fn center(&self) -> (i64, i64) {
let (x, y) = self.south_west_corner();
let half_size = self.size() / 2;
(x + half_size, y + half_size)
}
pub fn south_west_corner(&self) -> (i64, i64) {
let morton_number = self.morton_number() as u64;
let level = self.level();
let shift = 63 - (2 * level + 1);
let (x, y) = MortonCode::new(morton_number << shift).to_nds_coordinates();
(x as i64, y as i64)
}
pub fn north_east_corner(&self) -> (i64, i64) {
let (x, y) = self.south_west_corner();
let size = self.size();
(x + size, y + size)
}
pub fn morton_number(&self) -> u32 {
let tile_level = self.level();
self.value - (1u32 << (16 + tile_level))
}
fn validate(&self) -> Result<(), TileIdError> {
let min_packed_tile_id: u32 = 1 << 16;
if self.value < min_packed_tile_id {
return Err(TileIdError::ValueTooSmall(self.value()));
}
let tile_level = self.level();
let morton = self.morton_number();
let max_morton: u64 = (1u64 << (2 * tile_level + 1)) - 1;
if (morton as u64) > max_morton {
return Err(TileIdError::InvalidMortonNumber {
morton: morton as i64,
level: tile_level,
max: max_morton,
});
}
Ok(())
}
fn deinterleave_morton(morton: u32, level: u32) -> (u32, u32) {
let mut x = 0u32;
let mut y = 0u32;
for i in 0..level {
if morton & (1u32 << (2 * i)) != 0 {
x |= 1u32 << i;
}
if morton & (1u32 << (2 * i + 1)) != 0 {
y |= 1u32 << i;
}
}
if morton & (1u32 << (2 * level)) != 0 {
x |= 1u32 << level;
}
(x, y)
}
fn interleave_coords(x: u32, y: u32, level: u32) -> u32 {
let mut morton = 0u32;
for i in 0..level {
if x & (1u32 << i) != 0 {
morton |= 1u32 << (2 * i);
}
if y & (1u32 << i) != 0 {
morton |= 1u32 << (2 * i + 1);
}
}
if x & (1u32 << level) != 0 {
morton |= 1u32 << (2 * level);
}
morton
}
pub fn west_neighbour(&self) -> PackedTileId {
let level = self.level();
let morton = self.morton_number();
let (mut x, y) = Self::deinterleave_morton(morton, level);
let max_x = (1u32 << (level + 1)) - 1;
x = x.wrapping_sub(1) & max_x;
let new_morton = Self::interleave_coords(x, y, level);
PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
}
pub fn east_neighbour(&self) -> PackedTileId {
let level = self.level();
let morton = self.morton_number();
let (mut x, y) = Self::deinterleave_morton(morton, level);
let max_x = (1u32 << (level + 1)) - 1;
x = x.wrapping_add(1) & max_x;
let new_morton = Self::interleave_coords(x, y, level);
PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
}
pub fn south_neighbour(&self) -> PackedTileId {
let level = self.level();
let morton = self.morton_number();
let (x, mut y) = Self::deinterleave_morton(morton, level);
let max_y = (1u32 << level) - 1;
y = y.wrapping_sub(1) & max_y;
let new_morton = Self::interleave_coords(x, y, level);
PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
}
pub fn north_neighbour(&self) -> PackedTileId {
let level = self.level();
let morton = self.morton_number();
let (x, mut y) = Self::deinterleave_morton(morton, level);
let max_y = (1u32 << level) - 1;
y = y.wrapping_add(1) & max_y;
let new_morton = Self::interleave_coords(x, y, level);
PackedTileId::from_tile_index(new_morton, level).expect("valid neighbour")
}
}
impl std::fmt::Display for PackedTileId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PackedTileId(value={})", self.value())
}
}
#[inline]
fn floor_div(a: i64, b: i64) -> i64 {
a.div_euclid(b)
}
pub fn get_tile_ids_for_bounding_box(
sw_x: i64,
sw_y: i64,
ne_x: i64,
ne_y: i64,
level: u32,
) -> Vec<PackedTileId> {
let mut tile_ids = Vec::new();
let tile_size: i64 = 1 << (31 - level);
let start_tile_x = floor_div(sw_x, tile_size);
let start_tile_y = floor_div(sw_y, tile_size);
let end_tile_x = floor_div(ne_x, tile_size);
let end_tile_y = floor_div(ne_y, tile_size);
let mut tile_y = start_tile_y;
while tile_y <= end_tile_y {
let mut tile_x = start_tile_x;
while tile_x <= end_tile_x {
let tile_sw_x = tile_x * tile_size;
let tile_sw_y = tile_y * tile_size;
let morton = MortonCode::from_nds_coordinates(tile_sw_x as i32, tile_sw_y as i32);
let tile_id = PackedTileId::from_morton_and_level(morton, level)
.expect("tile within bounding box must be valid");
tile_ids.push(tile_id);
tile_x += 1;
}
tile_y += 1;
}
tile_ids
}
pub fn bounding_box_from_tile_ids(tiles: &[PackedTileId]) -> Option<(i64, i64, i64, i64)> {
let (first, rest) = tiles.split_first()?;
let (mut min_x, mut min_y) = first.south_west_corner();
let (mut max_x, mut max_y) = first.north_east_corner();
for tile in rest {
let (sw_x, sw_y) = tile.south_west_corner();
let (ne_x, ne_y) = tile.north_east_corner();
min_x = min_x.min(sw_x);
min_y = min_y.min(sw_y);
max_x = max_x.max(ne_x);
max_y = max_y.max(ne_y);
}
Some((min_x, min_y, max_x - 1, max_y - 1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn level_15_is_negative() {
let t = PackedTileId::from_tile_index(0, 15).unwrap();
assert_eq!(t.value(), -2147483648);
assert_eq!(t.level(), 15);
assert_eq!(t.morton_number(), 0);
}
#[test]
fn signed_unsigned_constructors_agree() {
let a = PackedTileId::from_i64(-2147483648).unwrap();
let b = PackedTileId::from_i64(2147483648).unwrap();
assert_eq!(a, b);
assert_eq!(a.value(), -2147483648);
}
#[test]
fn max_level15_tile() {
let t = PackedTileId::from_tile_index((1 << 31) - 1, 15).unwrap();
assert_eq!(t.value(), -1);
}
#[test]
fn level14_positive() {
let t = PackedTileId::from_tile_index(0, 14).unwrap();
assert_eq!(t.value(), 1073741824);
}
#[test]
fn invalid_level() {
assert!(matches!(
PackedTileId::from_tile_index(0, 16),
Err(TileIdError::InvalidLevel(16))
));
}
#[test]
fn invalid_morton() {
assert!(PackedTileId::from_tile_index(2, 0).is_err());
}
#[test]
fn corners_and_center() {
let t = PackedTileId::from_tile_index(0, 0).unwrap();
assert_eq!(t.size(), 2147483648);
assert_eq!(t.south_west_corner(), (0, 0));
assert_eq!(t.north_east_corner(), (2147483648, 2147483648));
assert_eq!(t.center(), (1073741824, 1073741824));
}
#[test]
fn bbox_floor_div_negative() {
let tiles = get_tile_ids_for_bounding_box(-1, -1, 0, 0, 1);
assert!(!tiles.is_empty());
}
#[test]
fn bbox_from_single_tile() {
let t = PackedTileId::from_i64(131072).unwrap();
let bbox = bounding_box_from_tile_ids(&[t]).unwrap();
assert_eq!(bbox, (0, 0, 1073741823, 1073741823));
}
#[test]
fn empty_bbox_returns_none() {
assert!(bounding_box_from_tile_ids(&[]).is_none());
}
}