1use crate::{
3 affine::GeoTransform, errors::MapEngineError, raster::Raster, windows::Window, MAXZOOMLEVEL,
4 SUPPORTED_FORMATS,
5};
6use gdal::spatial_ref::{CoordTransform, SpatialRef};
7use std::cmp;
8use std::f64::consts::PI;
9
10const RE: f64 = 6378137.0;
11const EPSILON: f64 = 1e-14;
12pub const TILE_SIZE: usize = 256;
15
16#[derive(Debug, PartialEq)]
18pub struct Tile {
19 pub x: u32,
21 pub y: u32,
23 pub z: u32,
25 ext: Option<String>,
27}
28
29impl Tile {
30 pub fn new(x: u32, y: u32, z: u32) -> Self {
31 Self {
32 x,
33 y,
34 z,
35 ext: Some("png".to_string()),
36 }
37 }
38
39 pub fn set_extension(&mut self, ext: &str) -> Result<(), MapEngineError> {
40 if !SUPPORTED_FORMATS.contains(&ext) {
41 return Err(MapEngineError::Msg(format!(
42 "The extension {:?} is not yet supported",
43 ext
44 )));
45 }
46 self.ext = Some(ext.to_string());
47 Ok(())
48 }
49
50 pub fn to_tuple(&self) -> (u32, u32, u32) {
51 (self.x, self.y, self.z)
52 }
53
54 pub fn ul(&self) -> (f64, f64) {
56 let (xtile, ytile, zoom) = self.to_tuple();
57 let z2: f64 = 2u32.pow(zoom).into();
58 let lon_deg = (xtile as f64) / z2 * 360.0 - 180.0;
59 let lat_rad = (PI * (1.0 - 2.0 * (ytile as f64) / z2)).sinh().atan();
60 let lat_deg = lat_rad.to_degrees();
61 (lon_deg, lat_deg)
62 }
63
64 pub fn ul_xy(&self) -> (f64, f64) {
66 let (lon_deg, lat_deg) = self.ul();
67 xy(lon_deg, lat_deg)
68 }
69
70 pub fn bounds(&self) -> (f64, f64, f64, f64) {
74 let (xtile, ytile, zoom) = self.to_tuple();
75 let z2: f64 = 2u32.pow(zoom).into();
76
77 let min_lng_deg = (xtile as f64) / z2 * 360.0 - 180.0;
78 let max_lat_rad = (PI * (1.0 - 2.0 * (ytile as f64) / z2)).sinh().atan();
79 let max_lat_deg = max_lat_rad.to_degrees();
80
81 let max_lng_deg = ((xtile + 1) as f64) / z2 * 360.0 - 180.0;
82 let min_lat_rad = (PI * (1.0 - 2.0 * ((ytile + 1) as f64) / z2)).sinh().atan();
83 let min_lat_deg = min_lat_rad.to_degrees();
84 (min_lng_deg, max_lat_deg, max_lng_deg, min_lat_deg)
85 }
86
87 pub fn bounds_xy(&self) -> (f64, f64, f64, f64) {
91 let (min_lng_deg, max_lat_deg, max_lng_deg, min_lat_deg) = self.bounds();
92 let (min_x, max_y) = xy(min_lng_deg, max_lat_deg);
93 let (max_x, min_y) = xy(max_lng_deg, min_lat_deg);
94 (min_x, max_y, max_x, min_y)
95 }
96
97 pub fn vertices(&self) -> [(f64, f64); 4] {
101 let (min_x, max_y, max_x, min_y) = self.bounds();
102 [
103 (min_x, max_y), (max_x, max_y), (max_x, min_y), (min_x, min_y), ]
108 }
109
110 pub fn zoom_out(&self, zoom: Option<u32>) -> Option<Self> {
112 if self.z == 0 {
113 return None;
114 };
115 let target_zoom = zoom.unwrap_or(self.z - 1);
116 let mut return_tile = Tile::new(self.x, self.y, self.z);
117 while return_tile.z > target_zoom {
118 let (xtile, ytile, ztile) = (return_tile.x, return_tile.y, return_tile.z);
119 let newx = if xtile % 2 == 0 {
120 xtile / 2
121 } else {
122 (xtile - 1) / 2
123 };
124 let newy = if ytile % 2 == 0 {
125 ytile / 2
126 } else {
127 (ytile - 1) / 2
128 };
129 let newz = ztile - 1;
130 return_tile = Tile::new(newx, newy, newz);
131 }
132 Some(return_tile)
133 }
134
135 pub fn zoom_in(&self, zoom: Option<u32>) -> Option<Vec<Self>> {
137 if self.z == MAXZOOMLEVEL {
138 return None;
139 }
140 let target_zoom = zoom.unwrap_or(self.z + 1);
141 let mut tiles = vec![Tile::new(self.x, self.y, self.z)];
142 while tiles[0].z < target_zoom {
143 tiles = tiles
144 .iter()
145 .map(|tile| {
146 [
147 Tile::new(tile.x * 2, tile.y * 2, tile.z + 1),
148 Tile::new(tile.x * 2 + 1, tile.y * 2, tile.z + 1),
149 Tile::new(tile.x * 2 + 1, tile.y * 2 + 1, tile.z + 1),
150 Tile::new(tile.x * 2, tile.y * 2 + 1, tile.z + 1),
151 ]
152 })
153 .flatten()
154 .collect()
155 }
156 Some(tiles)
157 }
158
159 pub fn from_lat_lng(lng: f64, lat: f64, zoom: u32) -> Self {
161 let (x, y) = _xy(lng, lat);
162 let z2 = 2u32.pow(zoom);
163 let xtile = if x <= 0.0 {
164 0u32
165 } else if x >= 1.0 {
166 z2 - 1
167 } else {
168 ((x + EPSILON) * (z2 as f64)).floor() as u32
169 };
170
171 let ytile = if y <= 0.0 {
172 0u32
173 } else if y >= 1.0 {
174 z2 - 1
175 } else {
176 ((y + EPSILON) * (z2 as f64)).floor() as u32
177 };
178
179 Self::new(xtile, ytile, zoom)
180 }
181
182 pub fn to_window(&self, raster: &Raster) -> Result<(Window, bool), MapEngineError> {
201 let geo = raster.geo();
202 let spatial_ref = raster.spatial_ref()?;
203
204 let src_ref_code = spatial_ref.auth_code().unwrap_or(3857) as u32;
205 let src_spatial_units = spatial_ref
206 .linear_units_name()
207 .unwrap_or_else(|_| "metre".to_string());
208
209 let wgs84_crs = gdal::spatial_ref::SpatialRef::from_epsg(4326)?;
210 let src_crs = SpatialRef::from_epsg(src_ref_code)?;
211
212 let vertex_trans = gdal::spatial_ref::CoordTransform::new(&wgs84_crs, &src_crs)?;
213
214 let vertices = self.vertices();
215 let mut xs = [vertices[0].0, vertices[1].0, vertices[2].0, vertices[3].0];
216
217 let mut ys = [vertices[0].1, vertices[1].1, vertices[2].1, vertices[3].1];
218 let mut zs = [0.0f64; 4];
219
220 vertex_trans.transform_coords(&mut ys, &mut xs, &mut zs)?;
222
223 let offset = get_vertices_offset(self.z, &src_spatial_units)?;
224
225 let row_cols = get_row_cols(&xs, &ys, &offset, geo, &src_spatial_units);
226
227 let is_skewed = (row_cols[0].0 != row_cols[1].0)
228 || (row_cols[2].0 != row_cols[3].0 || (row_cols[0].1 != row_cols[3].1))
229 || (row_cols[1].1 != row_cols[2].1);
230
231 let win = Window::new(
232 cmp::min(row_cols[0].1, row_cols[3].1) as isize,
233 cmp::min(row_cols[0].0, row_cols[1].0) as isize,
234 cmp::max(row_cols[1].1 - row_cols[0].1, row_cols[2].1 - row_cols[3].1) as usize + 1,
235 cmp::max(row_cols[3].0 - row_cols[0].0, row_cols[2].0 - row_cols[1].0) as usize + 1,
236 );
237
238 Ok((win, is_skewed))
239 }
240}
241
242fn get_row_cols(
243 xs: &[f64],
244 ys: &[f64],
245 offset: &[(f64, f64)],
246 geo: &GeoTransform,
247 src_spatial_units: &str,
248) -> Vec<(i32, i32)> {
249 xs.iter()
250 .zip(ys)
251 .zip(offset)
252 .map(|((x, y), (x_off, y_off))| {
253 if src_spatial_units == "metre" {
254 geo.rowcol(y + y_off, x + x_off).unwrap()
256 } else {
257 geo.rowcol(x + x_off, y + y_off).unwrap()
258 }
259 })
260 .collect()
261}
262
263fn get_vertices_offset(
264 zoom: u32,
265 src_spatial_units: &str,
266) -> Result<[(f64, f64); 4], MapEngineError> {
267 let mercator_crs = SpatialRef::from_epsg(3857)?;
268 let wgs84_crs = gdal::spatial_ref::SpatialRef::from_epsg(4326).unwrap();
269 let tile_res_trans = CoordTransform::new(&mercator_crs, &wgs84_crs).unwrap();
270 let tile_z2: f64 = 2u32.pow(zoom).into();
271 let tile_res = (2.0 * PI * RE / TILE_SIZE as f64) / tile_z2;
272 let tile_res_m = ([tile_res], [tile_res], [0.0]);
273 let mut tile_res_deg = ([tile_res], [tile_res], [0.0]);
274
275 tile_res_trans
276 .transform_coords(
277 &mut tile_res_deg.0,
278 &mut tile_res_deg.1,
279 &mut tile_res_deg.2,
280 )
281 .unwrap();
282
283 let offset_prop = 0.01;
285 let offset = if src_spatial_units == "metre" {
286 [
288 (
289 -tile_res_m.0[0] * offset_prop,
290 tile_res_m.1[0] * offset_prop,
291 ),
292 (
293 -tile_res_m.0[0] * offset_prop,
294 -tile_res_m.1[0] * offset_prop,
295 ),
296 (
297 tile_res_m.0[0] * offset_prop,
298 -tile_res_m.1[0] * offset_prop,
299 ),
300 (tile_res_m.0[0] * offset_prop, tile_res_m.1[0] * offset_prop),
301 ]
302 } else {
303 [
304 (
305 tile_res_deg.1[0] * offset_prop,
306 -tile_res_deg.0[0] * offset_prop,
307 ),
308 (
309 -tile_res_deg.1[0] * offset_prop,
310 -tile_res_deg.0[0] * offset_prop,
311 ),
312 (
313 -tile_res_deg.1[0] * offset_prop,
314 tile_res_deg.0[0] * offset_prop,
315 ),
316 (
317 tile_res_deg.1[0] * offset_prop,
318 tile_res_deg.0[0] * offset_prop,
319 ),
320 ]
321 };
322 Ok(offset)
323}
324
325fn xy(lng: f64, lat: f64) -> (f64, f64) {
326 let x = RE * lng.to_radians();
327 let y = if lat <= -90.0 {
328 std::f64::NEG_INFINITY
329 } else if lat >= 90.0 {
330 std::f64::INFINITY
331 } else {
332 RE * (PI * 0.25 + lat.to_radians() * 0.5).tan().ln()
333 };
334 (x, y)
335}
336
337fn _xy(lng: f64, lat: f64) -> (f64, f64) {
338 let x = lng / 360.0 + 0.5;
339 let sinlat = lat.to_radians().sin();
340 let y = 0.5 - 0.25 * (sinlat + 1.0).ln() / (1.0 - sinlat) / std::f64::consts::PI;
341 (x, y)
342}
343
344#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn test_mercator_xy() {
375 let tile = Tile::new(1, 2, 3);
376 let (lng, lat) = tile.ul();
377 assert_eq!(xy(lng, lat), (-15028131.257091932, 10018754.171394626));
378 }
379}