slippy_map_tiles/
lib.rs

1//! Abstractions and functions for working with OpenStreetMap (etc.) tiles
2//!
3//! # Examples
4//! ```
5//! use slippy_map_tiles::Tile;
6//!
7//! let t = Tile::new(6, 35, 23).unwrap();
8//!
9//! ```
10//!
11//! You cannot create invalid tiles
12//! ```
13//! # use slippy_map_tiles::Tile;
14//! assert!(Tile::new(0, 3, 3).is_none());
15//! ```
16#[macro_use]
17extern crate lazy_static;
18extern crate regex;
19
20#[cfg(feature = "world_file")]
21extern crate world_image_file;
22
23use regex::Regex;
24use std::borrow::Borrow;
25use std::convert::TryFrom;
26use std::fs::File;
27use std::io::{BufRead, BufReader, Seek, SeekFrom};
28use std::ops::Deref;
29use std::str::FromStr;
30
31#[cfg(feature = "world_file")]
32use world_image_file::WorldFile;
33
34#[cfg(test)]
35mod tests;
36
37/// A single tile.
38#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
39pub struct Tile {
40    zoom: u8,
41    x: u32,
42    y: u32,
43}
44
45impl Tile {
46    /// Constucts a Tile with the following zoom, x and y values.
47    ///
48    /// Returns None if the x/y are invalid for that zoom level, or if the zoom is >= 100.
49    /// # Examples
50    /// ```
51    /// # use slippy_map_tiles::Tile;
52    /// assert!(Tile::new(0, 3, 3).is_none());
53    /// ```
54    pub fn new(zoom: u8, x: u32, y: u32) -> Option<Tile> {
55        if zoom >= 100 {
56            None
57        } else if x < 2u32.pow(zoom as u32) && y < 2u32.pow(zoom as u32) {
58            Some(Tile {
59                zoom: zoom,
60                x: x,
61                y: y,
62            })
63        } else {
64            None
65        }
66    }
67
68    /// zoom of this tile
69    pub fn zoom(&self) -> u8 {
70        self.zoom
71    }
72
73    /// X value of this tile
74    pub fn x(&self) -> u32 {
75        self.x
76    }
77
78    /// Y value of tile
79    pub fn y(&self) -> u32 {
80        self.y
81    }
82
83    /// Constucts a Tile with the following zoom, x and y values based on a TMS URL.
84    /// Returns None if the TMS url is invalid, or those
85    ///
86    /// # Examples
87    /// ```
88    /// # use slippy_map_tiles::Tile;
89    /// let t = Tile::from_tms("/10/547/380.png");
90    /// assert_eq!(t, Tile::new(10, 547, 380));
91    /// assert_eq!(Tile::from_tms("foobar"), None);
92    /// ```
93    pub fn from_tms(tms: &str) -> Option<Tile> {
94        lazy_static! {
95            static ref RE: Regex = Regex::new(
96                "/?(?P<zoom>[0-9]?[0-9])/(?P<x>[0-9]{1,10})/(?P<y>[0-9]{1,10})(\\.[a-zA-Z]{3,4})?$"
97            )
98            .unwrap();
99        }
100
101        let caps = RE.captures(tms)?;
102
103        let zoom = caps.name("zoom");
104        let x = caps.name("x");
105        let y = caps.name("y");
106        if zoom.is_none() || x.is_none() || y.is_none() {
107            return None;
108        }
109        let zoom = zoom.unwrap().as_str().parse();
110        let x = x.unwrap().as_str().parse();
111        let y = y.unwrap().as_str().parse();
112
113        if zoom.is_err() || x.is_err() || y.is_err() {
114            return None;
115        }
116        let zoom: u8 = zoom.unwrap();
117        let x: u32 = x.unwrap();
118        let y: u32 = y.unwrap();
119
120        Tile::new(zoom, x, y)
121    }
122
123    // TODO Add from_tc to parse the directory hiearchy so we can turn a filename in to a tile.
124    // TODO Add from_ts to parse the directory hiearchy so we can turn a filename in to a tile.
125
126    /// Returns the parent tile for this tile, i.e. the tile at the `zoom-1` that this tile is
127    /// inside.
128    ///
129    /// ```
130    /// # use slippy_map_tiles::Tile;
131    /// assert_eq!(Tile::new(1, 0, 0).unwrap().parent(), Tile::new(0, 0, 0));
132    /// ```
133    /// None if there is no parent, which is at zoom 0.
134    ///
135    /// ```
136    /// # use slippy_map_tiles::Tile;
137    /// assert_eq!(Tile::new(0, 0, 0).unwrap().parent(), None);
138    /// ```
139    pub fn parent(&self) -> Option<Tile> {
140        match self.zoom {
141            0 => {
142                // zoom 0, no parent
143                None
144            }
145            _ => Tile::new(self.zoom - 1, self.x / 2, self.y / 2),
146        }
147    }
148
149    /// Returns the subtiles (child) tiles for this tile. The 4 tiles at zoom+1 which cover this
150    /// tile. Returns None if this is at the maximum permissable zoom level, and hence there are no
151    /// subtiles.
152    ///
153    /// ```
154    /// # use slippy_map_tiles::Tile;
155    /// let t = Tile::new(0, 0, 0).unwrap();
156    /// let subtiles: [Tile; 4] = t.subtiles().unwrap();
157    /// assert_eq!(subtiles[0], Tile::new(1, 0, 0).unwrap());
158    /// assert_eq!(subtiles[1], Tile::new(1, 1, 0).unwrap());
159    /// assert_eq!(subtiles[2], Tile::new(1, 0, 1).unwrap());
160    /// assert_eq!(subtiles[3], Tile::new(1, 1, 1).unwrap());
161    /// ```
162    pub fn subtiles(&self) -> Option<[Tile; 4]> {
163        match self.zoom {
164            std::u8::MAX => None,
165            _ => {
166                let z = self.zoom + 1;
167                let x = 2 * self.x;
168                let y = 2 * self.y;
169                Some([
170                    Tile {
171                        zoom: z,
172                        x: x,
173                        y: y,
174                    },
175                    Tile {
176                        zoom: z,
177                        x: x + 1,
178                        y: y,
179                    },
180                    Tile {
181                        zoom: z,
182                        x: x,
183                        y: y + 1,
184                    },
185                    Tile {
186                        zoom: z,
187                        x: x + 1,
188                        y: y + 1,
189                    },
190                ])
191            }
192        }
193    }
194
195    /// Iterate on all child tiles of this tile
196    pub fn all_subtiles_iter(&self) -> AllSubTilesIterator {
197        AllSubTilesIterator::new_from_tile(&self)
198    }
199
200    /// Returns the LatLon for the centre of this tile.
201    pub fn centre_point(&self) -> LatLon {
202        tile_nw_lat_lon(self.zoom, (self.x as f32) + 0.5, (self.y as f32) + 0.5)
203    }
204
205    /// Returns the LatLon for the centre of this tile.
206    pub fn center_point(&self) -> LatLon {
207        self.centre_point()
208    }
209
210    /// Returns the LatLon of the top left, i.e. north west corner, of this tile.
211    pub fn nw_corner(&self) -> LatLon {
212        tile_nw_lat_lon(self.zoom, self.x as f32, self.y as f32)
213    }
214
215    /// Returns the LatLon of the top right, i.e. north east corner, of this tile.
216    pub fn ne_corner(&self) -> LatLon {
217        tile_nw_lat_lon(self.zoom, (self.x as f32) + 1.0, self.y as f32)
218    }
219
220    /// Returns the LatLon of the bottom left, i.e. south west corner, of this tile.
221    pub fn sw_corner(&self) -> LatLon {
222        tile_nw_lat_lon(self.zoom, self.x as f32, (self.y as f32) + 1.0)
223    }
224
225    /// Returns the LatLon of the bottom right, i.e. south east corner, of this tile.
226    pub fn se_corner(&self) -> LatLon {
227        tile_nw_lat_lon(self.zoom, (self.x as f32) + 1.0, (self.y as f32) + 1.0)
228    }
229
230    pub fn top(&self) -> f32 {
231        self.nw_corner().lat
232    }
233    pub fn bottom(&self) -> f32 {
234        self.sw_corner().lat
235    }
236    pub fn left(&self) -> f32 {
237        self.nw_corner().lon
238    }
239    pub fn right(&self) -> f32 {
240        self.se_corner().lon
241    }
242
243    /// Returns the TC (TileCache) path for storing this tile.
244    pub fn tc_path<T: std::fmt::Display>(&self, ext: T) -> String {
245        let tc = xy_to_tc(self.x, self.y);
246        format!(
247            "{}/{}/{}/{}/{}/{}/{}.{}",
248            self.zoom, tc[0], tc[1], tc[2], tc[3], tc[4], tc[5], ext
249        )
250    }
251
252    /// Returns the MP (MapProxy) path for storing this tile.
253    pub fn mp_path<T: std::fmt::Display>(&self, ext: T) -> String {
254        let mp = xy_to_mp(self.x, self.y);
255        format!(
256            "{}/{}/{}/{}/{}.{}",
257            self.zoom, mp[0], mp[1], mp[2], mp[3], ext
258        )
259    }
260
261    /// Returns the TS (TileStash safe) path for storing this tile.
262    pub fn ts_path<T: std::fmt::Display>(&self, ext: T) -> String {
263        let ts = xy_to_ts(self.x, self.y);
264        format!(
265            "{}/{}/{}/{}/{}.{}",
266            self.zoom, ts[0], ts[1], ts[2], ts[3], ext
267        )
268    }
269
270    /// Returns the Z/X/Y representation of this tile
271    pub fn zxy(&self) -> String {
272        format!("{}/{}/{}", self.zoom, self.x, self.y)
273    }
274
275    /// Returns the ZXY path for storing this tile.
276    pub fn zxy_path<T: std::fmt::Display>(&self, ext: T) -> String {
277        format!("{}/{}/{}.{}", self.zoom, self.x, self.y, ext)
278    }
279
280    /// Returns the ModTileMetatile path for storing this tile
281    pub fn mt_path<T: std::fmt::Display>(&self, ext: T) -> String {
282        let tc = xy_to_mt(self.x, self.y);
283        format!(
284            "{}/{}/{}/{}/{}/{}.{}",
285            self.zoom, tc[0], tc[1], tc[2], tc[3], tc[4], ext
286        )
287    }
288
289    /// Returns an iterator that yields all the tiles possible, starting from `0/0/0`. Tiles are
290    /// generated in a breath first manner, with all zoom 1 tiles before zoom 2 etc.
291    ///
292    /// ```
293    /// # use slippy_map_tiles::Tile;
294    /// let mut all_tiles_iter = Tile::all();
295    /// ```
296    pub fn all() -> AllTilesIterator {
297        AllTilesIterator {
298            next_zoom: 0,
299            next_zorder: 0,
300        }
301    }
302
303    /// Returns an iterator that yields all the tiles from zoom 0 down to, and including, all the
304    /// tiles at `max_zoom` zoom level.  Tiles are
305    /// generated in a breath first manner, with all zoom 1 tiles before zoom 2 etc.
306    pub fn all_to_zoom(max_zoom: u8) -> AllTilesToZoomIterator {
307        AllTilesToZoomIterator {
308            max_zoom: max_zoom,
309            next_zoom: 0,
310            next_x: 0,
311            next_y: 0,
312        }
313    }
314
315    /// The BBox for this tile.
316    pub fn bbox(&self) -> BBox {
317        let nw = self.nw_corner();
318        let se = self.se_corner();
319
320        BBox::new_from_points(&nw, &se)
321    }
322
323    pub fn metatile(&self, scale: u8) -> Option<Metatile> {
324        Metatile::new(scale, self.zoom(), self.x(), self.y())
325    }
326
327    pub fn modtile_metatile(&self) -> Option<ModTileMetatile> {
328        ModTileMetatile::new(self.zoom(), self.x(), self.y())
329    }
330
331    #[cfg(feature = "world_file")]
332    /// Return the World File (in EPSG:3857 / Web Mercator SRID) for this tile
333    pub fn world_file(&self) -> WorldFile {
334        let total_merc_width = 20037508.342789244;
335        let tile_merc_width = (2. * total_merc_width) / 2f64.powi(self.zoom as i32);
336        let scale = tile_merc_width / 256.;
337
338        WorldFile {
339            x_scale: scale,
340            y_scale: -scale,
341
342            x_skew: 0.,
343            y_skew: 0.,
344
345            x_coord: tile_merc_width * (self.x as f64) - total_merc_width,
346            y_coord: -tile_merc_width * (self.y as f64) + total_merc_width,
347        }
348    }
349}
350
351impl FromStr for Tile {
352    type Err = &'static str;
353
354    fn from_str(s: &str) -> Result<Self, Self::Err> {
355        lazy_static! {
356            static ref TILE_RE: Regex =
357                Regex::new("^(?P<zoom>[0-9]?[0-9])/(?P<x>[0-9]{1,10})/(?P<y>[0-9]{1,10})$")
358                    .unwrap();
359        }
360
361        let caps = TILE_RE.captures(s);
362
363        if caps.is_none() {
364            return Err("Tile Z/X/Y regex didn't match");
365        }
366        let caps = caps.unwrap();
367
368        // If the regex matches, then none of these should fail, right?
369        let zoom = caps.name("zoom").unwrap().as_str().parse().unwrap();
370        let x = caps.name("x").unwrap().as_str().parse().unwrap();
371        let y = caps.name("y").unwrap().as_str().parse().unwrap();
372
373        match Tile::new(zoom, x, y) {
374            None => {
375                // Invalid x or y for the zoom
376                Err("Invalid X or Y for this zoom")
377            }
378            Some(t) => Ok(t),
379        }
380    }
381}
382
383/// Iterates over all the tiles in the world.
384pub struct AllTilesIterator {
385    next_zoom: u8,
386    next_zorder: u64,
387}
388
389impl Iterator for AllTilesIterator {
390    type Item = Tile;
391
392    fn next(&mut self) -> Option<Tile> {
393        let zoom = self.next_zoom;
394        let (x, y) = zorder_to_xy(self.next_zorder);
395        let tile = Tile::new(zoom, x, y);
396
397        let max_tile_no = 2u32.pow(zoom as u32) - 1;
398        if x == max_tile_no && y == max_tile_no {
399            // we're at the end
400            self.next_zoom = zoom + 1;
401            self.next_zorder = 0;
402        } else {
403            self.next_zorder += 1;
404        }
405
406        tile
407    }
408}
409
410/// Iterates over all the tiles from 0/0/0 up to, and including, `max_zoom`.
411pub struct AllTilesToZoomIterator {
412    max_zoom: u8,
413    next_zoom: u8,
414    next_x: u32,
415    next_y: u32,
416}
417
418fn remaining_in_this_zoom(next_zoom: u8, next_x: u32, next_y: u32) -> Option<usize> {
419    if next_zoom == 0 && next_x == 0 && next_y == 0 {
420        return Some(1);
421    }
422
423    let max_tile_no = 2u32.pow(next_zoom as u32);
424    let remaining_in_column = max_tile_no - next_y;
425    let remaining_in_column = remaining_in_column as usize;
426    let remaining_rows = max_tile_no - next_x - 1;
427    let remaining_rows = remaining_rows as usize;
428
429    let remaining_after_this_column = remaining_rows.checked_mul(max_tile_no as usize)?;
430
431    remaining_in_column.checked_add(remaining_after_this_column)
432}
433
434impl Iterator for AllTilesToZoomIterator {
435    type Item = Tile;
436
437    fn next(&mut self) -> Option<Tile> {
438        if self.next_zoom > self.max_zoom {
439            return None;
440        }
441        let tile = Tile::new(self.next_zoom, self.next_x, self.next_y);
442        let max_tile_no = 2u32.pow(self.next_zoom as u32) - 1;
443        if self.next_y < max_tile_no {
444            self.next_y += 1;
445        } else if self.next_x < max_tile_no {
446            self.next_x += 1;
447            self.next_y = 0;
448        } else if self.next_zoom < std::u8::MAX {
449            self.next_zoom += 1;
450            self.next_x = 0;
451            self.next_y = 0;
452        }
453
454        tile
455    }
456
457    fn size_hint(&self) -> (usize, Option<usize>) {
458        if self.next_zoom > self.max_zoom {
459            return (0, Some(0));
460        }
461
462        let remaining_in_this_level =
463            remaining_in_this_zoom(self.next_zoom, self.next_x, self.next_y);
464        if remaining_in_this_level.is_none() {
465            return (std::usize::MAX, None);
466        }
467        let remaining_in_this_level = remaining_in_this_level.unwrap();
468
469        let mut total: usize = remaining_in_this_level as usize;
470        for i in (self.next_zoom + 1)..(self.max_zoom + 1) {
471            let tiles_this_zoom = num_tiles_in_zoom(i);
472            if tiles_this_zoom.is_none() {
473                return (std::usize::MAX, None);
474            }
475
476            let tiles_this_zoom = tiles_this_zoom.unwrap();
477
478            let new_total = total.checked_add(tiles_this_zoom);
479            if new_total.is_none() {
480                return (std::usize::MAX, None);
481            }
482            total = new_total.unwrap();
483        }
484
485        // If we've got to here, we know how big it is
486        (total, Some(total))
487    }
488}
489
490pub struct AllSubTilesIterator {
491    _tiles: Vec<Tile>,
492}
493
494impl AllSubTilesIterator {
495    pub fn new_from_tile(base_tile: &Tile) -> Self {
496        let new_tiles = match base_tile.subtiles() {
497            None => Vec::new(),
498            Some(t) => vec![t[0], t[1], t[2], t[3]],
499        };
500        AllSubTilesIterator { _tiles: new_tiles }
501    }
502}
503
504impl Iterator for AllSubTilesIterator {
505    type Item = Tile;
506
507    fn next(&mut self) -> Option<Tile> {
508        if self._tiles.is_empty() {
509            return None;
510        }
511        let next = self._tiles.remove(0);
512        if let Some(subtiles) = next.subtiles() {
513            self._tiles.extend_from_slice(&subtiles);
514        }
515        Some(next)
516    }
517}
518
519/// Metatiles are NxN tiles
520#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
521pub struct Metatile {
522    scale: u8,
523    zoom: u8,
524    x: u32,
525    y: u32,
526}
527
528impl Metatile {
529    pub fn new(scale: u8, zoom: u8, x: u32, y: u32) -> Option<Self> {
530        if !scale.is_power_of_two() {
531            return None;
532        }
533        if zoom >= 100 {
534            None
535        } else if x < 2u32.pow(zoom as u32) && y < 2u32.pow(zoom as u32) {
536            let s = scale as u32;
537            let x = (x / s) * s;
538            let y = (y / s) * s;
539            Some(Metatile {
540                scale: scale,
541                zoom: zoom,
542                x: x,
543                y: y,
544            })
545        } else {
546            None
547        }
548    }
549
550    pub fn scale(&self) -> u8 {
551        self.scale
552    }
553
554    pub fn zoom(&self) -> u8 {
555        self.zoom
556    }
557
558    /// What is the width or height of this metatile. For small zoom numbers (e.g. z1), there will
559    /// not be the full `scale` tiles across.
560    pub fn size(&self) -> u8 {
561        let num_tiles_in_zoom = 2u32.pow(self.zoom as u32);
562        if num_tiles_in_zoom < (self.scale as u32) {
563            num_tiles_in_zoom as u8
564        } else {
565            self.scale
566        }
567    }
568
569    /// Returns the LatLon for the centre of this metatile.
570    pub fn centre_point(&self) -> LatLon {
571        tile_nw_lat_lon(
572            self.zoom,
573            (self.x as f32) + (self.size() as f32) / 2.,
574            (self.y as f32) + (self.size() as f32) / 2.,
575        )
576    }
577
578    /// Returns the LatLon for the centre of this metatile.
579    pub fn center_point(&self) -> LatLon {
580        self.centre_point()
581    }
582
583    /// Returns the LatLon of the top left, i.e. north west corner, of this metatile.
584    pub fn nw_corner(&self) -> LatLon {
585        tile_nw_lat_lon(self.zoom, self.x as f32, self.y as f32)
586    }
587
588    /// Returns the LatLon of the top right, i.e. north east corner, of this metatile.
589    pub fn ne_corner(&self) -> LatLon {
590        tile_nw_lat_lon(
591            self.zoom,
592            (self.x + self.size() as u32) as f32,
593            self.y as f32,
594        )
595    }
596
597    /// Returns the LatLon of the bottom left, i.e. south west corner, of this metatile.
598    pub fn sw_corner(&self) -> LatLon {
599        tile_nw_lat_lon(
600            self.zoom,
601            self.x as f32,
602            (self.y + self.size() as u32) as f32,
603        )
604    }
605
606    /// Returns the LatLon of the bottom right, i.e. south east corner, of this metatile.
607    pub fn se_corner(&self) -> LatLon {
608        tile_nw_lat_lon(
609            self.zoom,
610            (self.x + self.size() as u32) as f32,
611            (self.y + self.size() as u32) as f32,
612        )
613    }
614
615    /// X value of this metatile
616    pub fn x(&self) -> u32 {
617        self.x
618    }
619
620    /// Y value of metatile
621    pub fn y(&self) -> u32 {
622        self.y
623    }
624
625    pub fn tiles(&self) -> Vec<Tile> {
626        let size = self.size() as u32;
627        (0..(size * size))
628            .map(|n| {
629                // oh for a divmod
630                let (i, j) = (n / size, n % size);
631                // being cheeky and skipping the usuall Tile::new checks here, since we know it's valid
632                Tile {
633                    zoom: self.zoom,
634                    x: self.x + i,
635                    y: self.y + j,
636                }
637            })
638            .collect()
639    }
640
641    pub fn all(scale: u8) -> MetatilesIterator {
642        assert!(scale.is_power_of_two());
643        MetatilesIterator::all(scale)
644    }
645}
646
647impl FromStr for Metatile {
648    type Err = ();
649
650    fn from_str(s: &str) -> Result<Self, Self::Err> {
651        lazy_static! {
652            static ref METATILE_RE: Regex = Regex::new(
653                "^(?P<scale>[0-9]+) (?P<zoom>[0-9]?[0-9])/(?P<x>[0-9]{1,10})/(?P<y>[0-9]{1,10})$"
654            )
655            .unwrap();
656        }
657
658        let caps = METATILE_RE.captures(s);
659
660        if caps.is_none() {
661            return Err(());
662        }
663        let caps = caps.unwrap();
664
665        // If the regex matches, then none of these should fail, right?
666        let scale = caps.name("scale").unwrap().as_str().parse().unwrap();
667        let zoom = caps.name("zoom").unwrap().as_str().parse().unwrap();
668        let x = caps.name("x").unwrap().as_str().parse().unwrap();
669        let y = caps.name("y").unwrap().as_str().parse().unwrap();
670
671        match Metatile::new(scale, zoom, x, y) {
672            None => {
673                // Invalid x or y for the zoom
674                Err(())
675            }
676            Some(mt) => Ok(mt),
677        }
678    }
679}
680
681/// Iterates over all the metatiles in the world.
682#[derive(Debug)]
683pub struct MetatilesIterator {
684    scale: u8,
685    curr_zoom: u8,
686    maxzoom: u8,
687    curr_zorder: u64,
688    bbox: Option<BBox>,
689
690    // In metatile coords, i.e. x/scale
691    curr_zoom_width_height: Option<(u32, u32)>,
692    curr_zoom_start_xy: Option<(u32, u32)>,
693
694    // If we're reading from a file
695    total: Option<usize>,
696    tile_list_file: Option<BufReader<File>>,
697}
698
699impl MetatilesIterator {
700    pub fn all(scale: u8) -> Self {
701        MetatilesIterator {
702            scale: scale,
703            curr_zoom: 0,
704            curr_zorder: 0,
705            bbox: None,
706            maxzoom: 32,
707            curr_zoom_width_height: None,
708            curr_zoom_start_xy: None,
709            total: None,
710            tile_list_file: None,
711        }
712    }
713
714    pub fn new_for_bbox(scale: u8, bbox: &BBox) -> Self {
715        MetatilesIterator::new_for_bbox_zoom(scale, &Some(bbox.clone()), 0, 32)
716    }
717
718    /// `None` for bbox means 'whole world'
719    pub fn new_for_bbox_zoom(scale: u8, bbox: &Option<BBox>, minzoom: u8, maxzoom: u8) -> Self {
720        let mut it = MetatilesIterator {
721            scale: scale,
722            curr_zoom: minzoom,
723            curr_zorder: 0,
724            bbox: bbox.clone(),
725            maxzoom: maxzoom,
726            curr_zoom_width_height: None,
727            curr_zoom_start_xy: None,
728            total: None,
729            tile_list_file: None,
730        };
731        it.set_zoom_width_height();
732        it.set_zoom_start_xy();
733
734        it
735    }
736
737    pub fn new_from_filelist(filename: String) -> Self {
738        let mut file = BufReader::new(File::open(&filename).unwrap());
739        file.seek(SeekFrom::Start(0)).unwrap();
740
741        // we're intentionally ignore usize overflow. If you have that many lines in a file,
742        // you're probably doing something wrong.
743        let total = file.lines().count();
744
745        let file = BufReader::new(File::open(filename).unwrap());
746
747        MetatilesIterator {
748            scale: 0,
749            curr_zoom: 0,
750            curr_zorder: 0,
751            bbox: None,
752            maxzoom: 0,
753            curr_zoom_width_height: None,
754            curr_zoom_start_xy: None,
755            total: Some(total),
756            tile_list_file: Some(file),
757        }
758    }
759
760    /// Update the `self.curr_zoom_width_height` variable with the correct value for this zoom
761    /// (`self.curr_zoom`)
762    fn set_zoom_width_height(&mut self) {
763        if let Some(ref bbox) = self.bbox {
764            let scale = self.scale as u32;
765            let zoom = self.curr_zoom;
766            // TODO is this x/y lat/lon the right way around?
767            let (x1, y1) = lat_lon_to_tile(bbox.top, bbox.left, zoom);
768            let (x1, y1) = (x1 / scale, y1 / scale);
769            let (x2, y2) = lat_lon_to_tile(bbox.bottom, bbox.right, zoom);
770            let (x2, y2) = (x2 / scale, y2 / scale);
771
772            let width = x2 - x1 + 1;
773            let height = y2 - y1 + 1;
774
775            self.curr_zoom_width_height = Some((width, height));
776        }
777    }
778
779    fn set_zoom_start_xy(&mut self) {
780        if self.bbox.is_none() {
781            return;
782        }
783
784        let top = match self.bbox {
785            None => 90.,
786            Some(ref b) => b.top,
787        };
788        let left = match self.bbox {
789            None => -180.,
790            Some(ref b) => b.left,
791        };
792        // TODO is this x/y lat/lon the right way around?
793        let (x1, y1) = lat_lon_to_tile(top, left, self.curr_zoom);
794        self.curr_zoom_start_xy = Some((x1 / self.scale as u32, y1 / self.scale as u32));
795    }
796
797    fn next_from_zorder(&mut self) -> Option<Metatile> {
798        // have to set a value, but we're never going to read it
799        #[allow(unused_assignments)]
800        let mut zoom = 0;
801        #[allow(unused_assignments)]
802        let mut x = 0;
803        #[allow(unused_assignments)]
804        let mut y = 0;
805
806        let scale = self.scale as u32;
807
808        loop {
809            if self.curr_zoom > self.maxzoom {
810                // We're finished
811                return None;
812            }
813
814            zoom = self.curr_zoom;
815            let (width, height) = match self.curr_zoom_width_height {
816                None => {
817                    let max_num = 2u32.pow(zoom as u32);
818                    let mut max = max_num / scale;
819                    if max_num % scale > 0 {
820                        max += 1
821                    }
822                    (max, max)
823                }
824                Some((width, height)) => (width, height),
825            };
826
827            let max_zorder_for_zoom = xy_to_zorder(width - 1, height - 1);
828
829            let (i, j) = zorder_to_xy(self.curr_zorder);
830            let bits = match self.curr_zoom_start_xy {
831                None => (i, j),
832                Some(start) => (start.0 + i, start.1 + j),
833            };
834            x = bits.0;
835            y = bits.1;
836
837            if self.curr_zorder > max_zorder_for_zoom {
838                // Next zoom
839                // we're at the end
840                self.curr_zoom = zoom + 1;
841                self.curr_zorder = 0;
842                self.set_zoom_start_xy();
843                self.set_zoom_width_height();
844            } else if i > width || j > height {
845                // If the bbox is non-square, there will be X (or Y) tiles which are outside
846                // the bbox. Rather than go to the next zoom level, we want to contine to look at
847                // the next tile in order, and keep going until we get a tile that's inside the
848                // bbox.  to the next tile
849                self.curr_zorder += 1;
850                continue;
851            } else {
852                // This z order is OK
853                self.curr_zorder += 1;
854                break;
855            }
856        }
857
858        let (x, y) = (x * scale, y * scale);
859        Metatile::new(self.scale, zoom, x, y)
860    }
861
862    fn next_from_file(&mut self) -> Option<Metatile> {
863        let mut s = String::new();
864        if let Some(ref mut file) = self.tile_list_file {
865            file.read_line(&mut s).unwrap();
866        }
867        // remove trailing newline
868        let s = s.trim_end();
869
870        s.parse().ok()
871    }
872
873    pub fn total(&self) -> Option<usize> {
874        self.total
875    }
876}
877
878impl Iterator for MetatilesIterator {
879    type Item = Metatile;
880
881    fn next(&mut self) -> Option<Self::Item> {
882        if self.tile_list_file.is_some() {
883            self.next_from_file()
884        } else {
885            self.next_from_zorder()
886        }
887    }
888}
889
890/// Metatiles as found by mod_tile, always 8x8
891#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
892pub struct ModTileMetatile {
893    inner: Metatile,
894}
895
896impl ModTileMetatile {
897    pub fn new(zoom: u8, x: u32, y: u32) -> Option<Self> {
898        match Metatile::new(8, zoom, x, y) {
899            None => None,
900            Some(inner) => Some(ModTileMetatile { inner: inner }),
901        }
902    }
903
904    /// Returns the mod_tile path for storing this metatile
905    pub fn path<T: std::fmt::Display>(&self, ext: T) -> String {
906        let mt = xy_to_mt(self.inner.x, self.inner.y);
907        format!(
908            "{}/{}/{}/{}/{}/{}.{}",
909            self.inner.zoom, mt[0], mt[1], mt[2], mt[3], mt[4], ext
910        )
911    }
912
913    /// X value of this metatile
914    pub fn x(&self) -> u32 {
915        self.inner.x
916    }
917
918    /// Y value of metatile
919    pub fn y(&self) -> u32 {
920        self.inner.y
921    }
922
923    /// Zoom value of metatile
924    pub fn zoom(&self) -> u8 {
925        self.inner.zoom
926    }
927
928    /// What is the width or height of this metatile. For small zoom numbers (e.g. z1), there will
929    /// not be the full `scale` tiles across.
930    pub fn size(self) -> u8 {
931        self.inner.size()
932    }
933}
934
935impl From<ModTileMetatile> for Metatile {
936    fn from(mt: ModTileMetatile) -> Self {
937        mt.inner
938    }
939}
940
941impl TryFrom<Metatile> for ModTileMetatile {
942    type Error = &'static str;
943
944    fn try_from(mt: Metatile) -> Result<Self, Self::Error> {
945        if mt.scale == 8 {
946            Ok(ModTileMetatile { inner: mt })
947        } else {
948            Err("Can only convert scale 8 metatiles into ModTileMetatile")
949        }
950    }
951}
952
953impl Borrow<Metatile> for ModTileMetatile {
954    fn borrow(&self) -> &Metatile {
955        &self.inner
956    }
957}
958
959impl Deref for ModTileMetatile {
960    type Target = Metatile;
961
962    fn deref(&self) -> &Metatile {
963        &self.inner
964    }
965}
966
967fn tile_nw_lat_lon(zoom: u8, x: f32, y: f32) -> LatLon {
968    let n: f32 = 2f32.powi(zoom as i32);
969    let lon_deg: f32 = (x as f32) / n * 360f32 - 180f32;
970    let lat_rad: f32 = ((1f32 - 2f32 * (y as f32) / n) * std::f32::consts::PI)
971        .sinh()
972        .atan();
973    let lat_deg: f32 = lat_rad * 180f32 * std::f32::consts::FRAC_1_PI;
974
975    // FIXME figure out the unwrapping here....
976    // Do we always know it's valid?
977    LatLon::new(lat_deg, lon_deg).unwrap()
978}
979
980/// Return the x,y of a tile which has this lat/lon for this zoom level
981pub fn lat_lon_to_tile(lat: f32, lon: f32, zoom: u8) -> (u32, u32) {
982    // TODO do this at compile time?
983    #[allow(non_snake_case)]
984    let MAX_LAT: f64 = std::f64::consts::PI.sinh().atan();
985
986    let lat: f64 = lat as f64;
987    let lat = lat.to_radians();
988
989    let lon: f64 = lon as f64;
990
991    // Clip the latitude to the max & min (~85.0511)
992    let lat = if lat > MAX_LAT {
993        MAX_LAT
994    } else if lat < -MAX_LAT {
995        -MAX_LAT
996    } else {
997        lat
998    };
999
1000    let n: f64 = 2f64.powi(zoom as i32);
1001    let xtile: u32 = (n * ((lon + 180.) / 360.)).trunc() as u32;
1002    let ytile: u32 = (n * (1. - ((lat.tan() + (1. / lat.cos())).ln() / std::f64::consts::PI)) / 2.)
1003        .trunc() as u32;
1004
1005    (xtile, ytile)
1006}
1007
1008/// Return the x,y of a tile which (for this zoom) has this web mercator 3857 x/y, and then the x,y
1009/// of the pixel within that image (presuming a 256x256 image)
1010pub fn merc_location_to_tile_coords(x: f64, y: f64, zoom: u8) -> ((u32, u32), (u32, u32)) {
1011    let num_tiles = 2u32.pow(zoom as u32) as f64;
1012    let global_extent = 20_037_508.342789244;
1013    let tile_width = (2. * global_extent) / num_tiles;
1014
1015    (
1016        // location within the tile
1017        (
1018            ((x + global_extent) / tile_width) as u32,
1019            ((y + global_extent) / tile_width) as u32,
1020        ),
1021        // Tile x/y
1022        (
1023            (((x + global_extent) % tile_width) / tile_width * 256.) as u32,
1024            (num_tiles - ((y + global_extent) % tile_width) / tile_width * 256. - 1.) as u32,
1025        ),
1026    )
1027}
1028
1029/// How many tiles does this bbox cover at this zoom
1030/// If there is an overflow for usize, `None` is returned, if not, a `Some(...)`
1031pub fn size_bbox_zoom(bbox: &BBox, zoom: u8) -> Option<usize> {
1032    let top_left_tile = lat_lon_to_tile(bbox.top(), bbox.left(), zoom);
1033    let bottom_right_tile = lat_lon_to_tile(bbox.bottom(), bbox.right(), zoom);
1034    let height = (bottom_right_tile.0 - top_left_tile.0) as usize + 1;
1035    let width = (bottom_right_tile.1 - top_left_tile.1) as usize + 1;
1036
1037    height.checked_mul(width)
1038}
1039
1040/// How many metatiles, of this scale, does this bbox cover at this zoom
1041/// If there is an overflow for usize, `None` is returned, if not, a `Some(...)`
1042/// This is less likely to overflow than `size_bbox_zoom` because metatiles are larger
1043pub fn size_bbox_zoom_metatiles(bbox: &BBox, zoom: u8, metatile_scale: u8) -> Option<usize> {
1044    let metatile_scale = metatile_scale as u32;
1045    let top_left_tile = lat_lon_to_tile(bbox.top(), bbox.left(), zoom);
1046    let bottom_right_tile = lat_lon_to_tile(bbox.bottom(), bbox.right(), zoom);
1047    let bottom = (bottom_right_tile.0 / metatile_scale) * metatile_scale;
1048    let top = (top_left_tile.0 / metatile_scale) * metatile_scale;
1049    let left = (top_left_tile.1 / metatile_scale) * metatile_scale;
1050    let right = (bottom_right_tile.1 / metatile_scale) * metatile_scale;
1051
1052    let height = ((bottom - top) / metatile_scale as u32) as usize + 1;
1053    let width = ((right - left) / metatile_scale as u32) as usize + 1;
1054
1055    height.checked_mul(width)
1056}
1057
1058/// A single point in the world.
1059///
1060/// Since OSM uses up to 7 decimal places, this stores the lat/lon as `f32` which is enough
1061/// precision of that
1062#[derive(PartialEq, Debug, Clone)]
1063pub struct LatLon {
1064    lat: f32,
1065    lon: f32,
1066}
1067
1068impl LatLon {
1069    /// Constructs a LatLon from a given `lat` and `lon`. Returns `None` if the lat or lon is
1070    /// invalid, e.g. a lat of 100.
1071    pub fn new(lat: f32, lon: f32) -> Option<LatLon> {
1072        if lat <= 90f32 && lat >= -90f32 && lon <= 180f32 && lon >= -180f32 {
1073            Some(LatLon { lat: lat, lon: lon })
1074        } else {
1075            None
1076        }
1077    }
1078
1079    /// Latitude
1080    pub fn lat(&self) -> f32 {
1081        self.lat
1082    }
1083    /// Longitude
1084    pub fn lon(&self) -> f32 {
1085        self.lon
1086    }
1087
1088    /// Convert to Web Mercator format (SRID 3857)
1089    pub fn to_3857(&self) -> (f32, f32) {
1090        let x = self.lon() * 20037508.34 / 180.;
1091        let pi = std::f32::consts::PI;
1092        let y = ((90. + self.lat()) * pi / 360.).tan().ln() / (pi / 180.);
1093        let y = y * 20037508.34 / 180.;
1094
1095        (x, y)
1096    }
1097
1098    /// What tile is this point at on this zoom level
1099    pub fn tile(&self, zoom: u8) -> Tile {
1100        let (x, y) = lat_lon_to_tile(self.lat, self.lon, zoom);
1101        Tile::new(zoom, x, y).unwrap()
1102    }
1103}
1104
1105/// A Bounding box
1106#[derive(PartialEq, Debug, Clone)]
1107pub struct BBox {
1108    top: f32,
1109    left: f32,
1110    bottom: f32,
1111    right: f32,
1112}
1113
1114impl BBox {
1115    /// Construct a new BBox from the given max and min latitude and longitude. Returns `None` if
1116    /// the lat or lon is invalid, e.g. a lon of 200
1117    pub fn new(top: f32, left: f32, bottom: f32, right: f32) -> Option<BBox> {
1118        //let top = if top > bottom { top } else { bottom };
1119        //let bottom = if top > bottom { bottom } else { top };
1120        //let left = if right > left { left } else { right };
1121        //let right = if right > left { right } else { left };
1122
1123        if top <= 90.
1124            && top >= -90.
1125            && bottom <= 90.
1126            && bottom >= -90.
1127            && left <= 180.
1128            && left >= -180.
1129            && right <= 180.
1130            && right >= -180.
1131        {
1132            Some(BBox {
1133                top: top,
1134                left: left,
1135                bottom: bottom,
1136                right: right,
1137            })
1138        } else {
1139            None
1140        }
1141    }
1142
1143    /// Given two points, return the bounding box specified by those 2 points
1144    pub fn new_from_points(topleft: &LatLon, bottomright: &LatLon) -> BBox {
1145        BBox {
1146            top: topleft.lat,
1147            left: topleft.lon,
1148            bottom: bottomright.lat,
1149            right: bottomright.lon,
1150        }
1151    }
1152
1153    /// Construct a BBox from a tile
1154    pub fn new_from_tile(tile: &Tile) -> Self {
1155        tile.bbox()
1156    }
1157
1158    /// Return true iff this point is in this bbox
1159    pub fn contains_point(&self, point: &LatLon) -> bool {
1160        point.lat <= self.top
1161            && point.lat > self.bottom
1162            && point.lon >= self.left
1163            && point.lon < self.right
1164    }
1165
1166    /// Returns true iff this bbox and `other` share at least one point
1167    pub fn overlaps_bbox(&self, other: &BBox) -> bool {
1168        // FXME check top & left edges
1169        self.left < other.right
1170            && self.right > other.left
1171            && self.top > other.bottom
1172            && self.bottom < other.top
1173    }
1174
1175    /// Iterate over all the tiles from z0 onwards that this bbox is in
1176    pub fn tiles(&self) -> BBoxTilesIterator {
1177        BBoxTilesIterator::new(&self)
1178    }
1179
1180    /// Iterate over all the metatiles from z0 onwards that this bbox is in
1181    pub fn metatiles(&self, scale: u8) -> MetatilesIterator {
1182        let bbox: BBox = (*self).clone();
1183        MetatilesIterator {
1184            curr_zoom: 0,
1185            maxzoom: 32,
1186            bbox: Some(bbox),
1187            curr_zorder: 0,
1188            scale: scale,
1189            curr_zoom_width_height: None,
1190            curr_zoom_start_xy: None,
1191            total: None,
1192            tile_list_file: None,
1193        }
1194    }
1195
1196    /// Return the top value of this bbox
1197    pub fn top(&self) -> f32 {
1198        self.top
1199    }
1200
1201    /// Return the bottom value of this bbox
1202    pub fn bottom(&self) -> f32 {
1203        self.bottom
1204    }
1205
1206    /// Return the left value of this bbox
1207    pub fn left(&self) -> f32 {
1208        self.left
1209    }
1210
1211    /// Return the right value of this bbox
1212    pub fn right(&self) -> f32 {
1213        self.right
1214    }
1215
1216    /// For this zoom level, return all the tiles that cover this bbox
1217    pub fn tiles_for_zoom(&self, zoom: u8) -> impl Iterator<Item = Tile> {
1218        let top_left_tile = lat_lon_to_tile(self.top, self.left, zoom);
1219        let bottom_right_tile = lat_lon_to_tile(self.bottom, self.right, zoom);
1220
1221        (top_left_tile.0..=bottom_right_tile.0)
1222            .flat_map(move |x| {
1223                (top_left_tile.1..=bottom_right_tile.1)
1224                    .map(move |y| (x, y))
1225            })
1226            .map(move |(x, y)| Tile::new(zoom, x, y).unwrap())
1227    }
1228
1229    /// Returns the LatLon for the centre of this bbox
1230    pub fn centre_point(&self) -> LatLon {
1231        LatLon::new((self.top + self.bottom) / 2., (self.left + self.right) / 2.).unwrap()
1232    }
1233
1234    /// Returns the LatLon for the centre of this bbox
1235    pub fn center_point(&self) -> LatLon {
1236        self.centre_point()
1237    }
1238
1239    /// Returns the LatLon of the top left, i.e. north west corner, of this bbot
1240    pub fn nw_corner(&self) -> LatLon {
1241        LatLon::new(self.top, self.left).unwrap()
1242    }
1243
1244    /// Returns the LatLon of the top right, i.e. north east corner, of this bbox
1245    pub fn ne_corner(&self) -> LatLon {
1246        LatLon::new(self.top, self.right).unwrap()
1247    }
1248
1249    /// Returns the LatLon of the bottom left, i.e. south west corner, of this bbox
1250    pub fn sw_corner(&self) -> LatLon {
1251        LatLon::new(self.bottom, self.left).unwrap()
1252    }
1253
1254    /// Returns the LatLon of the bottom right, i.e. south east corner, of this bbox.
1255    pub fn se_corner(&self) -> LatLon {
1256        LatLon::new(self.bottom, self.right).unwrap()
1257    }
1258}
1259
1260impl FromStr for BBox {
1261    type Err = &'static str;
1262
1263    /// Given a string like "$MINLON $MINLAT $MAXLON $MAXLAT" parse that into a BBox. Returns None
1264    /// if there is no match.
1265    fn from_str(string: &str) -> Result<Self, Self::Err> {
1266        lazy_static! {
1267            //static ref num_regex: &'static str = r"-?[0-9]{1,3}(\.[0-9]{1,10})?";
1268            static ref SIMPLE_COPY_SPACE: Regex = Regex::new(r"^(?P<minlon>-?[0-9]{1,3}(\.[0-9]{1,10})?) (?P<minlat>-?[0-9]{1,3}(\.[0-9]{1,10})?) (?P<maxlon>-?[0-9]{1,3}(\.[0-9]{1,10})?) (?P<maxlat>-?[0-9]{1,3}(\.[0-9]{1,10})?)$").unwrap();
1269            static ref SIMPLE_COPY_COMMA: Regex = Regex::new(r"^(?P<minlon>-?[0-9]{1,3}(\.[0-9]{1,10})?),(?P<minlat>-?[0-9]{1,3}(\.[0-9]{1,10})?),(?P<maxlon>-?[0-9]{1,3}(\.[0-9]{1,10})?),(?P<maxlat>-?[0-9]{1,3}(\.[0-9]{1,10})?)$").unwrap();
1270        }
1271        let caps = SIMPLE_COPY_SPACE
1272            .captures(string)
1273            .or_else(|| SIMPLE_COPY_COMMA.captures(string));
1274        if caps.is_none() {
1275            return Err("regex not match");
1276        }
1277        let caps = caps.unwrap();
1278
1279        let minlat = caps.name("minlat");
1280        let maxlat = caps.name("maxlat");
1281        let minlon = caps.name("minlon");
1282        let maxlon = caps.name("maxlon");
1283
1284        if minlat.is_none() || maxlat.is_none() || minlon.is_none() || maxlon.is_none() {
1285            return Err("bad lat/lon");
1286        }
1287
1288        let minlat = minlat.unwrap().as_str().parse();
1289        let maxlat = maxlat.unwrap().as_str().parse();
1290        let minlon = minlon.unwrap().as_str().parse();
1291        let maxlon = maxlon.unwrap().as_str().parse();
1292
1293        if minlat.is_err() || maxlat.is_err() || minlon.is_err() || maxlon.is_err() {
1294            return Err("bad lat/lon");
1295        }
1296
1297        let minlat = minlat.unwrap();
1298        let maxlat = maxlat.unwrap();
1299        let minlon = minlon.unwrap();
1300        let maxlon = maxlon.unwrap();
1301
1302        BBox::new(maxlat, minlon, minlat, maxlon).ok_or("bad lat/lon")
1303    }
1304}
1305
1306pub struct BBoxTilesIterator<'a> {
1307    bbox: &'a BBox,
1308    tiles: Vec<Tile>,
1309    tile_index: usize,
1310}
1311
1312impl<'a> BBoxTilesIterator<'a> {
1313    pub fn new(bbox: &'a BBox) -> BBoxTilesIterator<'a> {
1314        // Everything is in 0/0/0, so start with that.
1315        BBoxTilesIterator {
1316            bbox: bbox,
1317            tiles: vec![Tile::new(0, 0, 0).unwrap()],
1318            tile_index: 0,
1319        }
1320    }
1321}
1322
1323impl<'a> Iterator for BBoxTilesIterator<'a> {
1324    type Item = Tile;
1325
1326    fn next(&mut self) -> Option<Tile> {
1327        if self.tile_index >= self.tiles.len() {
1328            // We've sent off all the existing tiles, so start looking at the children
1329            let mut new_tiles: Vec<Tile> = Vec::with_capacity(self.tiles.len() * 4);
1330            for t in self.tiles.iter() {
1331                match t.subtiles() {
1332                    None => {}
1333                    Some(sub) => {
1334                        if self.bbox.overlaps_bbox(&sub[0].bbox()) {
1335                            new_tiles.push(sub[0]);
1336                        }
1337                        if self.bbox.overlaps_bbox(&sub[1].bbox()) {
1338                            new_tiles.push(sub[1]);
1339                        }
1340                        if self.bbox.overlaps_bbox(&sub[2].bbox()) {
1341                            new_tiles.push(sub[2]);
1342                        }
1343                        if self.bbox.overlaps_bbox(&sub[3].bbox()) {
1344                            new_tiles.push(sub[3]);
1345                        }
1346                    }
1347                }
1348            }
1349
1350            new_tiles.shrink_to_fit();
1351            self.tiles = new_tiles;
1352            self.tile_index = 0;
1353        }
1354
1355        let tile = self.tiles[self.tile_index];
1356        self.tile_index += 1;
1357        Some(tile)
1358    }
1359}
1360
1361/// Convert x & y to a TileCache (tc) directory parts
1362fn xy_to_tc(x: u32, y: u32) -> [String; 6] {
1363    [
1364        format!("{:03}", x / 1_000_000),
1365        format!("{:03}", (x / 1_000) % 1_000),
1366        format!("{:03}", x % 1_000),
1367        format!("{:03}", y / 1_000_000),
1368        format!("{:03}", (y / 1_000) % 1_000),
1369        format!("{:03}", y % 1_000),
1370    ]
1371}
1372
1373/// Convert x & y to a MapProxy (mp) directory parts
1374fn xy_to_mp(x: u32, y: u32) -> [String; 4] {
1375    [
1376        format!("{:04}", x / 10_000),
1377        format!("{:04}", x % 10_000),
1378        format!("{:04}", y / 10_000),
1379        format!("{:04}", y % 10_000),
1380    ]
1381}
1382
1383/// Convert x & y to a TileStash (ts) safe directory parts
1384fn xy_to_ts(x: u32, y: u32) -> [String; 4] {
1385    [
1386        format!("{:03}", x / 1_000),
1387        format!("{:03}", x % 1_000),
1388        format!("{:03}", y / 1_000),
1389        format!("{:03}", y % 1_000),
1390    ]
1391}
1392
1393/// Convert x & y to a ModTile metatile directory parts
1394fn xy_to_mt(x: u32, y: u32) -> [String; 5] {
1395    // /[Z]/[xxxxyyyy]/[xxxxyyyy]/[xxxxyyyy]/[xxxxyyyy]/[xxxxyyyy].png
1396    // i.e. /[Z]/a/b/c/d/e.png
1397
1398    let mut x = x;
1399    let mut y = y;
1400
1401    let e = (((x & 0x0f) << 4) | (y & 0x0f)) as u8;
1402    x >>= 4;
1403    y >>= 4;
1404
1405    let d = (((x & 0x0f) << 4) | (y & 0x0f)) as u8;
1406    x >>= 4;
1407    y >>= 4;
1408
1409    let c = (((x & 0b000_1111 as u32) << 4) | (y & 0b000_1111 as u32)) as u8;
1410    x >>= 4;
1411    y >>= 4;
1412
1413    let b = (((x & 0b000_1111 as u32) << 4) | (y & 0b000_1111 as u32)) as u8;
1414    x >>= 4;
1415    y >>= 4;
1416
1417    let a = (((x & 0b000_1111 as u32) << 4) | (y & 0b000_1111 as u32)) as u8;
1418    //x >>= 4;
1419    //y >>= 4;
1420
1421    [
1422        format!("{}", a),
1423        format!("{}", b),
1424        format!("{}", c),
1425        format!("{}", d),
1426        format!("{}", e),
1427    ]
1428}
1429
1430/// How many times are in this soom level? Returns None if there would be a usize overflow
1431fn num_tiles_in_zoom(zoom: u8) -> Option<usize> {
1432    // From experience it looks like you can't calc above zoom >= 6
1433    if zoom == 0 {
1434        // Special case of known value
1435        Some(1)
1436    } else if zoom <= 5 {
1437        Some(2u64.pow(2u32.pow(zoom as u32)) as usize)
1438    } else {
1439        None
1440    }
1441}
1442
1443pub fn xy_to_zorder(x: u32, y: u32) -> u64 {
1444    let mut res: u64 = 0;
1445    for i in 0..32 {
1446        let x_set: bool = (x >> i) & 1 == 1;
1447        let y_set: bool = (y >> i) & 1 == 1;
1448        if x_set {
1449            res |= 1 << (i * 2);
1450        }
1451        if y_set {
1452            res |= 1 << (i * 2) + 1;
1453        }
1454    }
1455
1456    res
1457}
1458
1459pub fn zorder_to_xy(zorder: u64) -> (u32, u32) {
1460    let mut x: u32 = 0;
1461    let mut y: u32 = 0;
1462
1463    for i in 0..32 {
1464        let x_bit_set = (zorder >> (i * 2)) & 1 == 1;
1465        let y_bit_set = (zorder >> ((i * 2) + 1)) & 1 == 1;
1466
1467        if x_bit_set {
1468            x |= 1 << i;
1469        }
1470        if y_bit_set {
1471            y |= 1 << i;
1472        }
1473    }
1474
1475    (x, y)
1476}