use std::f64::consts::{PI, TAU, FRAC_PI_4};
#[cfg(test)]
#[macro_use]
extern crate approx;
pub const MAX_ZOOM: u64 = 29;
type D = f64;
type M = f64;
type P = u64;
type Z = u64;
type T = u64;
#[derive(Debug)]
pub struct GlobalMercator {
tile_size: u64,
a: f64,
initial_resolution: f64,
}
impl GlobalMercator {
pub fn new(tile_size: u64) -> Self {
let a = 6378137.0;
let initial_resolution = TAU * a / (tile_size as f64);
Self {
tile_size,
initial_resolution,
a,
}
}
pub fn tile_size(&self) -> u64 {
self.tile_size
}
pub fn lat_lon_to_meters(&self, lon: D, lat: D) -> (M, M) {
let mx = self.a * lon.to_radians();
let my = self.a * f64::ln(f64::tan(FRAC_PI_4 + lat.to_radians() / 2.0));
(mx, my)
}
pub fn meters_2_lat_lon(&self, mx: M, my: M) -> (D, D) {
let lon = (mx / self.a).to_degrees();
let lat = (f64::atan(f64::sinh(my / self.a))).to_degrees();
(lon, lat)
}
pub fn pixels_to_meters(&self, px: P, py: P, zoom: Z) -> (M, M) {
let res = self.resolution(zoom);
let mx = (px as f64) * res - PI * self.a;
let my = (py as f64) * res - PI * self.a;
(mx, my)
}
pub fn meters_to_pixels(&self, mx: M, my: M, zoom: Z) -> (P, P) {
let res = self.resolution(zoom);
let px = M::floor((mx + PI * self.a) / res) as P;
let py = M::floor((my + PI * self.a) / res) as P;
(px, py)
}
pub fn pixels_to_tile(&self, px: P, py: P) -> (T, T) {
let tx = f64::ceil(px as f64 / self.tile_size as f64) as u64 - 1;
let ty = f64::ceil(py as f64 / self.tile_size as f64) as u64 - 1;
(tx, ty)
}
pub fn pixels_to_raster(&self, px: P, py: P, zoom: Z) -> (P, P) {
let map_size = (self.tile_size) << zoom;
(px, map_size - py)
}
pub fn meters_to_tile(&self, mx: M, my: M, zoom: Z) -> (T, T) {
let (px, py) = self.meters_to_pixels(mx, my, zoom);
self.pixels_to_tile(px, py)
}
pub fn tile_bounds(&self, tx: T, ty: T, zoom: Z) -> (M, M, M, M) {
let (minx, miny) = self.pixels_to_meters(tx * self.tile_size, ty * self.tile_size, zoom);
let (maxx, maxy) = self.pixels_to_meters(
(tx + 1) * self.tile_size,
(ty + 1) * self.tile_size,
zoom,
);
(minx, miny, maxx, maxy)
}
pub fn tile_lat_lon_bounds(&self, tx: T, ty: T, zoom: Z) -> (D, D, D, D) {
let (min_x, min_y, max_x, max_y) = self.tile_bounds(tx, ty, zoom);
let (min_lon, min_lat) = self.meters_2_lat_lon(min_x, min_y);
let (max_lon, max_lat) = self.meters_2_lat_lon(max_x, max_y);
(min_lon, min_lat, max_lon, max_lat)
}
pub fn resolution(&self, zoom: Z) -> f64 {
self.initial_resolution / (2.0f64.powi(zoom as i32))
}
pub fn zoom_for_pixel_size(&self, pixel_size: f64) -> Z {
for i in 0..=MAX_ZOOM {
if pixel_size > self.resolution(i) {
return std::cmp::max(0, i - 1);
}
}
panic!("Invalid pixel_size: {}", pixel_size);
}
pub fn google_tile(&self, tx: T, ty: T, zoom: Z) -> (T, T) {
(tx, (2u64.pow(zoom as u32) - 1) - ty)
}
pub fn quad_tree(&self, tx: T, ty: T, zoom: Z) -> String {
let mut quad_key = String::new();
let ty = (f64::powi(2.0, zoom as i32) - 1.0) as T - ty;
for i in (1..(zoom + 1) as i32).rev() {
let mut digit = 0;
let mask = 1 << (i - 1);
if (tx & mask) != 0 {
digit += 1;
}
if (ty & mask) != 0 {
digit += 2;
}
quad_key.push_str(format!("{}", digit).as_str());
}
quad_key
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let transformer = GlobalMercator::new(256);
assert_eq!(transformer.tile_size, 256);
}
#[test]
fn lat_lon_to_meters() {
let transformer = GlobalMercator::new(256);
let (x, y) = transformer.lat_lon_to_meters(0.0, 0.0);
assert_relative_eq!(x, 0.0, epsilon = 1e-9);
assert_relative_eq!(y, 0.0, epsilon = 1e-9);
let (x, y) = transformer.lat_lon_to_meters(10.0, 10.0);
assert_relative_eq!(x, 1113194.91, epsilon = 1e-2);
assert_relative_eq!(y, 1118889.97, epsilon = 1e-2);
let (x, y) = transformer.lat_lon_to_meters(179.999_996_920_671_83, 85.051_128_514_163);
assert_relative_eq!(x, 20037508.0, epsilon = 1e-2);
assert_relative_eq!(y, 20037508.0, epsilon = 1e-2);
let (x, y) = transformer.lat_lon_to_meters(180.0, 85.0);
assert_relative_eq!(x, 20037508.34, epsilon = 5e-3);
assert_relative_eq!(y, 19971868.88, epsilon = 5e-3);
let (x, y) = transformer.lat_lon_to_meters(-180.0, -85.0);
assert_relative_eq!(x, -20037508.34, epsilon = 5e-3);
assert_relative_eq!(y, -19971868.88, epsilon = 5e-3);
}
#[test]
fn meters_to_lat_lon() {
let transformer = GlobalMercator::new(256);
let (lon, lat) = transformer.meters_2_lat_lon(0.0, 0.0);
assert_relative_eq!(lon, 0.0, epsilon = f64::EPSILON);
assert_relative_eq!(lat, 0.0, epsilon = f64::EPSILON);
let (lon, lat) = transformer.meters_2_lat_lon(-20037508.0, -20037508.0);
assert_relative_eq!(lon, -179.999997, epsilon = 1e-6);
assert_relative_eq!(lat, -85.051129, epsilon = 1e-6);
let (lon, lat) = transformer.meters_2_lat_lon(20037508.0, 20037508.0);
assert_relative_eq!(lon, 179.999997, epsilon = 1e-6);
assert_relative_eq!(lat, 85.051129, epsilon = 1e-6);
}
#[test]
fn tile_bounds() {
let transformer = GlobalMercator::new(256);
let c = 20_037_508.34;
for z in 0..MAX_ZOOM {
let num_tiles = 2u64.pow(z as u32);
let m = c * 2.0 / (num_tiles as f64) - c;
let bounds = transformer.tile_bounds(0, 0, z);
assert_relative_eq!(bounds.0, -c, epsilon = 5e-3);
assert_relative_eq!(bounds.1, -c, epsilon = 5e-3);
assert_relative_eq!(bounds.2, m, epsilon = 5e-3);
assert_relative_eq!(bounds.3, m, epsilon = 5e-3);
let bounds = transformer.tile_bounds(num_tiles - 1, 0, z);
assert_relative_eq!(bounds.0, -m, epsilon = 5e-3);
assert_relative_eq!(bounds.1, -c, epsilon = 5e-3);
assert_relative_eq!(bounds.2, c, epsilon = 5e-3);
assert_relative_eq!(bounds.3, m, epsilon = 5e-3);
}
}
}