use crate::Direction;
use crate::constants::*;
use crate::errors::QuadbinError;
use crate::tiles::Tile;
use crate::utils::*;
use core::{fmt, num::NonZeroU64};
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Cell(NonZeroU64);
impl TryFrom<u64> for Cell {
type Error = QuadbinError;
fn try_from(value: u64) -> Result<Self, QuadbinError> {
if !is_valid_cell(value) {
return Err(QuadbinError::InvalidCell(Some(value)));
}
Ok(Self(NonZeroU64::new(value).expect("non-zero cell index")))
}
}
impl Cell {
pub fn get(&self) -> u64 {
self.0.get()
}
pub fn new(value: u64) -> Self {
Cell::try_from(value).expect("cell index")
}
pub fn resolution(&self) -> u8 {
((self.0.get() >> 52) & 0x1F) as u8
}
pub fn parent(&self, parent_res: u8) -> Result<Self, QuadbinError> {
cell_to_parent(self, parent_res)
}
pub fn children(
&self,
children_res: u8,
) -> Result<impl Iterator<Item = Result<Cell, QuadbinError>>, QuadbinError> {
let resolution = self.resolution();
if children_res <= resolution || children_res > 26 {
return Err(QuadbinError::InvalidResolution(children_res));
}
let resolution_diff = children_res - resolution;
let block_range = (1 << (resolution_diff << 1)) as u64;
let block_shift = (52 - (children_res << 1)) as u64;
let cell = self.get();
let child_base = (cell & !(0x1F << 52)) | ((children_res as u64) << 52);
let child_base = child_base & !((block_range - 1) << block_shift);
Ok((0..block_range).map(move |x| {
let child = child_base | (x << block_shift);
Cell::try_from(child)
}))
}
pub fn neighbor(&self, direction: Direction) -> Option<Self> {
let tile = self.to_tile().neighbor(direction)?;
tile.to_cell().ok()
}
pub fn sibling(&self, direction: Direction) -> Option<Self> {
self.neighbor(direction)
}
pub fn neighbors(&self) -> [Option<Cell>; 4] {
let mut neighbors = [None; 4];
for (i, neighbor) in neighbors.iter_mut().enumerate() {
*neighbor = self.neighbor(Direction::new_unchecked(i as u8));
}
neighbors
}
pub fn area_m2(&self) -> f64 {
self.to_tile().area()
}
pub fn area_km2(&self) -> f64 {
self.area_m2() / 1_000_000_f64
}
pub fn to_point(&self) -> [f64; 2] {
cell_to_point(self)
}
pub fn to_bbox(&self) -> [f64; 4] {
let tile = &self.to_tile();
let xmin = tile.to_longitude(0.0).expect("offset");
let xmax = tile.to_longitude(1.0).expect("offset");
let ymin = tile.to_latitude(1.0).expect("offset");
let ymax = tile.to_latitude(0.0).expect("offset");
[xmin, ymin, xmax, ymax]
}
pub fn from_point(lat: f64, lng: f64, res: u8) -> Result<Self, QuadbinError> {
point_to_cell(lat, lng, res)
}
pub(crate) fn to_tile(self) -> Tile {
cell_to_tile(&self)
}
}
impl fmt::Display for Cell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get())
}
}
fn is_valid_cell(cell64: u64) -> bool {
let header = HEADER;
let mode = (cell64 >> 59) & 7;
let resolution = (cell64 >> 52) & 0x1F;
let resolution_shift = resolution.saturating_mul(4);
let unused = if resolution_shift >= 64 {
0
} else {
FOOTER >> resolution_shift
};
(cell64 & header == header) && mode == 1 && resolution <= 26 && (cell64 & unused == unused)
}
pub(crate) fn tile_to_cell(tile: Tile) -> Result<Cell, QuadbinError> {
let mut x = tile.x as u64;
let mut y = tile.y as u64;
let z = tile.z as u64;
x <<= 32 - z;
y <<= 32 - z;
x = (x | (x << S[4])) & B[4];
y = (y | (y << S[4])) & B[4];
x = (x | (x << S[3])) & B[3];
y = (y | (y << S[3])) & B[3];
x = (x | (x << S[2])) & B[2];
y = (y | (y << S[2])) & B[2];
x = (x | (x << S[1])) & B[1];
y = (y | (y << S[1])) & B[1];
x = (x | (x << S[0])) & B[0];
y = (y | (y << S[0])) & B[0];
let cell = HEADER | (1 << 59) | (z << 52) | ((x | (y << 1)) >> 12) | (FOOTER >> (z * 2));
Cell::try_from(cell)
}
fn cell_to_tile(cell: &Cell) -> Tile {
let cell64 = cell.get();
let z = (cell64 >> 52) & 31;
let q = (cell64 & FOOTER) << 12;
let mut x = q;
let mut y = q >> 1;
x &= B[0];
y &= B[0];
x = (x | (x >> S[0])) & B[1];
y = (y | (y >> S[0])) & B[1];
x = (x | (x >> S[1])) & B[2];
y = (y | (y >> S[1])) & B[2];
x = (x | (x >> S[2])) & B[3];
y = (y | (y >> S[2])) & B[3];
x = (x | (x >> S[3])) & B[4];
y = (y | (y >> S[3])) & B[4];
x = (x | (x >> S[4])) & B[5];
y = (y | (y >> S[4])) & B[5];
x >>= 32 - z;
y >>= 32 - z;
Tile::new(x as u32, y as u32, z as u8)
}
fn point_to_cell(lat: f64, lng: f64, res: u8) -> Result<Cell, QuadbinError> {
let lng = clip_longitude(lng);
let lat = clip_latitude(lat);
let tile = Tile::from_point(lat, lng, res)?;
tile.to_cell()
}
fn cell_to_point(cell: &Cell) -> [f64; 2] {
let tile = cell.to_tile();
let lat = tile.to_latitude(0.5).expect("offset");
let lon = tile.to_longitude(0.5).expect("offset");
[lat, lon]
}
fn cell_to_parent(cell: &Cell, parent_res: u8) -> Result<Cell, QuadbinError> {
let resolution = cell.resolution();
if parent_res >= resolution {
return Err(QuadbinError::InvalidResolution(parent_res));
}
let result = (cell.get() & !(0x1F << 52))
| ((parent_res as u64) << 52)
| (FOOTER >> ((parent_res as u64) << 1));
Cell::try_from(result)
}