slint_mapping/
projection.rs1pub const TILE_SIZE_DEFAULT: f64 = 256.0;
16
17pub const MAX_LATITUDE: f64 = 85.051_128_779_806_59;
20
21#[inline]
26pub fn lonlat_to_tile(longitude: f64, latitude: f64, zoom: f64) -> (f64, f64) {
27 let n = 2f64.powf(zoom);
28 let x = (longitude + 180.0) / 360.0 * n;
29 let lat_rad = latitude.clamp(-MAX_LATITUDE, MAX_LATITUDE).to_radians();
30 let y = (1.0 - (lat_rad.tan() + 1.0 / lat_rad.cos()).ln() / std::f64::consts::PI) / 2.0 * n;
31 (x, y)
32}
33
34#[inline]
36pub fn tile_to_lonlat(tile_x: f64, tile_y: f64, zoom: f64) -> (f64, f64) {
37 let n = 2f64.powf(zoom);
38 let longitude = tile_x / n * 360.0 - 180.0;
39 let lat_rad = (std::f64::consts::PI * (1.0 - 2.0 * tile_y / n))
40 .sinh()
41 .atan();
42 (longitude, lat_rad.to_degrees())
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn roundtrip_zero_meridian() {
51 for z in 0..=18 {
52 let (x, y) = lonlat_to_tile(0.0, 0.0, z as f64);
53 let (lon, lat) = tile_to_lonlat(x, y, z as f64);
54 assert!((lon - 0.0).abs() < 1e-9, "z={z} lon drift");
55 assert!((lat - 0.0).abs() < 1e-9, "z={z} lat drift");
56 }
57 }
58
59 #[test]
60 fn roundtrip_known_cities() {
61 let cases = [
62 ("NYC", -74.0060, 40.7128),
63 ("Sydney", 151.2093, -33.8688),
64 ("Reykjavik", -21.9426, 64.1466),
65 ];
66 for (name, lon, lat) in cases {
67 for z in [0.0, 5.0, 10.0, 17.0] {
68 let (x, y) = lonlat_to_tile(lon, lat, z);
69 let (lon2, lat2) = tile_to_lonlat(x, y, z);
70 assert!((lon - lon2).abs() < 1e-9, "{name}@z{z} lon drift");
71 assert!((lat - lat2).abs() < 1e-9, "{name}@z{z} lat drift");
72 }
73 }
74 }
75
76 #[test]
77 fn z0_covers_world_in_one_tile() {
78 for lon in [-179.9, -90.0, 0.0, 90.0, 179.9] {
81 for lat in [-80.0, -45.0, 0.0, 45.0, 80.0] {
82 let (x, y) = lonlat_to_tile(lon, lat, 0.0);
83 assert!((0.0..1.0).contains(&x), "x out of range: {x}");
84 assert!((0.0..1.0).contains(&y), "y out of range: {y}");
85 }
86 }
87 }
88}