1use libm::{atan, cos, exp, floor, log, pow, sin, tan};
2
3use crate::{A, EARTH_CIRCUMFERENCE, MAXEXTENT};
4
5use core::f64::consts::PI;
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum Source {
10 WGS84,
12 Google,
14}
15
16pub fn get_zoom_size(zoom: u8, tile_size: f64) -> (f64, f64, f64, f64) {
18 let size = tile_size * pow(2., zoom as f64);
19 (size / 360., size / (2. * PI), size / 2., size)
20}
21
22pub fn ll_to_px(
25 lonlat: (f64, f64),
26 zoom: u8,
27 anti_meridian: Option<bool>,
28 tile_size: Option<f64>,
29) -> (f64, f64) {
30 let anti_meridian = anti_meridian.unwrap_or(false);
31 let tile_size = tile_size.unwrap_or(512.);
32
33 let (bc, cc, zc, ac) = get_zoom_size(zoom, tile_size);
34 let expansion = if anti_meridian { 2. } else { 1. };
35 let d = zc;
36 let f = sin((lonlat.1).to_radians()).clamp(-0.999999999999, 0.999999999999);
37 let mut x = d + lonlat.0 * bc;
38 let mut y = d + 0.5 * log((1. + f) / (1. - f)) * -cc;
39 if x > ac * expansion {
40 x = ac * expansion;
41 }
42 if y > ac {
43 y = ac;
44 }
45
46 (x, y)
47}
48
49pub fn px_to_ll(xy: (f64, f64), zoom: u8, tile_size: Option<f64>) -> (f64, f64) {
52 let tile_size = tile_size.unwrap_or(512.);
53 let (bc, cc, zc, _) = get_zoom_size(zoom, tile_size);
54 let g = (xy.1 - zc) / -cc;
55 let lon = (xy.0 - zc) / bc;
56 let lat = 2. * atan(exp(g)).to_degrees() - 0.5 * PI;
57
58 (lon, lat)
59}
60
61pub fn ll_to_merc(lonlan: (f64, f64)) -> (f64, f64) {
64 let mut x = (A * lonlan.0).to_radians();
65 let mut y = A * log(tan(PI * 0.25 + (0.5 * lonlan.1).to_radians()));
66 x = x.clamp(-MAXEXTENT, MAXEXTENT);
68 y = y.clamp(-MAXEXTENT, MAXEXTENT);
69
70 (x, y)
71}
72
73pub fn merc_to_ll(merc: (f64, f64)) -> (f64, f64) {
76 let x = (merc.0 / A).to_degrees();
77 let y = (0.5 * PI - 2. * atan(exp(-merc.1 / A))).to_degrees();
78 (x, y)
79}
80
81pub fn px_to_tile(px: (f64, f64), tile_size: Option<f64>) -> (u32, u32) {
84 let tile_size = tile_size.unwrap_or(512.);
85 (floor(px.0 / tile_size) as u32, floor(px.1 / tile_size) as u32)
86}
87
88pub fn tile_to_bbox(tile: (u8, u32, u32), tile_size: Option<f64>) -> (f64, f64, f64, f64) {
91 let tile_size = tile_size.unwrap_or(512.);
92 let (_zoom, x, y) = tile;
93 let min_x = x as f64 * tile_size;
94 let min_y = y as f64 * tile_size;
95 let max_x = min_x + tile_size;
96 let max_y = min_y + tile_size;
97
98 (min_x, min_y, max_x, max_y)
99}
100
101pub fn ll_to_tile(lonlat: (f64, f64), zoom: u8, tile_size: Option<f64>) -> (u32, u32) {
104 let px = ll_to_px(lonlat, zoom, Some(false), tile_size);
105 px_to_tile(px, tile_size)
106}
107
108pub fn ll_to_tile_px(
111 lonlat: (f64, f64),
112 tile: (u8, u32, u32),
113 tile_size: Option<f64>,
114) -> (f64, f64) {
115 let (zoom, x, y) = tile;
116 let tile_size = tile_size.unwrap_or(512.);
117 let px = ll_to_px(lonlat, zoom, Some(false), Some(tile_size));
118 let tile_x_start = x as f64 * tile_size;
119 let tile_y_start = y as f64 * tile_size;
120
121 ((px.0 - tile_x_start) / tile_size, (px.1 - tile_y_start) / tile_size)
122}
123
124pub fn convert_bbox(bbox: &(f64, f64, f64, f64), source: Source) -> (f64, f64, f64, f64) {
127 let low: (f64, f64);
128 let high: (f64, f64);
129 match source {
130 Source::WGS84 => {
131 low = merc_to_ll((bbox.0, bbox.1));
132 high = merc_to_ll((bbox.2, bbox.3));
133 }
134 Source::Google => {
135 low = ll_to_merc((bbox.0, bbox.1));
136 high = ll_to_merc((bbox.2, bbox.3));
137 }
138 };
139 (low.0, low.1, high.0, high.1)
140}
141
142pub fn xyz_to_bbox(
146 x: f64,
147 y: f64,
148 zoom: u8,
149 tms_style: Option<bool>,
150 source: Option<Source>,
151 tile_size: Option<f64>,
152) -> (f64, f64, f64, f64) {
153 let tms_style = tms_style.unwrap_or(true);
154 let source = source.unwrap_or(Source::Google);
155 let tile_size = tile_size.unwrap_or(512.0);
156 let mut y = y;
157 if tms_style {
160 y = pow(2., zoom as f64) - 1. - y;
161 }
162 let bl: (f64, f64) = (x * tile_size, (y + 1.) * tile_size);
164 let tr: (f64, f64) = ((x + 1.) * tile_size, y * tile_size);
166 let px_bl = px_to_ll(bl, zoom, Some(tile_size));
168 let px_tr = px_to_ll(tr, zoom, Some(tile_size));
169
170 match source {
171 Source::Google => {
172 let ll_bl = ll_to_merc(px_bl);
173 let ll_tr = ll_to_merc(px_tr);
174 (ll_bl.0, ll_bl.1, ll_tr.0, ll_tr.1)
175 }
176 _ => (px_bl.0, px_bl.1, px_tr.0, px_tr.1),
177 }
178}
179
180pub fn bbox_to_xyz_bounds(
186 bbox: (f64, f64, f64, f64),
187 zoom: u8,
188 tms_style: Option<bool>,
189 source: Option<Source>,
190 tile_size: Option<f64>,
191) -> (f64, f64, f64, f64) {
192 let tms_style = tms_style.unwrap_or(true);
193 let source = source.unwrap_or(Source::Google);
194 let tile_size = tile_size.unwrap_or(512.0);
195
196 let mut bl = (bbox.0, bbox.1); let mut tr = (bbox.2, bbox.3); if source == Source::Google {
200 bl = ll_to_merc(bl);
201 tr = ll_to_merc(tr);
202 }
203 let px_bl = ll_to_px(bl, zoom, Some(false), Some(tile_size));
204 let px_tr = ll_to_px(tr, zoom, Some(false), Some(tile_size));
205 let x = (floor(px_bl.0) / tile_size, floor((px_tr.0 - 1.0) / tile_size));
206 let y = (floor(px_tr.1) / tile_size, floor((px_bl.1 - 1.0) / tile_size));
207
208 let mut bounds =
209 (f64::min(x.0, x.1), f64::min(y.0, y.1), f64::max(x.0, x.1), f64::max(y.0, y.1));
210 if bounds.0 < 0. {
211 bounds.0 = 0.
212 }
213 if bounds.1 < 0. {
214 bounds.1 = 0.
215 }
216
217 if tms_style {
218 let zoom_diff = pow(2., zoom as f64) - 1.;
219 bounds.1 = zoom_diff - bounds.3;
220 bounds.3 = zoom_diff - bounds.1;
221 }
222
223 bounds
224}
225
226pub fn circumference_at_latitude(latitude: f64) -> f64 {
228 EARTH_CIRCUMFERENCE * cos(latitude.to_radians())
229}
230
231pub fn lng_to_mercator_x(lng: f64) -> f64 {
234 (180.0 + lng) / 360.0
235}
236
237pub fn lat_to_mercator_y(lat: f64) -> f64 {
240 (180. - (180. / PI) * log(tan(PI / 4. + (lat * PI) / 360.))) / 360.
241}
242
243pub fn altitude_to_mercator_z(altitude: f64, lat: f64) -> f64 {
246 altitude / circumference_at_latitude(lat)
247}
248
249pub fn lng_from_mercator_x(x: f64) -> f64 {
252 x * 360. - 180.
253}
254
255pub fn lat_from_mercator_y(y: f64) -> f64 {
258 let y2 = 180. - y * 360.;
259 (360. / PI) * atan(exp((y2 * PI) / 180.)) - 90.
260}
261
262pub fn altitude_from_mercator_z(z: f64, y: f64) -> f64 {
265 z * circumference_at_latitude(lat_from_mercator_y(y))
266}
267
268pub fn mercator_lat_scale(lat: f64) -> f64 {
274 1. / cos((lat * PI) / 180.)
275}