use std::{fmt::Display, sync::Arc};
pub mod errors;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TileSet {
Contours,
Countries,
Hillshading,
Land,
Landcover,
MaptilerPlanet,
MaptilerPlanetLite,
OpenMapTiles,
OpenMapTilesWGS84,
Outdoor,
Satellite,
SatelliteMediumRes2016,
SatelliteMediumRes2018,
Terrain3D,
TerrainRGB,
Custom {
endpoint: &'static str,
extension: &'static str,
},
}
impl TileSet {
pub fn endpoint(&self) -> &'static str {
match self {
TileSet::Contours => "contours",
TileSet::Countries => "countries",
TileSet::Hillshading => "hillshades",
TileSet::Land => "land",
TileSet::Landcover => "landcover",
TileSet::MaptilerPlanet => "v3",
TileSet::MaptilerPlanetLite => "v3-lite",
TileSet::OpenMapTiles => "v3-openmaptiles",
TileSet::OpenMapTilesWGS84 => "v3-4326",
TileSet::Outdoor => "outdoor",
TileSet::Satellite => "satellite",
TileSet::SatelliteMediumRes2016 => "satellite-mediumres",
TileSet::SatelliteMediumRes2018 => "satellite-mediumres-2018",
TileSet::Terrain3D => "terrain-quantized-mesh",
TileSet::TerrainRGB => "terrain-rgb",
TileSet::Custom {
endpoint,
extension: _,
} => endpoint,
}
}
pub fn max_zoom(&self) -> u32 {
match self {
TileSet::Contours => 14,
TileSet::Countries => 11,
TileSet::Hillshading => 12,
TileSet::Land => 14,
TileSet::Landcover => 9,
TileSet::MaptilerPlanet => 14,
TileSet::MaptilerPlanetLite => 10,
TileSet::OpenMapTiles => 14,
TileSet::OpenMapTilesWGS84 => 13,
TileSet::Outdoor => 14,
TileSet::Satellite => 20,
TileSet::SatelliteMediumRes2016 => 13,
TileSet::SatelliteMediumRes2018 => 13,
TileSet::Terrain3D => 13,
TileSet::TerrainRGB => 12,
TileSet::Custom {
endpoint: _,
extension: _,
} => 20,
}
}
pub fn min_zoom(&self) -> u32 {
match self {
TileSet::Contours => 9,
TileSet::Countries => 0,
TileSet::Hillshading => 0,
TileSet::Land => 0,
TileSet::Landcover => 0,
TileSet::MaptilerPlanet => 0,
TileSet::MaptilerPlanetLite => 0,
TileSet::OpenMapTiles => 0,
TileSet::OpenMapTilesWGS84 => 0,
TileSet::Outdoor => 5,
TileSet::Satellite => 0,
TileSet::SatelliteMediumRes2016 => 0,
TileSet::SatelliteMediumRes2018 => 0,
TileSet::Terrain3D => 0,
TileSet::TerrainRGB => 0,
TileSet::Custom {
endpoint: _,
extension: _,
} => 0,
}
}
pub fn file_extension(&self) -> &'static str {
match self {
TileSet::Contours
| TileSet::Countries
| TileSet::Land
| TileSet::Landcover
| TileSet::MaptilerPlanet
| TileSet::MaptilerPlanetLite
| TileSet::OpenMapTiles
| TileSet::OpenMapTilesWGS84
| TileSet::Outdoor => "pbf",
TileSet::Hillshading | TileSet::TerrainRGB => "png",
TileSet::Satellite
| TileSet::SatelliteMediumRes2016
| TileSet::SatelliteMediumRes2018 => "jpg",
TileSet::Terrain3D => "quantized-mesh-1.0",
TileSet::Custom {
endpoint: _,
extension,
} => extension,
}
}
}
impl Display for TileSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
TileSet::Contours => "Contours",
TileSet::Countries => "Countries",
TileSet::Hillshading => "Hillshades",
TileSet::Land => "Land",
TileSet::Landcover => "Landcover",
TileSet::MaptilerPlanet => "MaptilerPlanet",
TileSet::MaptilerPlanetLite => "MaptilerPlanetLite",
TileSet::OpenMapTiles => "OpenMapTiles",
TileSet::OpenMapTilesWGS84 => "OpenMapTilesWGS84",
TileSet::Outdoor => "Outdoor",
TileSet::Satellite => "Satellite",
TileSet::SatelliteMediumRes2016 => "SatelliteMediumRes2016",
TileSet::SatelliteMediumRes2018 => "SatelliteMediumRes2018",
TileSet::Terrain3D => "Terrain3D",
TileSet::TerrainRGB => "TerrainRGB",
TileSet::Custom {
endpoint,
extension: _,
} => endpoint,
}
)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct TileRequest {
set: TileSet,
zoom: u32,
tile_x: u32,
tile_y: u32,
}
impl TileRequest {
pub fn new(set: TileSet, x: u32, y: u32, zoom: u32) -> Result<Self, errors::ArgumentError> {
if zoom > set.max_zoom() {
return Err(errors::ArgumentError::ZoomTooLarge(
zoom,
set,
set.max_zoom(),
));
} else if zoom < set.min_zoom() {
return Err(errors::ArgumentError::ZoomTooSmall(
zoom,
set,
set.min_zoom(),
));
}
let max_coordinate = Self::max_coordinate_with_zoom(zoom);
if x > max_coordinate {
return Err(errors::ArgumentError::XTooLarge(x, zoom, max_coordinate));
}
if y > max_coordinate {
return Err(errors::ArgumentError::YTooLarge(y, zoom, max_coordinate));
}
Ok(Self {
set,
zoom,
tile_x: x,
tile_y: y,
})
}
fn max_coordinate_with_zoom(zoom: u32) -> u32 {
if zoom == 0 {
0
} else {
1 << zoom
}
}
pub fn x(&self) -> u32 {
self.tile_x
}
pub fn y(&self) -> u32 {
self.tile_y
}
pub fn zoom(&self) -> u32 {
self.zoom
}
}
impl From<TileRequest> for RequestType {
fn from(tile_request: TileRequest) -> Self {
RequestType::TileRequest(tile_request)
}
}
#[derive(Debug, Copy, Clone)]
pub enum RequestType {
TileRequest(TileRequest),
}
#[derive(Debug, Clone)]
pub struct ConstructedRequest {
api_key: Arc<String>,
inner: RequestType,
client: Arc<reqwest::Client>,
}
impl ConstructedRequest {
pub async fn execute(&self) -> Result<Vec<u8>, errors::Error> {
match self.inner {
RequestType::TileRequest(tile_request) => self.execute_tile(tile_request).await,
}
}
async fn execute_tile(&self, tile_request: TileRequest) -> Result<Vec<u8>, errors::Error> {
let tileset = &tile_request.set;
let endpoint = tileset.endpoint();
let extension = tileset.file_extension();
let zoom = tile_request.zoom;
let x = tile_request.tile_x;
let y = tile_request.tile_y;
let url = format!(
"https://api.maptiler.com/tiles/{}/{}/{}/{}.{}?key={}",
endpoint, zoom, x, y, extension, &self.api_key
);
let res = self.client.get(url).send().await?;
match res.status() {
reqwest::StatusCode::OK => Ok(res.bytes().await?.to_vec()),
status => Err(errors::Error::Http(status)),
}
}
}
pub struct Maptiler {
api_key: Arc<String>,
client: Arc<reqwest::Client>,
}
impl Maptiler {
pub fn new<S>(api_key: S) -> Result<Self, errors::Error>
where
S: Into<String>,
{
Ok(Self {
api_key: Arc::new(api_key.into()),
client: Arc::new(reqwest::Client::builder().build()?),
})
}
pub fn new_with_client<S>(
api_key: S,
client: Arc<reqwest::Client>,
) -> Result<Self, errors::Error>
where
S: Into<String>,
{
Ok(Self {
api_key: Arc::new(api_key.into()),
client,
})
}
pub fn create_request(&self, request: impl Into<RequestType>) -> ConstructedRequest {
ConstructedRequest {
api_key: Arc::clone(&self.api_key),
inner: request.into(),
client: self.client.clone(),
}
}
pub fn create_tile_request(&self, tile_request: TileRequest) -> ConstructedRequest {
ConstructedRequest {
api_key: Arc::clone(&self.api_key),
inner: RequestType::TileRequest(tile_request),
client: self.client.clone(),
}
}
}