use std::{collections::HashMap, sync::OnceLock};
use axum::{
Json, Router,
extract::{Path, State},
http::StatusCode,
routing::get,
};
use serde::Deserialize;
use ogcapi_types::{
common::{
Link,
link_rel::{TILESETS_VECTOR, TILING_SCHEME},
media_type::JSON,
},
tiles::{Query, TileMatrix, TileMatrixSet, TileMatrixSetItem, TileMatrixSets, TileSets},
};
use crate::{
AppState, Error, Result,
extractors::{Qs, RemoteUrl},
};
const CONFORMANCE: [&str; 7] = [
"http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/core",
"http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/tileset",
"http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/tilesets-list",
"http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/geodata-tilesets",
"http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/dataset-tilesets",
"http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/geodata-selection",
"http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/mvt",
];
const WEB_MERCARTOR_QUAD: &[u8; 8005] = include_bytes!("../../assets/tms/WebMercartorQuad.json");
static TMS: OnceLock<HashMap<String, TileMatrixSet>> = OnceLock::new();
static TM: OnceLock<HashMap<String, HashMap<String, TileMatrix>>> = OnceLock::new();
#[derive(Deserialize, Debug)]
pub struct TileParams {
collection_id: Option<String>,
tms_id: String,
matrix: String,
row: u32,
col: u32,
}
async fn tile_matrix_sets(RemoteUrl(url): RemoteUrl) -> Result<Json<TileMatrixSets>> {
let tms = TMS.get().expect("TMS cell to be inizialized");
let mut tile_matrix_sets = Vec::new();
for tms in tms.values() {
let item = TileMatrixSetItem {
id: Some(tms.id.to_owned()),
title: tms.title_description_keywords.title.to_owned(),
links: vec![Link::new(
url.join(&format!("tileMatrixSets/{}", &tms.id))?,
TILING_SCHEME,
)],
..Default::default()
};
tile_matrix_sets.push(item);
}
Ok(Json(TileMatrixSets { tile_matrix_sets }))
}
async fn tile_matrix_set(Path(id): Path<String>) -> Result<Json<TileMatrixSet>> {
match TMS.get().and_then(|tms| tms.get(&id)) {
Some(tms) => Ok(Json(tms.to_owned())),
None => Err(Error::Exception(
StatusCode::NOT_FOUND,
"Unable to find resource".to_string(),
)),
}
}
async fn tiles() -> Result<Json<TileSets>> {
let tile_sets = TileSets {
tilesets: vec![],
links: None,
};
Ok(Json(tile_sets))
}
async fn tile(
Path(params): Path<TileParams>,
Qs(query): Qs<Query>,
State(state): State<AppState>,
) -> Result<Vec<u8>> {
let tms = TMS
.get()
.and_then(|tms| tms.get(¶ms.tms_id))
.expect("Get tms from TMS");
let tiles = state
.drivers
.tiles
.tile(
¶ms.collection_id.or(query.collections).unwrap(),
tms,
¶ms.matrix,
params.row,
params.col,
)
.await?;
Ok(tiles)
}
pub(crate) fn router(state: &AppState) -> Router<AppState> {
let mut root = state.root.write().unwrap();
root.links.push(
Link::new("tiles", TILESETS_VECTOR)
.title("List of available vector features tilesets for the dataset")
.mediatype(JSON),
);
state.conformance.write().unwrap().extend(&CONFORMANCE);
let mut tms_map = HashMap::new();
let web_mercartor_quad: TileMatrixSet =
serde_json::from_slice(WEB_MERCARTOR_QUAD).expect("parse tms");
tms_map.insert(web_mercartor_quad.id.to_owned(), web_mercartor_quad);
let mut tm = HashMap::new();
for tms in tms_map.values() {
tm.insert(tms.id.to_owned(), HashMap::new());
for tile_matrix in &tms.tile_matrices {
tm.get_mut(&tms.id).and_then(|tm_map| {
tm_map.insert(tile_matrix.id.to_owned(), tile_matrix.to_owned())
});
}
}
TMS.set(tms_map).expect("set `TMS` once cell content");
TM.set(tm).expect("set `TM` once cell content");
Router::new()
.route("/tileMatrixSets", get(tile_matrix_sets))
.route("/tileMatrixSets/{tms_id}", get(tile_matrix_set))
.route("/tiles", get(tiles))
.route("/tiles/{tms_id}", get(tile_matrix_set))
.route("/tiles/{tms_id}/{matrix}/{row}/{col}", get(tile))
.route(
"/collections/{collection_id}/tiles/{tms_id}/{matrix}/{row}/{col}",
get(tile),
)
}