use crate::{get_epsg_3857_tile_bounds, TileSource};
use std::collections::HashMap;
use serde::Deserialize;
use async_trait::async_trait;
use sqlx::{query, PgPool, Row};
use futures::stream::StreamExt;
#[derive(Clone, Deserialize, Debug)]
pub struct TM2Source {
pub name: String,
pub pixel_scale: i64,
#[serde(rename = "Layer")]
pub layers: Vec<DataLayer>,
pub attribution: String,
#[serde(rename = "minzoom")]
pub min_zoom: i64,
#[serde(rename = "maxzoom")]
pub max_zoom: i64,
pub center: [f64; 3],
pub bounds: [f64; 4],
}
#[derive(Clone, Deserialize, Debug)]
pub struct DataLayer {
pub id: String,
pub properties: DataLayerProperties,
#[serde(rename = "Datasource")]
pub source: LayerSource,
}
#[derive(Clone, Deserialize, Debug)]
pub struct LayerSource {
pub table: String,
}
#[derive(Clone, Deserialize, Debug)]
pub struct DataLayerProperties {
#[serde(rename = "buffer-size")]
pub buffer_size: i64,
}
impl TM2Source {
pub fn from(data: &str) -> Result<TM2Source, failure::Error> {
let mut result: TM2Source = serde_yaml::from_str(data)?;
for layer in result.layers.iter_mut() {
layer.source.table = layer
.source
.table
.trim() .chars() .skip(1) .take(layer.source.table.len() - 7) .collect();
}
Ok(result)
}
fn buffer_sizes(&self) -> Vec<i64> {
let mut result: Vec<i64> = self
.layers
.iter()
.map(|x| x.properties.buffer_size)
.collect();
result.sort_unstable();
result.dedup();
result
}
fn prepared_statement_sql(&self) -> String {
let buffer_param_indices: HashMap<i64, i64> = self
.buffer_sizes()
.iter()
.enumerate()
.map(|(i, x)| (x.to_owned(), 7 + (i * 4) as i64))
.collect();
let queries: Vec<String> = self.layers.iter().map(|layer| {
let buffer_param_index = buffer_param_indices[&layer.properties.buffer_size];
let clip = layer.properties.buffer_size < 8; let geom = format!("ST_AsMVTGeom(geometry,!bbox_nobuffer!,4096,{},{})", layer.properties.buffer_size, clip);
let layer_query = layer.source.table.replace("geometry", &format!("{} as mvtgeometry", geom));
let base_query = format!("SELECT ST_ASMVT(tile, '{}', 4096, 'mvtgeometry') FROM ({} WHERE {} IS NOT NULL) AS tile", layer.id, layer_query, geom);
base_query
.replace("!bbox_nobuffer!", "ST_MakeBox2D(ST_Point($1, $2), ST_Point($3, $4))")
.replace("z(!scale_denominator!)", "$5")
.replace("!pixel_width!", "$6")
.replace("!bbox!", &format!("ST_MakeBox2D(ST_Point(${}, ${}), ST_Point(${}, ${}))", buffer_param_index, buffer_param_index + 1, buffer_param_index + 2, buffer_param_index + 3))
}).collect();
queries.join(" UNION ALL ")
}
}
#[async_trait]
impl TileSource for TM2Source {
async fn render_mvt(&self, pool: &PgPool, zoom: u8, x: i32, y: i32) -> Result<Vec<u8>, sqlx::Error> {
let z: i32 = zoom.into();
let tile_bounds = get_epsg_3857_tile_bounds(self.pixel_scale, zoom, x, y, 0);
let buffer_sizes = self.buffer_sizes();
let buffered_tile_bounds = buffer_sizes.iter().map(|buffer_size| {
get_epsg_3857_tile_bounds(self.pixel_scale, zoom, x, y, buffer_size.to_owned())
});
let prepare_sql = self.prepared_statement_sql();
let mut conn = pool.acquire().await?;
let init_query = query(&prepare_sql)
.bind(tile_bounds.west)
.bind(tile_bounds.south)
.bind(tile_bounds.east)
.bind(tile_bounds.north)
.bind(z)
.bind(self.pixel_scale);
let query = buffered_tile_bounds.fold(init_query, |acc, bbox| {
acc.bind(bbox.west)
.bind(bbox.south)
.bind(bbox.east)
.bind(bbox.north)
});
let mut raw_tile: Vec<u8> = Vec::new();
let mut stream = query.fetch(&mut conn);
while let Some(result) = stream.next().await {
match result {
Ok(row) => {
let layer: Vec<u8> = row.get(0);
raw_tile.extend_from_slice(&layer);
}
Err(e) => return Err(e),
}
}
Ok(raw_tile)
}
}
#[cfg(test)]
mod tests {
use std::fs::File;
use std::io::Read;
use super::*;
#[test]
fn test_parse_tm2source() {
let mut file =
File::open("test_data/tm2layers.yml").expect("Unable to open the test yml file.");
let mut data = String::new();
file.read_to_string(&mut data)
.expect("Unable to read the file");
let source: Result<TM2Source, _> = TM2Source::from(data.as_str());
match source {
Ok(result) => {
assert_eq!("OpenMapTiles", result.name);
assert_eq!(256, result.pixel_scale);
assert_ne!(0, result.layers.len());
}
Err(e) => panic!("{}", e),
}
}
#[test]
fn test_generate_prepared_statement_sql() {
let source = TM2Source {
name: String::from("Test Style"),
pixel_scale: 256,
layers: vec![
DataLayer {
id: String::from("water"),
properties: DataLayerProperties { buffer_size: 4 },
source: LayerSource {
table: String::from("(SELECT geometry FROM layer_water(!bbox!))"),
},
},
DataLayer {
id: String::from("land"),
properties: DataLayerProperties { buffer_size: 4 },
source: LayerSource {
table: String::from("(SELECT geometry FROM layer_land(!bbox!))"),
},
},
DataLayer {
id: String::from("poi"),
properties: DataLayerProperties { buffer_size: 32 },
source: LayerSource {
table: String::from("(SELECT geometry FROM layer_poi(!bbox!))"),
},
},
],
attribution: String::from("OpenStreetMap"),
min_zoom: 0,
max_zoom: 14,
center: [0.0, 0.0, 4.0],
bounds: [-180.0, -85.0511, 180.0, 85.0511],
};
let sql = source.prepared_statement_sql();
assert_ne!(0, sql.len());
assert_eq!(sql.contains("SELECT ST_ASMVT(tile, \'water\'"), true);
assert_eq!(sql.contains("SELECT ST_ASMVT(tile, \'land\'"), true);
assert_eq!(sql.contains("SELECT ST_ASMVT(tile, \'poi\'"), true);
}
}