use crate::{lat_to_y, lon_to_x, tools::Tool};
pub struct Bounds {
pub height: u32,
pub width: u32,
pub x_center: f64,
pub y_center: f64,
pub x_min: i32,
pub x_max: i32,
pub y_min: i32,
pub y_max: i32,
pub tile_size: u32,
pub zoom: u8,
}
impl Bounds {
pub fn x_to_px(&self, x: f64) -> f64 {
let px = (x - self.x_center) * f64::from(self.tile_size) + f64::from(self.width) / 2.;
px.round()
}
pub fn y_to_px(&self, y: f64) -> f64 {
let px = (y - self.y_center) * f64::from(self.tile_size) + f64::from(self.height) / 2.;
px.round()
}
}
#[derive(Default)]
pub struct BoundsBuilder {
lon_min: f64,
lat_min: f64,
lon_max: f64,
lat_max: f64,
zoom: Option<u8>,
height: u32,
width: u32,
padding: (u32, u32),
tile_size: u32,
lat_center: Option<f64>,
lon_center: Option<f64>,
}
impl BoundsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn zoom(mut self, zoom: Option<u8>) -> Self {
self.zoom = zoom;
self
}
pub fn tile_size(mut self, size: u32) -> Self {
self.tile_size = size;
self
}
pub fn height(mut self, height: u32) -> Self {
self.height = height;
self
}
pub fn width(mut self, width: u32) -> Self {
self.width = width;
self
}
pub fn lon_center(mut self, center: Option<f64>) -> Self {
self.lon_center = center;
self
}
pub fn lat_center(mut self, center: Option<f64>) -> Self {
self.lat_center = center;
self
}
pub fn padding(mut self, padding: (u32, u32)) -> Self {
self.padding = padding;
self
}
pub fn build(&mut self, tools: &[Box<dyn Tool>]) -> Bounds {
let zoom = if let Some(z) = self.zoom {
self.determine_extent(z, tools);
z
} else {
self.calculate_zoom(tools)
};
let (lon_center, lat_center) = match self.lon_center.zip(self.lat_center) {
Some((x, y)) => (x, y),
_ => (
(self.lon_min + self.lon_max) / 2.,
(self.lat_min + self.lat_max) / 2.,
),
};
let x_center = lon_to_x(lon_center, zoom);
let y_center = lat_to_y(lat_center, zoom);
let x_m = 0.5 * f64::from(self.width) / f64::from(self.tile_size);
let y_m = 0.5 * f64::from(self.height) / f64::from(self.tile_size);
let x_min = (x_center - x_m).floor() as i32;
let x_max = (x_center + x_m).ceil() as i32;
let y_min = (y_center - y_m).floor() as i32;
let y_max = (y_center + y_m).ceil() as i32;
Bounds {
height: self.height,
width: self.width,
x_center,
y_center,
x_min,
x_max,
y_min,
y_max,
tile_size: self.tile_size,
zoom,
}
}
#[inline]
fn determine_height(&self, zoom: u8) -> f64 {
(lat_to_y(self.lat_min, zoom) - lat_to_y(self.lat_max, zoom)) * f64::from(self.tile_size)
}
#[inline]
fn determine_width(&self, zoom: u8) -> f64 {
(lon_to_x(self.lon_max, zoom) - lon_to_x(self.lon_min, zoom)) * f64::from(self.tile_size)
}
#[inline]
fn determine_extent(&mut self, zoom: u8, tools: &[Box<dyn Tool>]) {
let extent: Vec<(f64, f64, f64, f64)> = tools
.iter()
.map(|x| x.extent(zoom, self.tile_size.into()))
.collect();
self.lon_min = extent.iter().map(|x| x.0).fold(f64::NAN, f64::min);
self.lat_min = extent.iter().map(|x| x.1).fold(f64::NAN, f64::min);
self.lon_max = extent.iter().map(|x| x.2).fold(f64::NAN, f64::max);
self.lat_max = extent.iter().map(|x| x.3).fold(f64::NAN, f64::max);
}
fn calculate_zoom(&mut self, tools: &[Box<dyn Tool>]) -> u8 {
let mut zoom = 1;
for z in (0..=17).rev() {
self.determine_extent(z, tools);
if self.determine_width(z) > (self.width - self.padding.0 * 2).into() {
continue;
}
if self.determine_height(z) > (self.height - self.padding.1 * 2).into() {
continue;
}
zoom = z;
break;
}
zoom
}
}