#[macro_use]
extern crate lazy_static;
extern crate regex;
#[cfg(feature = "world_file")]
extern crate world_image_file;
use regex::Regex;
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::fs::File;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::ops::Deref;
use std::str::FromStr;
#[cfg(feature = "world_file")]
use world_image_file::WorldFile;
#[cfg(test)]
mod tests;
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub struct Tile {
zoom: u8,
x: u32,
y: u32,
}
impl Tile {
pub fn new(zoom: u8, x: u32, y: u32) -> Option<Tile> {
if zoom >= 100 {
None
} else if x < 2u32.pow(zoom as u32) && y < 2u32.pow(zoom as u32) {
Some(Tile {
zoom: zoom,
x: x,
y: y,
})
} else {
None
}
}
pub fn zoom(&self) -> u8 {
self.zoom
}
pub fn x(&self) -> u32 {
self.x
}
pub fn y(&self) -> u32 {
self.y
}
pub fn from_tms(tms: &str) -> Option<Tile> {
lazy_static! {
static ref RE: Regex = Regex::new(
"/?(?P<zoom>[0-9]?[0-9])/(?P<x>[0-9]{1,10})/(?P<y>[0-9]{1,10})(\\.[a-zA-Z]{3,4})?$"
)
.unwrap();
}
let caps = RE.captures(tms)?;
let zoom = caps.name("zoom");
let x = caps.name("x");
let y = caps.name("y");
if zoom.is_none() || x.is_none() || y.is_none() {
return None;
}
let zoom = zoom.unwrap().as_str().parse();
let x = x.unwrap().as_str().parse();
let y = y.unwrap().as_str().parse();
if zoom.is_err() || x.is_err() || y.is_err() {
return None;
}
let zoom: u8 = zoom.unwrap();
let x: u32 = x.unwrap();
let y: u32 = y.unwrap();
Tile::new(zoom, x, y)
}
pub fn parent(&self) -> Option<Tile> {
match self.zoom {
0 => {
None
}
_ => Tile::new(self.zoom - 1, self.x / 2, self.y / 2),
}
}
pub fn subtiles(&self) -> Option<[Tile; 4]> {
match self.zoom {
std::u8::MAX => None,
_ => {
let z = self.zoom + 1;
let x = 2 * self.x;
let y = 2 * self.y;
Some([
Tile {
zoom: z,
x: x,
y: y,
},
Tile {
zoom: z,
x: x + 1,
y: y,
},
Tile {
zoom: z,
x: x,
y: y + 1,
},
Tile {
zoom: z,
x: x + 1,
y: y + 1,
},
])
}
}
}
pub fn all_subtiles_iter(&self) -> AllSubTilesIterator {
AllSubTilesIterator::new_from_tile(&self)
}
pub fn centre_point(&self) -> LatLon {
tile_nw_lat_lon(self.zoom, (self.x as f32) + 0.5, (self.y as f32) + 0.5)
}
pub fn center_point(&self) -> LatLon {
self.centre_point()
}
pub fn nw_corner(&self) -> LatLon {
tile_nw_lat_lon(self.zoom, self.x as f32, self.y as f32)
}
pub fn ne_corner(&self) -> LatLon {
tile_nw_lat_lon(self.zoom, (self.x as f32) + 1.0, self.y as f32)
}
pub fn sw_corner(&self) -> LatLon {
tile_nw_lat_lon(self.zoom, self.x as f32, (self.y as f32) + 1.0)
}
pub fn se_corner(&self) -> LatLon {
tile_nw_lat_lon(self.zoom, (self.x as f32) + 1.0, (self.y as f32) + 1.0)
}
pub fn top(&self) -> f32 {
self.nw_corner().lat
}
pub fn bottom(&self) -> f32 {
self.sw_corner().lat
}
pub fn left(&self) -> f32 {
self.nw_corner().lon
}
pub fn right(&self) -> f32 {
self.se_corner().lon
}
pub fn tc_path<T: std::fmt::Display>(&self, ext: T) -> String {
let tc = xy_to_tc(self.x, self.y);
format!(
"{}/{}/{}/{}/{}/{}/{}.{}",
self.zoom, tc[0], tc[1], tc[2], tc[3], tc[4], tc[5], ext
)
}
pub fn mp_path<T: std::fmt::Display>(&self, ext: T) -> String {
let mp = xy_to_mp(self.x, self.y);
format!(
"{}/{}/{}/{}/{}.{}",
self.zoom, mp[0], mp[1], mp[2], mp[3], ext
)
}
pub fn ts_path<T: std::fmt::Display>(&self, ext: T) -> String {
let ts = xy_to_ts(self.x, self.y);
format!(
"{}/{}/{}/{}/{}.{}",
self.zoom, ts[0], ts[1], ts[2], ts[3], ext
)
}
pub fn zxy(&self) -> String {
format!("{}/{}/{}", self.zoom, self.x, self.y)
}
pub fn zxy_path<T: std::fmt::Display>(&self, ext: T) -> String {
format!("{}/{}/{}.{}", self.zoom, self.x, self.y, ext)
}
pub fn mt_path<T: std::fmt::Display>(&self, ext: T) -> String {
let tc = xy_to_mt(self.x, self.y);
format!(
"{}/{}/{}/{}/{}/{}.{}",
self.zoom, tc[0], tc[1], tc[2], tc[3], tc[4], ext
)
}
pub fn all() -> AllTilesIterator {
AllTilesIterator {
next_zoom: 0,
next_zorder: 0,
}
}
pub fn all_to_zoom(max_zoom: u8) -> AllTilesToZoomIterator {
AllTilesToZoomIterator {
max_zoom: max_zoom,
next_zoom: 0,
next_x: 0,
next_y: 0,
}
}
pub fn bbox(&self) -> BBox {
let nw = self.nw_corner();
let se = self.se_corner();
BBox::new_from_points(&nw, &se)
}
pub fn metatile(&self, scale: u8) -> Option<Metatile> {
Metatile::new(scale, self.zoom(), self.x(), self.y())
}
pub fn modtile_metatile(&self) -> Option<ModTileMetatile> {
ModTileMetatile::new(self.zoom(), self.x(), self.y())
}
#[cfg(feature = "world_file")]
pub fn world_file(&self) -> WorldFile {
let total_merc_width = 20037508.342789244;
let tile_merc_width = (2. * total_merc_width) / 2f64.powi(self.zoom as i32);
let scale = tile_merc_width / 256.;
WorldFile {
x_scale: scale,
y_scale: -scale,
x_skew: 0.,
y_skew: 0.,
x_coord: tile_merc_width * (self.x as f64) - total_merc_width,
y_coord: -tile_merc_width * (self.y as f64) + total_merc_width,
}
}
}
impl FromStr for Tile {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static! {
static ref TILE_RE: Regex =
Regex::new("^(?P<zoom>[0-9]?[0-9])/(?P<x>[0-9]{1,10})/(?P<y>[0-9]{1,10})$")
.unwrap();
}
let caps = TILE_RE.captures(s);
if caps.is_none() {
return Err("Tile Z/X/Y regex didn't match");
}
let caps = caps.unwrap();
let zoom = caps.name("zoom").unwrap().as_str().parse().unwrap();
let x = caps.name("x").unwrap().as_str().parse().unwrap();
let y = caps.name("y").unwrap().as_str().parse().unwrap();
match Tile::new(zoom, x, y) {
None => {
Err("Invalid X or Y for this zoom")
}
Some(t) => Ok(t),
}
}
}
pub struct AllTilesIterator {
next_zoom: u8,
next_zorder: u64,
}
impl Iterator for AllTilesIterator {
type Item = Tile;
fn next(&mut self) -> Option<Tile> {
let zoom = self.next_zoom;
let (x, y) = zorder_to_xy(self.next_zorder);
let tile = Tile::new(zoom, x, y);
let max_tile_no = 2u32.pow(zoom as u32) - 1;
if x == max_tile_no && y == max_tile_no {
self.next_zoom = zoom + 1;
self.next_zorder = 0;
} else {
self.next_zorder += 1;
}
tile
}
}
pub struct AllTilesToZoomIterator {
max_zoom: u8,
next_zoom: u8,
next_x: u32,
next_y: u32,
}
fn remaining_in_this_zoom(next_zoom: u8, next_x: u32, next_y: u32) -> Option<usize> {
if next_zoom == 0 && next_x == 0 && next_y == 0 {
return Some(1);
}
let max_tile_no = 2u32.pow(next_zoom as u32);
let remaining_in_column = max_tile_no - next_y;
let remaining_in_column = remaining_in_column as usize;
let remaining_rows = max_tile_no - next_x - 1;
let remaining_rows = remaining_rows as usize;
let remaining_after_this_column = remaining_rows.checked_mul(max_tile_no as usize)?;
remaining_in_column.checked_add(remaining_after_this_column)
}
impl Iterator for AllTilesToZoomIterator {
type Item = Tile;
fn next(&mut self) -> Option<Tile> {
if self.next_zoom > self.max_zoom {
return None;
}
let tile = Tile::new(self.next_zoom, self.next_x, self.next_y);
let max_tile_no = 2u32.pow(self.next_zoom as u32) - 1;
if self.next_y < max_tile_no {
self.next_y += 1;
} else if self.next_x < max_tile_no {
self.next_x += 1;
self.next_y = 0;
} else if self.next_zoom < std::u8::MAX {
self.next_zoom += 1;
self.next_x = 0;
self.next_y = 0;
}
tile
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.next_zoom > self.max_zoom {
return (0, Some(0));
}
let remaining_in_this_level =
remaining_in_this_zoom(self.next_zoom, self.next_x, self.next_y);
if remaining_in_this_level.is_none() {
return (std::usize::MAX, None);
}
let remaining_in_this_level = remaining_in_this_level.unwrap();
let mut total: usize = remaining_in_this_level as usize;
for i in (self.next_zoom + 1)..(self.max_zoom + 1) {
let tiles_this_zoom = num_tiles_in_zoom(i);
if tiles_this_zoom.is_none() {
return (std::usize::MAX, None);
}
let tiles_this_zoom = tiles_this_zoom.unwrap();
let new_total = total.checked_add(tiles_this_zoom);
if new_total.is_none() {
return (std::usize::MAX, None);
}
total = new_total.unwrap();
}
(total, Some(total))
}
}
pub struct AllSubTilesIterator {
_tiles: Vec<Tile>,
}
impl AllSubTilesIterator {
pub fn new_from_tile(base_tile: &Tile) -> Self {
let new_tiles = match base_tile.subtiles() {
None => Vec::new(),
Some(t) => vec![t[0], t[1], t[2], t[3]],
};
AllSubTilesIterator { _tiles: new_tiles }
}
}
impl Iterator for AllSubTilesIterator {
type Item = Tile;
fn next(&mut self) -> Option<Tile> {
if self._tiles.is_empty() {
return None;
}
let next = self._tiles.remove(0);
if let Some(subtiles) = next.subtiles() {
self._tiles.extend_from_slice(&subtiles);
}
Some(next)
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub struct Metatile {
scale: u8,
zoom: u8,
x: u32,
y: u32,
}
impl Metatile {
pub fn new(scale: u8, zoom: u8, x: u32, y: u32) -> Option<Self> {
if !scale.is_power_of_two() {
return None;
}
if zoom >= 100 {
None
} else if x < 2u32.pow(zoom as u32) && y < 2u32.pow(zoom as u32) {
let s = scale as u32;
let x = (x / s) * s;
let y = (y / s) * s;
Some(Metatile {
scale: scale,
zoom: zoom,
x: x,
y: y,
})
} else {
None
}
}
pub fn scale(&self) -> u8 {
self.scale
}
pub fn zoom(&self) -> u8 {
self.zoom
}
pub fn size(&self) -> u8 {
let num_tiles_in_zoom = 2u32.pow(self.zoom as u32);
if num_tiles_in_zoom < (self.scale as u32) {
num_tiles_in_zoom as u8
} else {
self.scale
}
}
pub fn centre_point(&self) -> LatLon {
tile_nw_lat_lon(
self.zoom,
(self.x as f32) + (self.size() as f32) / 2.,
(self.y as f32) + (self.size() as f32) / 2.,
)
}
pub fn center_point(&self) -> LatLon {
self.centre_point()
}
pub fn nw_corner(&self) -> LatLon {
tile_nw_lat_lon(self.zoom, self.x as f32, self.y as f32)
}
pub fn ne_corner(&self) -> LatLon {
tile_nw_lat_lon(
self.zoom,
(self.x + self.size() as u32) as f32,
self.y as f32,
)
}
pub fn sw_corner(&self) -> LatLon {
tile_nw_lat_lon(
self.zoom,
self.x as f32,
(self.y + self.size() as u32) as f32,
)
}
pub fn se_corner(&self) -> LatLon {
tile_nw_lat_lon(
self.zoom,
(self.x + self.size() as u32) as f32,
(self.y + self.size() as u32) as f32,
)
}
pub fn x(&self) -> u32 {
self.x
}
pub fn y(&self) -> u32 {
self.y
}
pub fn tiles(&self) -> Vec<Tile> {
let size = self.size() as u32;
(0..(size * size))
.map(|n| {
let (i, j) = (n / size, n % size);
Tile {
zoom: self.zoom,
x: self.x + i,
y: self.y + j,
}
})
.collect()
}
pub fn all(scale: u8) -> MetatilesIterator {
assert!(scale.is_power_of_two());
MetatilesIterator::all(scale)
}
}
impl FromStr for Metatile {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static! {
static ref METATILE_RE: Regex = Regex::new(
"^(?P<scale>[0-9]+) (?P<zoom>[0-9]?[0-9])/(?P<x>[0-9]{1,10})/(?P<y>[0-9]{1,10})$"
)
.unwrap();
}
let caps = METATILE_RE.captures(s);
if caps.is_none() {
return Err(());
}
let caps = caps.unwrap();
let scale = caps.name("scale").unwrap().as_str().parse().unwrap();
let zoom = caps.name("zoom").unwrap().as_str().parse().unwrap();
let x = caps.name("x").unwrap().as_str().parse().unwrap();
let y = caps.name("y").unwrap().as_str().parse().unwrap();
match Metatile::new(scale, zoom, x, y) {
None => {
Err(())
}
Some(mt) => Ok(mt),
}
}
}
#[derive(Debug)]
pub struct MetatilesIterator {
scale: u8,
curr_zoom: u8,
maxzoom: u8,
curr_zorder: u64,
bbox: Option<BBox>,
curr_zoom_width_height: Option<(u32, u32)>,
curr_zoom_start_xy: Option<(u32, u32)>,
total: Option<usize>,
tile_list_file: Option<BufReader<File>>,
}
impl MetatilesIterator {
pub fn all(scale: u8) -> Self {
MetatilesIterator {
scale: scale,
curr_zoom: 0,
curr_zorder: 0,
bbox: None,
maxzoom: 32,
curr_zoom_width_height: None,
curr_zoom_start_xy: None,
total: None,
tile_list_file: None,
}
}
pub fn new_for_bbox(scale: u8, bbox: &BBox) -> Self {
MetatilesIterator::new_for_bbox_zoom(scale, &Some(bbox.clone()), 0, 32)
}
pub fn new_for_bbox_zoom(scale: u8, bbox: &Option<BBox>, minzoom: u8, maxzoom: u8) -> Self {
let mut it = MetatilesIterator {
scale: scale,
curr_zoom: minzoom,
curr_zorder: 0,
bbox: bbox.clone(),
maxzoom: maxzoom,
curr_zoom_width_height: None,
curr_zoom_start_xy: None,
total: None,
tile_list_file: None,
};
it.set_zoom_width_height();
it.set_zoom_start_xy();
it
}
pub fn new_from_filelist(filename: String) -> Self {
let mut file = BufReader::new(File::open(&filename).unwrap());
file.seek(SeekFrom::Start(0)).unwrap();
let total = file.lines().count();
let file = BufReader::new(File::open(filename).unwrap());
MetatilesIterator {
scale: 0,
curr_zoom: 0,
curr_zorder: 0,
bbox: None,
maxzoom: 0,
curr_zoom_width_height: None,
curr_zoom_start_xy: None,
total: Some(total),
tile_list_file: Some(file),
}
}
fn set_zoom_width_height(&mut self) {
if let Some(ref bbox) = self.bbox {
let scale = self.scale as u32;
let zoom = self.curr_zoom;
let (x1, y1) = lat_lon_to_tile(bbox.top, bbox.left, zoom);
let (x1, y1) = (x1 / scale, y1 / scale);
let (x2, y2) = lat_lon_to_tile(bbox.bottom, bbox.right, zoom);
let (x2, y2) = (x2 / scale, y2 / scale);
let width = x2 - x1 + 1;
let height = y2 - y1 + 1;
self.curr_zoom_width_height = Some((width, height));
}
}
fn set_zoom_start_xy(&mut self) {
if self.bbox.is_none() {
return;
}
let top = match self.bbox {
None => 90.,
Some(ref b) => b.top,
};
let left = match self.bbox {
None => -180.,
Some(ref b) => b.left,
};
let (x1, y1) = lat_lon_to_tile(top, left, self.curr_zoom);
self.curr_zoom_start_xy = Some((x1 / self.scale as u32, y1 / self.scale as u32));
}
fn next_from_zorder(&mut self) -> Option<Metatile> {
#[allow(unused_assignments)]
let mut zoom = 0;
#[allow(unused_assignments)]
let mut x = 0;
#[allow(unused_assignments)]
let mut y = 0;
let scale = self.scale as u32;
loop {
if self.curr_zoom > self.maxzoom {
return None;
}
zoom = self.curr_zoom;
let (width, height) = match self.curr_zoom_width_height {
None => {
let max_num = 2u32.pow(zoom as u32);
let mut max = max_num / scale;
if max_num % scale > 0 {
max += 1
}
(max, max)
}
Some((width, height)) => (width, height),
};
let max_zorder_for_zoom = xy_to_zorder(width - 1, height - 1);
let (i, j) = zorder_to_xy(self.curr_zorder);
let bits = match self.curr_zoom_start_xy {
None => (i, j),
Some(start) => (start.0 + i, start.1 + j),
};
x = bits.0;
y = bits.1;
if self.curr_zorder > max_zorder_for_zoom {
self.curr_zoom = zoom + 1;
self.curr_zorder = 0;
self.set_zoom_start_xy();
self.set_zoom_width_height();
} else if i > width || j > height {
self.curr_zorder += 1;
continue;
} else {
self.curr_zorder += 1;
break;
}
}
let (x, y) = (x * scale, y * scale);
Metatile::new(self.scale, zoom, x, y)
}
fn next_from_file(&mut self) -> Option<Metatile> {
let mut s = String::new();
if let Some(ref mut file) = self.tile_list_file {
file.read_line(&mut s).unwrap();
}
let s = s.trim_end();
s.parse().ok()
}
pub fn total(&self) -> Option<usize> {
self.total
}
}
impl Iterator for MetatilesIterator {
type Item = Metatile;
fn next(&mut self) -> Option<Self::Item> {
if self.tile_list_file.is_some() {
self.next_from_file()
} else {
self.next_from_zorder()
}
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub struct ModTileMetatile {
inner: Metatile,
}
impl ModTileMetatile {
pub fn new(zoom: u8, x: u32, y: u32) -> Option<Self> {
match Metatile::new(8, zoom, x, y) {
None => None,
Some(inner) => Some(ModTileMetatile { inner: inner }),
}
}
pub fn path<T: std::fmt::Display>(&self, ext: T) -> String {
let mt = xy_to_mt(self.inner.x, self.inner.y);
format!(
"{}/{}/{}/{}/{}/{}.{}",
self.inner.zoom, mt[0], mt[1], mt[2], mt[3], mt[4], ext
)
}
pub fn x(&self) -> u32 {
self.inner.x
}
pub fn y(&self) -> u32 {
self.inner.y
}
pub fn zoom(&self) -> u8 {
self.inner.zoom
}
pub fn size(self) -> u8 {
self.inner.size()
}
}
impl From<ModTileMetatile> for Metatile {
fn from(mt: ModTileMetatile) -> Self {
mt.inner
}
}
impl TryFrom<Metatile> for ModTileMetatile {
type Error = &'static str;
fn try_from(mt: Metatile) -> Result<Self, Self::Error> {
if mt.scale == 8 {
Ok(ModTileMetatile { inner: mt })
} else {
Err("Can only convert scale 8 metatiles into ModTileMetatile")
}
}
}
impl Borrow<Metatile> for ModTileMetatile {
fn borrow(&self) -> &Metatile {
&self.inner
}
}
impl Deref for ModTileMetatile {
type Target = Metatile;
fn deref(&self) -> &Metatile {
&self.inner
}
}
fn tile_nw_lat_lon(zoom: u8, x: f32, y: f32) -> LatLon {
let n: f32 = 2f32.powi(zoom as i32);
let lon_deg: f32 = (x as f32) / n * 360f32 - 180f32;
let lat_rad: f32 = ((1f32 - 2f32 * (y as f32) / n) * std::f32::consts::PI)
.sinh()
.atan();
let lat_deg: f32 = lat_rad * 180f32 * std::f32::consts::FRAC_1_PI;
LatLon::new(lat_deg, lon_deg).unwrap()
}
pub fn lat_lon_to_tile(lat: f32, lon: f32, zoom: u8) -> (u32, u32) {
#[allow(non_snake_case)]
let MAX_LAT: f64 = std::f64::consts::PI.sinh().atan();
let lat: f64 = lat as f64;
let lat = lat.to_radians();
let lon: f64 = lon as f64;
let lat = if lat > MAX_LAT {
MAX_LAT
} else if lat < -MAX_LAT {
-MAX_LAT
} else {
lat
};
let n: f64 = 2f64.powi(zoom as i32);
let xtile: u32 = (n * ((lon + 180.) / 360.)).trunc() as u32;
let ytile: u32 = (n * (1. - ((lat.tan() + (1. / lat.cos())).ln() / std::f64::consts::PI)) / 2.)
.trunc() as u32;
(xtile, ytile)
}
pub fn merc_location_to_tile_coords(x: f64, y: f64, zoom: u8) -> ((u32, u32), (u32, u32)) {
let num_tiles = 2u32.pow(zoom as u32) as f64;
let global_extent = 20_037_508.342789244;
let tile_width = (2. * global_extent) / num_tiles;
(
(
((x + global_extent) / tile_width) as u32,
((y + global_extent) / tile_width) as u32,
),
(
(((x + global_extent) % tile_width) / tile_width * 256.) as u32,
(num_tiles - ((y + global_extent) % tile_width) / tile_width * 256. - 1.) as u32,
),
)
}
pub fn size_bbox_zoom(bbox: &BBox, zoom: u8) -> Option<usize> {
let top_left_tile = lat_lon_to_tile(bbox.top(), bbox.left(), zoom);
let bottom_right_tile = lat_lon_to_tile(bbox.bottom(), bbox.right(), zoom);
let height = (bottom_right_tile.0 - top_left_tile.0) as usize + 1;
let width = (bottom_right_tile.1 - top_left_tile.1) as usize + 1;
height.checked_mul(width)
}
pub fn size_bbox_zoom_metatiles(bbox: &BBox, zoom: u8, metatile_scale: u8) -> Option<usize> {
let metatile_scale = metatile_scale as u32;
let top_left_tile = lat_lon_to_tile(bbox.top(), bbox.left(), zoom);
let bottom_right_tile = lat_lon_to_tile(bbox.bottom(), bbox.right(), zoom);
let bottom = (bottom_right_tile.0 / metatile_scale) * metatile_scale;
let top = (top_left_tile.0 / metatile_scale) * metatile_scale;
let left = (top_left_tile.1 / metatile_scale) * metatile_scale;
let right = (bottom_right_tile.1 / metatile_scale) * metatile_scale;
let height = ((bottom - top) / metatile_scale as u32) as usize + 1;
let width = ((right - left) / metatile_scale as u32) as usize + 1;
height.checked_mul(width)
}
#[derive(PartialEq, Debug, Clone)]
pub struct LatLon {
lat: f32,
lon: f32,
}
impl LatLon {
pub fn new(lat: f32, lon: f32) -> Option<LatLon> {
if lat <= 90f32 && lat >= -90f32 && lon <= 180f32 && lon >= -180f32 {
Some(LatLon { lat: lat, lon: lon })
} else {
None
}
}
pub fn lat(&self) -> f32 {
self.lat
}
pub fn lon(&self) -> f32 {
self.lon
}
pub fn to_3857(&self) -> (f32, f32) {
let x = self.lon() * 20037508.34 / 180.;
let pi = std::f32::consts::PI;
let y = ((90. + self.lat()) * pi / 360.).tan().ln() / (pi / 180.);
let y = y * 20037508.34 / 180.;
(x, y)
}
pub fn tile(&self, zoom: u8) -> Tile {
let (x, y) = lat_lon_to_tile(self.lat, self.lon, zoom);
Tile::new(zoom, x, y).unwrap()
}
}
#[derive(PartialEq, Debug, Clone)]
pub struct BBox {
top: f32,
left: f32,
bottom: f32,
right: f32,
}
impl BBox {
pub fn new(top: f32, left: f32, bottom: f32, right: f32) -> Option<BBox> {
if top <= 90.
&& top >= -90.
&& bottom <= 90.
&& bottom >= -90.
&& left <= 180.
&& left >= -180.
&& right <= 180.
&& right >= -180.
{
Some(BBox {
top: top,
left: left,
bottom: bottom,
right: right,
})
} else {
None
}
}
pub fn new_from_points(topleft: &LatLon, bottomright: &LatLon) -> BBox {
BBox {
top: topleft.lat,
left: topleft.lon,
bottom: bottomright.lat,
right: bottomright.lon,
}
}
pub fn new_from_tile(tile: &Tile) -> Self {
tile.bbox()
}
pub fn contains_point(&self, point: &LatLon) -> bool {
point.lat <= self.top
&& point.lat > self.bottom
&& point.lon >= self.left
&& point.lon < self.right
}
pub fn overlaps_bbox(&self, other: &BBox) -> bool {
self.left < other.right
&& self.right > other.left
&& self.top > other.bottom
&& self.bottom < other.top
}
pub fn tiles(&self) -> BBoxTilesIterator {
BBoxTilesIterator::new(&self)
}
pub fn metatiles(&self, scale: u8) -> MetatilesIterator {
let bbox: BBox = (*self).clone();
MetatilesIterator {
curr_zoom: 0,
maxzoom: 32,
bbox: Some(bbox),
curr_zorder: 0,
scale: scale,
curr_zoom_width_height: None,
curr_zoom_start_xy: None,
total: None,
tile_list_file: None,
}
}
pub fn top(&self) -> f32 {
self.top
}
pub fn bottom(&self) -> f32 {
self.bottom
}
pub fn left(&self) -> f32 {
self.left
}
pub fn right(&self) -> f32 {
self.right
}
pub fn tiles_for_zoom(&self, zoom: u8) -> impl Iterator<Item = Tile> {
let top_left_tile = lat_lon_to_tile(self.top, self.left, zoom);
let bottom_right_tile = lat_lon_to_tile(self.bottom, self.right, zoom);
(top_left_tile.0..=bottom_right_tile.0)
.flat_map(move |x| {
(top_left_tile.1..=bottom_right_tile.1)
.map(move |y| (x, y))
})
.map(move |(x, y)| Tile::new(zoom, x, y).unwrap())
}
pub fn centre_point(&self) -> LatLon {
LatLon::new((self.top + self.bottom) / 2., (self.left + self.right) / 2.).unwrap()
}
pub fn center_point(&self) -> LatLon {
self.centre_point()
}
pub fn nw_corner(&self) -> LatLon {
LatLon::new(self.top, self.left).unwrap()
}
pub fn ne_corner(&self) -> LatLon {
LatLon::new(self.top, self.right).unwrap()
}
pub fn sw_corner(&self) -> LatLon {
LatLon::new(self.bottom, self.left).unwrap()
}
pub fn se_corner(&self) -> LatLon {
LatLon::new(self.bottom, self.right).unwrap()
}
}
impl FromStr for BBox {
type Err = &'static str;
fn from_str(string: &str) -> Result<Self, Self::Err> {
lazy_static! {
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();
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();
}
let caps = SIMPLE_COPY_SPACE
.captures(string)
.or_else(|| SIMPLE_COPY_COMMA.captures(string));
if caps.is_none() {
return Err("regex not match");
}
let caps = caps.unwrap();
let minlat = caps.name("minlat");
let maxlat = caps.name("maxlat");
let minlon = caps.name("minlon");
let maxlon = caps.name("maxlon");
if minlat.is_none() || maxlat.is_none() || minlon.is_none() || maxlon.is_none() {
return Err("bad lat/lon");
}
let minlat = minlat.unwrap().as_str().parse();
let maxlat = maxlat.unwrap().as_str().parse();
let minlon = minlon.unwrap().as_str().parse();
let maxlon = maxlon.unwrap().as_str().parse();
if minlat.is_err() || maxlat.is_err() || minlon.is_err() || maxlon.is_err() {
return Err("bad lat/lon");
}
let minlat = minlat.unwrap();
let maxlat = maxlat.unwrap();
let minlon = minlon.unwrap();
let maxlon = maxlon.unwrap();
BBox::new(maxlat, minlon, minlat, maxlon).ok_or("bad lat/lon")
}
}
pub struct BBoxTilesIterator<'a> {
bbox: &'a BBox,
tiles: Vec<Tile>,
tile_index: usize,
}
impl<'a> BBoxTilesIterator<'a> {
pub fn new(bbox: &'a BBox) -> BBoxTilesIterator<'a> {
BBoxTilesIterator {
bbox: bbox,
tiles: vec![Tile::new(0, 0, 0).unwrap()],
tile_index: 0,
}
}
}
impl<'a> Iterator for BBoxTilesIterator<'a> {
type Item = Tile;
fn next(&mut self) -> Option<Tile> {
if self.tile_index >= self.tiles.len() {
let mut new_tiles: Vec<Tile> = Vec::with_capacity(self.tiles.len() * 4);
for t in self.tiles.iter() {
match t.subtiles() {
None => {}
Some(sub) => {
if self.bbox.overlaps_bbox(&sub[0].bbox()) {
new_tiles.push(sub[0]);
}
if self.bbox.overlaps_bbox(&sub[1].bbox()) {
new_tiles.push(sub[1]);
}
if self.bbox.overlaps_bbox(&sub[2].bbox()) {
new_tiles.push(sub[2]);
}
if self.bbox.overlaps_bbox(&sub[3].bbox()) {
new_tiles.push(sub[3]);
}
}
}
}
new_tiles.shrink_to_fit();
self.tiles = new_tiles;
self.tile_index = 0;
}
let tile = self.tiles[self.tile_index];
self.tile_index += 1;
Some(tile)
}
}
fn xy_to_tc(x: u32, y: u32) -> [String; 6] {
[
format!("{:03}", x / 1_000_000),
format!("{:03}", (x / 1_000) % 1_000),
format!("{:03}", x % 1_000),
format!("{:03}", y / 1_000_000),
format!("{:03}", (y / 1_000) % 1_000),
format!("{:03}", y % 1_000),
]
}
fn xy_to_mp(x: u32, y: u32) -> [String; 4] {
[
format!("{:04}", x / 10_000),
format!("{:04}", x % 10_000),
format!("{:04}", y / 10_000),
format!("{:04}", y % 10_000),
]
}
fn xy_to_ts(x: u32, y: u32) -> [String; 4] {
[
format!("{:03}", x / 1_000),
format!("{:03}", x % 1_000),
format!("{:03}", y / 1_000),
format!("{:03}", y % 1_000),
]
}
fn xy_to_mt(x: u32, y: u32) -> [String; 5] {
let mut x = x;
let mut y = y;
let e = (((x & 0x0f) << 4) | (y & 0x0f)) as u8;
x >>= 4;
y >>= 4;
let d = (((x & 0x0f) << 4) | (y & 0x0f)) as u8;
x >>= 4;
y >>= 4;
let c = (((x & 0b000_1111 as u32) << 4) | (y & 0b000_1111 as u32)) as u8;
x >>= 4;
y >>= 4;
let b = (((x & 0b000_1111 as u32) << 4) | (y & 0b000_1111 as u32)) as u8;
x >>= 4;
y >>= 4;
let a = (((x & 0b000_1111 as u32) << 4) | (y & 0b000_1111 as u32)) as u8;
[
format!("{}", a),
format!("{}", b),
format!("{}", c),
format!("{}", d),
format!("{}", e),
]
}
fn num_tiles_in_zoom(zoom: u8) -> Option<usize> {
if zoom == 0 {
Some(1)
} else if zoom <= 5 {
Some(2u64.pow(2u32.pow(zoom as u32)) as usize)
} else {
None
}
}
pub fn xy_to_zorder(x: u32, y: u32) -> u64 {
let mut res: u64 = 0;
for i in 0..32 {
let x_set: bool = (x >> i) & 1 == 1;
let y_set: bool = (y >> i) & 1 == 1;
if x_set {
res |= 1 << (i * 2);
}
if y_set {
res |= 1 << (i * 2) + 1;
}
}
res
}
pub fn zorder_to_xy(zorder: u64) -> (u32, u32) {
let mut x: u32 = 0;
let mut y: u32 = 0;
for i in 0..32 {
let x_bit_set = (zorder >> (i * 2)) & 1 == 1;
let y_bit_set = (zorder >> ((i * 2) + 1)) & 1 == 1;
if x_bit_set {
x |= 1 << i;
}
if y_bit_set {
y |= 1 << i;
}
}
(x, y)
}