mirror of
https://github.com/maplibre/martin.git
synced 2024-12-18 12:21:56 +03:00
feat: add bounds to tilejson endpoints (#260) (h/t @jaspervercnocke)
This commit is contained in:
parent
d22cc7772b
commit
40b0a0c26a
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -97,6 +97,7 @@ jobs:
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/table_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/points1_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/points2_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/points3857_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source_query_params.sql
|
||||
env:
|
||||
|
1
.github/workflows/grcov.yml
vendored
1
.github/workflows/grcov.yml
vendored
@ -32,6 +32,7 @@ jobs:
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/table_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/points1_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/points2_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/points3857_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source.sql
|
||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source_query_params.sql
|
||||
env:
|
||||
|
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -1246,6 +1246,7 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"num_cpus",
|
||||
"postgis",
|
||||
"postgres",
|
||||
"postgres-native-tls",
|
||||
"postgres-protocol",
|
||||
@ -1641,6 +1642,17 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgis"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b52406590b7a682cadd0f0339c43905eb323568e84a2e97e855ef92645e0ec09"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes 1.1.0",
|
||||
"postgres-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgres"
|
||||
version = "0.19.2"
|
||||
|
@ -29,6 +29,7 @@ num_cpus = "1.13"
|
||||
postgres = { version = "0.19.1", features = ["with-time-0_2", "with-uuid-0_8", "with-serde_json-1"] }
|
||||
postgres-native-tls = "0.5.0"
|
||||
postgres-protocol = "0.6.2"
|
||||
postgis = "0.9.0"
|
||||
r2d2 = "0.8"
|
||||
r2d2_postgres = "0.18"
|
||||
semver = "1.0"
|
||||
|
@ -16,6 +16,7 @@ fn mock_table_source(schema: &str, table: &str) -> TableSource {
|
||||
table: table.to_owned(),
|
||||
id_column: None,
|
||||
geometry_column: "geom".to_owned(),
|
||||
bounds: None,
|
||||
srid: 3857,
|
||||
extent: Some(4096),
|
||||
buffer: Some(64),
|
||||
|
@ -47,6 +47,21 @@ impl CompositeSource {
|
||||
|
||||
format!("{} {}", bounds_cte, tile_query)
|
||||
}
|
||||
|
||||
pub fn get_bounds(&self) -> Option<Vec<f32>> {
|
||||
self.table_sources
|
||||
.iter()
|
||||
.filter_map(|table_source| table_source.bounds.as_ref())
|
||||
.map(|bounds| bounds.to_vec())
|
||||
.reduce(|a, b| {
|
||||
vec![
|
||||
if a[0] < b[0] { a[0] } else { b[0] },
|
||||
if a[1] < b[1] { a[1] } else { b[1] },
|
||||
if a[2] > b[2] { a[2] } else { b[2] },
|
||||
if a[3] > b[3] { a[3] } else { b[3] },
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for CompositeSource {
|
||||
@ -60,6 +75,10 @@ impl Source for CompositeSource {
|
||||
tilejson_builder.scheme("xyz");
|
||||
tilejson_builder.name(&self.id);
|
||||
|
||||
if let Some(bounds) = self.get_bounds() {
|
||||
tilejson_builder.bounds(bounds);
|
||||
};
|
||||
|
||||
Ok(tilejson_builder.finalize())
|
||||
}
|
||||
|
||||
|
23
src/dev.rs
23
src/dev.rs
@ -19,7 +19,8 @@ pub fn mock_table_sources() -> Option<TableSources> {
|
||||
table: "table_source".to_owned(),
|
||||
id_column: None,
|
||||
geometry_column: "geom".to_owned(),
|
||||
srid: 3857,
|
||||
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
|
||||
srid: 4326,
|
||||
extent: Some(4096),
|
||||
buffer: Some(64),
|
||||
clip_geom: Some(true),
|
||||
@ -33,7 +34,8 @@ pub fn mock_table_sources() -> Option<TableSources> {
|
||||
table: "points1".to_owned(),
|
||||
id_column: None,
|
||||
geometry_column: "geom".to_owned(),
|
||||
srid: 3857,
|
||||
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
|
||||
srid: 4326,
|
||||
extent: Some(4096),
|
||||
buffer: Some(64),
|
||||
clip_geom: Some(true),
|
||||
@ -47,6 +49,22 @@ pub fn mock_table_sources() -> Option<TableSources> {
|
||||
table: "points2".to_owned(),
|
||||
id_column: None,
|
||||
geometry_column: "geom".to_owned(),
|
||||
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
|
||||
srid: 4326,
|
||||
extent: Some(4096),
|
||||
buffer: Some(64),
|
||||
clip_geom: Some(true),
|
||||
geometry_type: None,
|
||||
properties: HashMap::new(),
|
||||
};
|
||||
|
||||
let table_source3857 = TableSource {
|
||||
id: "public.points3857".to_owned(),
|
||||
schema: "public".to_owned(),
|
||||
table: "points3857".to_owned(),
|
||||
id_column: None,
|
||||
geometry_column: "geom".to_owned(),
|
||||
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
|
||||
srid: 3857,
|
||||
extent: Some(4096),
|
||||
buffer: Some(64),
|
||||
@ -59,6 +77,7 @@ pub fn mock_table_sources() -> Option<TableSources> {
|
||||
table_sources.insert("public.table_source".to_owned(), Box::new(source));
|
||||
table_sources.insert("public.points1".to_owned(), Box::new(table_source1));
|
||||
table_sources.insert("public.points2".to_owned(), Box::new(table_source2));
|
||||
table_sources.insert("public.points3857".to_owned(), Box::new(table_source3857));
|
||||
Some(table_sources)
|
||||
}
|
||||
|
||||
|
4
src/scripts/get_bounds.sql
Executable file
4
src/scripts/get_bounds.sql
Executable file
@ -0,0 +1,4 @@
|
||||
SELECT
|
||||
ST_Transform (ST_SetSRID (ST_Extent ({geometry_column}), {srid}), 4326) AS bounds
|
||||
FROM
|
||||
{id}
|
@ -6,7 +6,7 @@ use tilejson::{TileJSON, TileJSONBuilder};
|
||||
|
||||
use crate::db::Connection;
|
||||
use crate::source::{Query, Source, Tile, Xyz};
|
||||
use crate::utils::{get_bounds_cte, get_srid_bounds, json_to_hashmap, prettify_error, tilebbox};
|
||||
use crate::utils;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TableSource {
|
||||
@ -16,6 +16,7 @@ pub struct TableSource {
|
||||
pub id_column: Option<String>,
|
||||
pub geometry_column: String,
|
||||
pub srid: u32,
|
||||
pub bounds: Option<Vec<f32>>,
|
||||
pub extent: Option<u32>,
|
||||
pub buffer: Option<u32>,
|
||||
pub clip_geom: Option<bool>,
|
||||
@ -27,7 +28,7 @@ pub type TableSources = HashMap<String, Box<TableSource>>;
|
||||
|
||||
impl TableSource {
|
||||
pub fn get_geom_query(&self, xyz: &Xyz) -> String {
|
||||
let mercator_bounds = tilebbox(xyz);
|
||||
let mercator_bounds = utils::tilebbox(xyz);
|
||||
|
||||
let properties = if self.properties.is_empty() {
|
||||
"".to_string()
|
||||
@ -73,8 +74,8 @@ impl TableSource {
|
||||
}
|
||||
|
||||
pub fn build_tile_query(&self, xyz: &Xyz) -> String {
|
||||
let srid_bounds = get_srid_bounds(self.srid, xyz);
|
||||
let bounds_cte = get_bounds_cte(srid_bounds);
|
||||
let srid_bounds = utils::get_srid_bounds(self.srid, xyz);
|
||||
let bounds_cte = utils::get_bounds_cte(srid_bounds);
|
||||
let tile_query = self.get_tile_query(xyz);
|
||||
|
||||
format!("{} {}", bounds_cte, tile_query)
|
||||
@ -92,6 +93,10 @@ impl Source for TableSource {
|
||||
tilejson_builder.scheme("xyz");
|
||||
tilejson_builder.name(&self.id);
|
||||
|
||||
if let Some(bounds) = &self.bounds {
|
||||
tilejson_builder.bounds(bounds.to_vec());
|
||||
};
|
||||
|
||||
Ok(tilejson_builder.finalize())
|
||||
}
|
||||
|
||||
@ -106,7 +111,7 @@ impl Source for TableSource {
|
||||
let tile: Tile = conn
|
||||
.query_one(tile_query.as_str(), &[])
|
||||
.map(|row| row.get("st_asmvt"))
|
||||
.map_err(prettify_error("Can't get table source tile"))?;
|
||||
.map_err(utils::prettify_error("Can't get table source tile"))?;
|
||||
|
||||
Ok(tile)
|
||||
}
|
||||
@ -121,7 +126,7 @@ pub fn get_table_sources(conn: &mut Connection) -> Result<TableSources, io::Erro
|
||||
|
||||
let rows = conn
|
||||
.query(include_str!("scripts/get_table_sources.sql"), &[])
|
||||
.map_err(prettify_error("Can't get table sources"))?;
|
||||
.map_err(utils::prettify_error("Can't get table sources"))?;
|
||||
|
||||
for row in &rows {
|
||||
let schema: String = row.get("f_table_schema");
|
||||
@ -142,7 +147,16 @@ pub fn get_table_sources(conn: &mut Connection) -> Result<TableSources, io::Erro
|
||||
continue;
|
||||
}
|
||||
|
||||
let properties = json_to_hashmap(&row.get("properties"));
|
||||
let bounds_query = utils::get_source_bounds(&id, srid as u32, &geometry_column);
|
||||
|
||||
let bounds: Option<Vec<f32>> = conn
|
||||
.query_one(bounds_query.as_str(), &[])
|
||||
.map(|row| row.get("bounds"))
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(utils::polygon_to_bbox);
|
||||
|
||||
let properties = utils::json_to_hashmap(&row.get("properties"));
|
||||
|
||||
let source = TableSource {
|
||||
id: id.to_string(),
|
||||
@ -150,6 +164,7 @@ pub fn get_table_sources(conn: &mut Connection) -> Result<TableSources, io::Erro
|
||||
table,
|
||||
id_column: None,
|
||||
geometry_column,
|
||||
bounds,
|
||||
srid: srid as u32,
|
||||
extent: Some(DEFAULT_EXTENT),
|
||||
buffer: Some(DEFAULT_BUFFER),
|
||||
|
26
src/utils.rs
26
src/utils.rs
@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::source::{Query, Xyz};
|
||||
use postgis::{ewkb, LineString, Point, Polygon};
|
||||
use postgres::types::Json;
|
||||
use serde_json::Value;
|
||||
|
||||
@ -66,3 +67,28 @@ pub fn get_srid_bounds(srid: u32, xyz: &Xyz) -> String {
|
||||
mercator_bounds = tilebbox(xyz),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_source_bounds(id: &str, srid: u32, geometry_column: &str) -> String {
|
||||
format!(
|
||||
include_str!("scripts/get_bounds.sql"),
|
||||
id = id,
|
||||
srid = srid,
|
||||
geometry_column = geometry_column,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn polygon_to_bbox(polygon: ewkb::Polygon) -> Option<Vec<f32>> {
|
||||
polygon.rings().next().and_then(|linestring| {
|
||||
let mut points = linestring.points();
|
||||
if let (Some(bottom_left), Some(top_right)) = (points.next(), points.nth(1)) {
|
||||
Some(vec![
|
||||
bottom_left.x() as f32,
|
||||
bottom_left.y() as f32,
|
||||
top_right.x() as f32,
|
||||
top_right.y() as f32,
|
||||
])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
19
tests/fixtures/points3857_source.sql
vendored
Normal file
19
tests/fixtures/points3857_source.sql
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
CREATE TABLE points3857(gid SERIAL PRIMARY KEY, geom GEOMETRY(POINT, 3857));
|
||||
|
||||
INSERT INTO points3857
|
||||
SELECT
|
||||
generate_series(1, 10000) as id,
|
||||
(
|
||||
ST_DUMP(
|
||||
ST_GENERATEPOINTS(
|
||||
ST_TRANSFORM(
|
||||
ST_GEOMFROMTEXT('POLYGON ((-179 89, 179 89, 179 -89, -179 -89, -179 89))', 4326),
|
||||
3857
|
||||
),
|
||||
10000
|
||||
)
|
||||
)
|
||||
).geom;
|
||||
|
||||
CREATE INDEX ON points3857 USING GIST(geom);
|
||||
CLUSTER points3857_geom_idx ON points3857;
|
@ -14,3 +14,4 @@ psql --dbname="$POSTGRES_DB" -f /fixtures/function_source_query_params.sql
|
||||
|
||||
psql --dbname="$POSTGRES_DB" -f /fixtures/points1_source.sql
|
||||
psql --dbname="$POSTGRES_DB" -f /fixtures/points2_source.sql
|
||||
psql --dbname="$POSTGRES_DB" -f /fixtures/points3857_source.sql
|
||||
|
@ -102,7 +102,7 @@ async fn test_get_composite_source_ok() {
|
||||
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri("/public.points1,public.points2.json")
|
||||
.uri("/public.points1,public.points2,public.points3857.json")
|
||||
.to_request();
|
||||
|
||||
let response = test::call_service(&mut app, req).await;
|
||||
@ -124,7 +124,7 @@ async fn test_get_composite_source_tile_ok() {
|
||||
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri("/public.points1,public.points2/0/0/0.pbf")
|
||||
.uri("/public.points1,public.points2,public.points3857/0/0/0.pbf")
|
||||
.to_request();
|
||||
|
||||
let response = test::call_service(&mut app, req).await;
|
||||
|
Loading…
Reference in New Issue
Block a user