mirror of
https://github.com/maplibre/martin.git
synced 2024-12-20 05:11:57 +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/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/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/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.sql
|
||||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source_query_params.sql
|
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source_query_params.sql
|
||||||
env:
|
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/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/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/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.sql
|
||||||
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source_query_params.sql
|
psql -h $POSTGRES_HOST -p $POSTGRES_PORT -U postgres -d test -f tests/fixtures/function_source_query_params.sql
|
||||||
env:
|
env:
|
||||||
|
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -1246,6 +1246,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"postgis",
|
||||||
"postgres",
|
"postgres",
|
||||||
"postgres-native-tls",
|
"postgres-native-tls",
|
||||||
"postgres-protocol",
|
"postgres-protocol",
|
||||||
@ -1641,6 +1642,17 @@ dependencies = [
|
|||||||
"plotters-backend",
|
"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]]
|
[[package]]
|
||||||
name = "postgres"
|
name = "postgres"
|
||||||
version = "0.19.2"
|
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 = { version = "0.19.1", features = ["with-time-0_2", "with-uuid-0_8", "with-serde_json-1"] }
|
||||||
postgres-native-tls = "0.5.0"
|
postgres-native-tls = "0.5.0"
|
||||||
postgres-protocol = "0.6.2"
|
postgres-protocol = "0.6.2"
|
||||||
|
postgis = "0.9.0"
|
||||||
r2d2 = "0.8"
|
r2d2 = "0.8"
|
||||||
r2d2_postgres = "0.18"
|
r2d2_postgres = "0.18"
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
|
@ -16,6 +16,7 @@ fn mock_table_source(schema: &str, table: &str) -> TableSource {
|
|||||||
table: table.to_owned(),
|
table: table.to_owned(),
|
||||||
id_column: None,
|
id_column: None,
|
||||||
geometry_column: "geom".to_owned(),
|
geometry_column: "geom".to_owned(),
|
||||||
|
bounds: None,
|
||||||
srid: 3857,
|
srid: 3857,
|
||||||
extent: Some(4096),
|
extent: Some(4096),
|
||||||
buffer: Some(64),
|
buffer: Some(64),
|
||||||
|
@ -47,6 +47,21 @@ impl CompositeSource {
|
|||||||
|
|
||||||
format!("{} {}", bounds_cte, tile_query)
|
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 {
|
impl Source for CompositeSource {
|
||||||
@ -60,6 +75,10 @@ impl Source for CompositeSource {
|
|||||||
tilejson_builder.scheme("xyz");
|
tilejson_builder.scheme("xyz");
|
||||||
tilejson_builder.name(&self.id);
|
tilejson_builder.name(&self.id);
|
||||||
|
|
||||||
|
if let Some(bounds) = self.get_bounds() {
|
||||||
|
tilejson_builder.bounds(bounds);
|
||||||
|
};
|
||||||
|
|
||||||
Ok(tilejson_builder.finalize())
|
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(),
|
table: "table_source".to_owned(),
|
||||||
id_column: None,
|
id_column: None,
|
||||||
geometry_column: "geom".to_owned(),
|
geometry_column: "geom".to_owned(),
|
||||||
srid: 3857,
|
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
|
||||||
|
srid: 4326,
|
||||||
extent: Some(4096),
|
extent: Some(4096),
|
||||||
buffer: Some(64),
|
buffer: Some(64),
|
||||||
clip_geom: Some(true),
|
clip_geom: Some(true),
|
||||||
@ -33,7 +34,8 @@ pub fn mock_table_sources() -> Option<TableSources> {
|
|||||||
table: "points1".to_owned(),
|
table: "points1".to_owned(),
|
||||||
id_column: None,
|
id_column: None,
|
||||||
geometry_column: "geom".to_owned(),
|
geometry_column: "geom".to_owned(),
|
||||||
srid: 3857,
|
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
|
||||||
|
srid: 4326,
|
||||||
extent: Some(4096),
|
extent: Some(4096),
|
||||||
buffer: Some(64),
|
buffer: Some(64),
|
||||||
clip_geom: Some(true),
|
clip_geom: Some(true),
|
||||||
@ -47,6 +49,22 @@ pub fn mock_table_sources() -> Option<TableSources> {
|
|||||||
table: "points2".to_owned(),
|
table: "points2".to_owned(),
|
||||||
id_column: None,
|
id_column: None,
|
||||||
geometry_column: "geom".to_owned(),
|
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,
|
srid: 3857,
|
||||||
extent: Some(4096),
|
extent: Some(4096),
|
||||||
buffer: Some(64),
|
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.table_source".to_owned(), Box::new(source));
|
||||||
table_sources.insert("public.points1".to_owned(), Box::new(table_source1));
|
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.points2".to_owned(), Box::new(table_source2));
|
||||||
|
table_sources.insert("public.points3857".to_owned(), Box::new(table_source3857));
|
||||||
Some(table_sources)
|
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::db::Connection;
|
||||||
use crate::source::{Query, Source, Tile, Xyz};
|
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)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TableSource {
|
pub struct TableSource {
|
||||||
@ -16,6 +16,7 @@ pub struct TableSource {
|
|||||||
pub id_column: Option<String>,
|
pub id_column: Option<String>,
|
||||||
pub geometry_column: String,
|
pub geometry_column: String,
|
||||||
pub srid: u32,
|
pub srid: u32,
|
||||||
|
pub bounds: Option<Vec<f32>>,
|
||||||
pub extent: Option<u32>,
|
pub extent: Option<u32>,
|
||||||
pub buffer: Option<u32>,
|
pub buffer: Option<u32>,
|
||||||
pub clip_geom: Option<bool>,
|
pub clip_geom: Option<bool>,
|
||||||
@ -27,7 +28,7 @@ pub type TableSources = HashMap<String, Box<TableSource>>;
|
|||||||
|
|
||||||
impl TableSource {
|
impl TableSource {
|
||||||
pub fn get_geom_query(&self, xyz: &Xyz) -> String {
|
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() {
|
let properties = if self.properties.is_empty() {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
@ -73,8 +74,8 @@ impl TableSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_tile_query(&self, xyz: &Xyz) -> String {
|
pub fn build_tile_query(&self, xyz: &Xyz) -> String {
|
||||||
let srid_bounds = get_srid_bounds(self.srid, xyz);
|
let srid_bounds = utils::get_srid_bounds(self.srid, xyz);
|
||||||
let bounds_cte = get_bounds_cte(srid_bounds);
|
let bounds_cte = utils::get_bounds_cte(srid_bounds);
|
||||||
let tile_query = self.get_tile_query(xyz);
|
let tile_query = self.get_tile_query(xyz);
|
||||||
|
|
||||||
format!("{} {}", bounds_cte, tile_query)
|
format!("{} {}", bounds_cte, tile_query)
|
||||||
@ -92,6 +93,10 @@ impl Source for TableSource {
|
|||||||
tilejson_builder.scheme("xyz");
|
tilejson_builder.scheme("xyz");
|
||||||
tilejson_builder.name(&self.id);
|
tilejson_builder.name(&self.id);
|
||||||
|
|
||||||
|
if let Some(bounds) = &self.bounds {
|
||||||
|
tilejson_builder.bounds(bounds.to_vec());
|
||||||
|
};
|
||||||
|
|
||||||
Ok(tilejson_builder.finalize())
|
Ok(tilejson_builder.finalize())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +111,7 @@ impl Source for TableSource {
|
|||||||
let tile: Tile = conn
|
let tile: Tile = conn
|
||||||
.query_one(tile_query.as_str(), &[])
|
.query_one(tile_query.as_str(), &[])
|
||||||
.map(|row| row.get("st_asmvt"))
|
.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)
|
Ok(tile)
|
||||||
}
|
}
|
||||||
@ -121,7 +126,7 @@ pub fn get_table_sources(conn: &mut Connection) -> Result<TableSources, io::Erro
|
|||||||
|
|
||||||
let rows = conn
|
let rows = conn
|
||||||
.query(include_str!("scripts/get_table_sources.sql"), &[])
|
.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 {
|
for row in &rows {
|
||||||
let schema: String = row.get("f_table_schema");
|
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;
|
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 {
|
let source = TableSource {
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
@ -150,6 +164,7 @@ pub fn get_table_sources(conn: &mut Connection) -> Result<TableSources, io::Erro
|
|||||||
table,
|
table,
|
||||||
id_column: None,
|
id_column: None,
|
||||||
geometry_column,
|
geometry_column,
|
||||||
|
bounds,
|
||||||
srid: srid as u32,
|
srid: srid as u32,
|
||||||
extent: Some(DEFAULT_EXTENT),
|
extent: Some(DEFAULT_EXTENT),
|
||||||
buffer: Some(DEFAULT_BUFFER),
|
buffer: Some(DEFAULT_BUFFER),
|
||||||
|
26
src/utils.rs
26
src/utils.rs
@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::source::{Query, Xyz};
|
use crate::source::{Query, Xyz};
|
||||||
|
use postgis::{ewkb, LineString, Point, Polygon};
|
||||||
use postgres::types::Json;
|
use postgres::types::Json;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
@ -66,3 +67,28 @@ pub fn get_srid_bounds(srid: u32, xyz: &Xyz) -> String {
|
|||||||
mercator_bounds = tilebbox(xyz),
|
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/points1_source.sql
|
||||||
psql --dbname="$POSTGRES_DB" -f /fixtures/points2_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);
|
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let req = test::TestRequest::get()
|
let req = test::TestRequest::get()
|
||||||
.uri("/public.points1,public.points2.json")
|
.uri("/public.points1,public.points2,public.points3857.json")
|
||||||
.to_request();
|
.to_request();
|
||||||
|
|
||||||
let response = test::call_service(&mut app, req).await;
|
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);
|
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
let req = test::TestRequest::get()
|
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();
|
.to_request();
|
||||||
|
|
||||||
let response = test::call_service(&mut app, req).await;
|
let response = test::call_service(&mut app, req).await;
|
||||||
|
Loading…
Reference in New Issue
Block a user