maps_engine_rust/
projection.rs1use std::f64::consts::PI;
7
8pub const MAX_LAT: f64 = 85.05112878;
10pub const EARTH_RADIUS: f64 = 6_378_137.0;
12pub const TILE_SIZE: u32 = 256;
14
15#[inline]
17pub fn clamp_lat(lat: f64) -> f64 {
18 lat.clamp(-MAX_LAT, MAX_LAT)
19}
20
21#[inline]
23pub fn project(lon: f64, lat: f64) -> (f64, f64) {
24 let lat = clamp_lat(lat);
25 let x = (lon + 180.0) / 360.0;
26 let sin = (lat * PI / 180.0).sin();
27 let y = 0.5 - ((1.0 + sin) / (1.0 - sin)).ln() / (4.0 * PI);
28 (x, y)
29}
30
31#[inline]
33pub fn unproject(x: f64, y: f64) -> (f64, f64) {
34 let lon = x * 360.0 - 180.0;
35 let n = PI * (1.0 - 2.0 * y);
36 let lat = 180.0 / PI * (0.5 * (n.exp() - (-n).exp())).atan();
37 (lon, lat)
38}
39
40#[inline]
42pub fn to_pixels(lon: f64, lat: f64, zoom: u8) -> (f64, f64) {
43 let scale = (TILE_SIZE as f64) * 2f64.powi(zoom as i32);
44 let (x, y) = project(lon, lat);
45 (x * scale, y * scale)
46}
47
48#[inline]
50pub fn from_pixels(px: f64, py: f64, zoom: u8) -> (f64, f64) {
51 let scale = (TILE_SIZE as f64) * 2f64.powi(zoom as i32);
52 unproject(px / scale, py / scale)
53}
54
55#[inline]
57pub fn meters_per_pixel(lat: f64, zoom: u8) -> f64 {
58 let lat_rad = clamp_lat(lat) * PI / 180.0;
59 2.0 * PI * EARTH_RADIUS * lat_rad.cos() / ((TILE_SIZE as f64) * 2f64.powi(zoom as i32))
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn origin_projects_to_center() {
68 let (x, y) = project(0.0, 0.0);
69 assert!((x - 0.5).abs() < 1e-12);
70 assert!((y - 0.5).abs() < 1e-12);
71 }
72
73 #[test]
74 fn roundtrip() {
75 for &(lon, lat) in &[(105.85, 21.02), (-73.99, 40.73), (139.69, 35.68)] {
76 let (x, y) = project(lon, lat);
77 let (lon2, lat2) = unproject(x, y);
78 assert!((lon - lon2).abs() < 1e-9, "lon {lon}");
79 assert!((lat - lat2).abs() < 1e-9, "lat {lat}");
80 }
81 }
82
83 #[test]
84 fn clamps_extreme_latitude() {
85 let (_, y1) = project(0.0, 90.0);
86 let (_, y2) = project(0.0, MAX_LAT);
87 assert!((y1 - y2).abs() < 1e-12);
88 }
89
90 #[test]
91 fn pixel_roundtrip_at_zoom() {
92 let (px, py) = to_pixels(105.85, 21.02, 14);
93 let (lon, lat) = from_pixels(px, py, 14);
94 assert!((lon - 105.85).abs() < 1e-9);
95 assert!((lat - 21.02).abs() < 1e-9);
96 }
97}