use crate::{
lon_lat,
position::{Pixels, Position},
tiles::TileId,
};
use std::f64::consts::PI;
pub(crate) fn total_pixels(zoom: f64) -> f64 {
2f64.powf(zoom) * (TILE_SIZE as f64)
}
pub(crate) fn total_tiles(zoom: u8) -> u32 {
2u32.pow(zoom as u32)
}
const TILE_SIZE: u32 = 256;
fn mercator_normalized(position: Position) -> (f64, f64) {
let x = position.x().to_radians();
let y = position.y().to_radians().tan().asinh();
let x = (1. + (x / PI)) / 2.;
let y = (1. - (y / PI)) / 2.;
(x, y)
}
pub(crate) fn tile_id(position: Position, mut zoom: u8, source_tile_size: u32) -> TileId {
let (x, y) = mercator_normalized(position);
zoom -= (source_tile_size as f64 / TILE_SIZE as f64).log2() as u8;
let number_of_tiles = 2u32.pow(zoom as u32) as f64;
let x = (x * number_of_tiles).floor() as u32;
let y = (y * number_of_tiles).floor() as u32;
TileId { x, y, zoom }
}
pub fn project(position: Position, zoom: f64) -> Pixels {
let total_pixels = total_pixels(zoom);
let (x, y) = mercator_normalized(position);
Pixels::new(x * total_pixels, y * total_pixels)
}
pub(crate) fn unproject(pixels: Pixels, zoom: f64) -> Position {
let number_of_pixels: f64 = 2f64.powf(zoom) * (TILE_SIZE as f64);
let lon = pixels.x();
let lon = lon / number_of_pixels;
let lon = (lon * 2. - 1.) * PI;
let lon = lon.to_degrees();
let lat = pixels.y();
let lat = lat / number_of_pixels;
let lat = (-lat * 2. + 1.) * PI;
let lat = lat.sinh().atan().to_degrees();
lon_lat(lon, lat)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lat_lon;
#[test]
fn projecting_position_and_tile() {
let citadel = lon_lat(21.00027, 52.26470);
let zoom = 20;
assert_eq!(
TileId {
x: 585455,
y: 345104,
zoom
},
tile_id(citadel, zoom, 256)
);
assert_eq!(
TileId {
x: 292727,
y: 172552,
zoom: zoom - 1
},
tile_id(citadel, zoom, 512)
);
assert_eq!(
Pixels::new(585455. * 256., 345104. * 256.),
tile_id(citadel, zoom, 256).project(256.)
);
let calculated = project(citadel, zoom as f64);
let citadel_proj = Pixels::new(585455. * 256. + 184., 345104. * 256. + 116.5);
approx::assert_relative_eq!(calculated.x(), citadel_proj.x(), max_relative = 0.5);
approx::assert_relative_eq!(calculated.y(), citadel_proj.y(), max_relative = 0.5);
}
#[test]
fn project_there_and_back() {
let citadel = lat_lon(21.00027, 52.26470);
let zoom = 16;
let calculated = unproject(project(citadel, zoom as f64), zoom as f64);
approx::assert_relative_eq!(calculated.x(), citadel.x(), max_relative = 1.0);
approx::assert_relative_eq!(calculated.y(), citadel.y(), max_relative = 1.0);
}
}