feat: add minzoom and maxzoom support (#265)

This commit is contained in:
Stepan Kuzmin 2021-10-13 14:51:29 +03:00 committed by GitHub
parent a9cf508c33
commit 194a83e63f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 801 additions and 142 deletions

View File

@ -390,8 +390,11 @@ martin --config config.yaml
You can find an example of a configuration file [here](https://github.com/urbica/martin/blob/master/tests/config.yaml).
```yaml
# The socket address to bind [default: 0.0.0.0:3000]
listen_addresses: '0.0.0.0:3000'
# Database connection string
connection_string: 'postgres://postgres@localhost/db'
connection_string: 'postgres://postgres@localhost:5432/db'
# Maximum connections pool size [default: 20]
pool_size: 20
@ -402,60 +405,84 @@ keep_alive: 75
# Number of web server workers
worker_processes: 8
# The socket address to bind [default: 0.0.0.0:3000]
listen_addresses: '0.0.0.0:3000'
# Enable watch mode
watch: true
watch: false
# Trust invalid certificates. This introduces significant vulnerabilities, and should only be used as a last resort.
danger_accept_invalid_certs: false
# associative arrays of table sources
# Associative arrays of table sources
table_sources:
public.table_source:
# table source id
# Table source id (required)
id: public.table_source
# table schema
# Table schema (required)
schema: public
# table name
# Table name (required)
table: table_source
# geometry column name
geometry_column: geom
# geometry srid
# Geometry SRID (required)
srid: 4326
# tile extent in tile coordinate space
# Geometry column name (required)
geometry_column: geom
# Feature id column name
id_column: ~
# An integer specifying the minimum zoom level
minzoom: 0
# An integer specifying the maximum zoom level. MUST be >= minzoom
maxzoom: 30
# The maximum extent of available map tiles. Bounds MUST define an area
# covered by all zoom levels. The bounds are represented in WGS:84
# latitude and longitude values, in the order left, bottom, right, top.
# Values may be integers or floating point numbers.
bounds: [-180.0, -90.0, 180.0, 90.0]
# Tile extent in tile coordinate space
extent: 4096
# buffer distance in tile coordinate space to optionally clip geometries
# Buffer distance in tile coordinate space to optionally clip geometries
buffer: 64
# boolean to control if geometries should be clipped or encoded as is
# Boolean to control if geometries should be clipped or encoded as is
clip_geom: true
# geometry type
# Geometry type
geometry_type: GEOMETRY
# list of columns, that should be encoded as a tile properties
# List of columns, that should be encoded as tile properties (required)
properties:
gid: int4
# associative arrays of function sources
# Associative arrays of function sources
function_sources:
public.function_source:
# function source id
# Function source id (required)
id: public.function_source
# schema name
# Schema name (required)
schema: public
# function name
# Function name (required)
function: function_source
# An integer specifying the minimum zoom level
minzoom: 0
# An integer specifying the maximum zoom level. MUST be >= minzoom
maxzoom: 30
# The maximum extent of available map tiles. Bounds MUST define an area
# covered by all zoom levels. The bounds are represented in WGS:84
# latitude and longitude values, in the order left, bottom, right, top.
# Values may be integers or floating point numbers.
bounds: [-180.0, -90.0, 180.0, 90.0]
```
## Using with Docker

View File

@ -16,6 +16,8 @@ fn mock_table_source(schema: &str, table: &str) -> TableSource {
table: table.to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
minzoom: None,
maxzoom: None,
bounds: None,
srid: 3857,
extent: Some(4096),
@ -31,6 +33,9 @@ fn mock_function_source(schema: &str, function: &str) -> FunctionSource {
id: format!("{}.{}", schema, function),
schema: schema.to_owned(),
function: function.to_owned(),
minzoom: None,
maxzoom: None,
bounds: None,
}
}

View File

@ -48,6 +48,20 @@ impl CompositeSource {
format!("{} {}", bounds_cte, tile_query)
}
pub fn get_minzoom(&self) -> Option<u8> {
self.table_sources
.iter()
.filter_map(|table_source| table_source.minzoom)
.min()
}
pub fn get_maxzoom(&self) -> Option<u8> {
self.table_sources
.iter()
.filter_map(|table_source| table_source.maxzoom)
.max()
}
pub fn get_bounds(&self) -> Option<Vec<f32>> {
self.table_sources
.iter()
@ -75,6 +89,14 @@ impl Source for CompositeSource {
tilejson_builder.scheme("xyz");
tilejson_builder.name(&self.id);
if let Some(minzoom) = self.get_minzoom() {
tilejson_builder.minzoom(minzoom);
};
if let Some(maxzoom) = self.get_maxzoom() {
tilejson_builder.maxzoom(maxzoom);
};
if let Some(bounds) = self.get_bounds() {
tilejson_builder.bounds(bounds);
};

View File

@ -12,13 +12,24 @@ use crate::function_source::{FunctionSource, FunctionSources};
use crate::server::AppState;
use crate::table_source::{TableSource, TableSources};
pub fn mock_table_sources() -> Option<TableSources> {
pub fn mock_table_sources(sources: Vec<TableSource>) -> TableSources {
let mut table_sources: TableSources = HashMap::new();
for source in sources {
table_sources.insert(source.id.to_owned(), Box::new(source));
}
table_sources
}
pub fn mock_default_table_sources() -> TableSources {
let source = TableSource {
id: "public.table_source".to_owned(),
schema: "public".to_owned(),
table: "table_source".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
minzoom: Some(0),
maxzoom: Some(30),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
srid: 4326,
extent: Some(4096),
@ -34,6 +45,8 @@ pub fn mock_table_sources() -> Option<TableSources> {
table: "points1".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
minzoom: Some(0),
maxzoom: Some(30),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
srid: 4326,
extent: Some(4096),
@ -49,6 +62,8 @@ pub fn mock_table_sources() -> Option<TableSources> {
table: "points2".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
minzoom: Some(0),
maxzoom: Some(30),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
srid: 4326,
extent: Some(4096),
@ -64,6 +79,8 @@ pub fn mock_table_sources() -> Option<TableSources> {
table: "points3857".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
minzoom: Some(0),
maxzoom: Some(30),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
srid: 3857,
extent: Some(4096),
@ -73,36 +90,38 @@ pub fn mock_table_sources() -> Option<TableSources> {
properties: HashMap::new(),
};
let mut table_sources: TableSources = HashMap::new();
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)
mock_table_sources(vec![source, table_source1, table_source2, table_source3857])
}
pub fn mock_function_sources() -> Option<FunctionSources> {
pub fn mock_function_sources(sources: Vec<FunctionSource>) -> FunctionSources {
let mut function_sources: FunctionSources = HashMap::new();
for source in sources {
function_sources.insert(source.id.to_owned(), Box::new(source));
}
function_sources.insert(
"public.function_source".to_owned(),
Box::new(FunctionSource {
function_sources
}
pub fn mock_default_function_sources() -> FunctionSources {
let function_source = FunctionSource {
id: "public.function_source".to_owned(),
schema: "public".to_owned(),
function: "function_source".to_owned(),
}),
);
minzoom: Some(0),
maxzoom: Some(30),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
};
function_sources.insert(
"public.function_source_query_params".to_owned(),
Box::new(FunctionSource {
let function_source_query_params = FunctionSource {
id: "public.function_source_query_params".to_owned(),
schema: "public".to_owned(),
function: "function_source_query_params".to_owned(),
}),
);
minzoom: Some(0),
maxzoom: Some(30),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
};
Some(function_sources)
mock_function_sources(vec![function_source, function_source_query_params])
}
pub fn make_pool() -> Pool {

View File

@ -11,9 +11,28 @@ use crate::utils::{prettify_error, query_to_json};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FunctionSource {
// Function source id
pub id: String,
// Schema name
pub schema: String,
// Function name
pub function: String,
// An integer specifying the minimum zoom level
#[serde(skip_serializing_if = "Option::is_none")]
pub minzoom: Option<u8>,
// An integer specifying the maximum zoom level. MUST be >= minzoom
#[serde(skip_serializing_if = "Option::is_none")]
pub maxzoom: Option<u8>,
// The maximum extent of available map tiles. Bounds MUST define an area
// covered by all zoom levels. The bounds are represented in WGS:84
// latitude and longitude values, in the order left, bottom, right, top.
// Values may be integers or floating point numbers.
#[serde(skip_serializing_if = "Option::is_none")]
pub bounds: Option<Vec<f32>>,
}
pub type FunctionSources = HashMap<String, Box<FunctionSource>>;
@ -30,6 +49,18 @@ impl Source for FunctionSource {
tilejson_builder.name(&self.id);
tilejson_builder.tiles(vec![]);
if let Some(minzoom) = &self.minzoom {
tilejson_builder.minzoom(*minzoom);
};
if let Some(maxzoom) = &self.maxzoom {
tilejson_builder.maxzoom(*maxzoom);
};
if let Some(bounds) = &self.bounds {
tilejson_builder.bounds(bounds.to_vec());
};
Ok(tilejson_builder.finalize())
}
@ -100,6 +131,9 @@ pub fn get_function_sources(conn: &mut Connection) -> Result<FunctionSources, io
id: id.clone(),
schema,
function,
minzoom: None,
maxzoom: None,
bounds: None,
};
sources.insert(id, Box::new(source));

View File

@ -170,6 +170,17 @@ async fn get_composite_source_tile(
.split(',')
.filter_map(|source_id| table_sources.get(source_id))
.map(|source| source.deref().clone())
.filter(|source| {
let gte_minzoom = source
.minzoom
.map_or(true, |minzoom| path.z >= minzoom.into());
let lte_maxzoom = source
.maxzoom
.map_or(true, |maxzoom| path.z <= maxzoom.into());
gte_minzoom && lte_maxzoom
})
.collect();
if sources.is_empty() {
@ -295,7 +306,20 @@ async fn get_function_source_tile(
.clone()
.ok_or_else(|| error::ErrorNotFound("There is no function sources"))?;
let source = function_sources.get(&path.source_id).ok_or_else(|| {
let source = function_sources
.get(&path.source_id)
.filter(|source| {
let gte_minzoom = source
.minzoom
.map_or(true, |minzoom| path.z >= minzoom.into());
let lte_maxzoom = source
.maxzoom
.map_or(true, |maxzoom| path.z <= maxzoom.into());
gte_minzoom && lte_maxzoom
})
.ok_or_else(|| {
error::ErrorNotFound(format!("Function source '{}' not found", path.source_id))
})?;

View File

@ -10,17 +10,57 @@ use crate::utils;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TableSource {
// Table source id
pub id: String,
// Table schema
pub schema: String,
// Table name
pub table: String,
pub id_column: Option<String>,
pub geometry_column: String,
// Geometry SRID
pub srid: u32,
// Geometry column name
pub geometry_column: String,
// Feature id column name
#[serde(skip_serializing_if = "Option::is_none")]
pub id_column: Option<String>,
// An integer specifying the minimum zoom level
#[serde(skip_serializing_if = "Option::is_none")]
pub minzoom: Option<u8>,
// An integer specifying the maximum zoom level. MUST be >= minzoom
#[serde(skip_serializing_if = "Option::is_none")]
pub maxzoom: Option<u8>,
// The maximum extent of available map tiles. Bounds MUST define an area
// covered by all zoom levels. The bounds are represented in WGS:84
// latitude and longitude values, in the order left, bottom, right, top.
// Values may be integers or floating point numbers.
#[serde(skip_serializing_if = "Option::is_none")]
pub bounds: Option<Vec<f32>>,
// Tile extent in tile coordinate space
#[serde(skip_serializing_if = "Option::is_none")]
pub extent: Option<u32>,
// Buffer distance in tile coordinate space to optionally clip geometries
#[serde(skip_serializing_if = "Option::is_none")]
pub buffer: Option<u32>,
// Boolean to control if geometries should be clipped or encoded as is
#[serde(skip_serializing_if = "Option::is_none")]
pub clip_geom: Option<bool>,
// Geometry type
#[serde(skip_serializing_if = "Option::is_none")]
pub geometry_type: Option<String>,
// List of columns, that should be encoded as tile properties
pub properties: HashMap<String, String>,
}
@ -93,6 +133,14 @@ impl Source for TableSource {
tilejson_builder.scheme("xyz");
tilejson_builder.name(&self.id);
if let Some(minzoom) = &self.minzoom {
tilejson_builder.minzoom(*minzoom);
};
if let Some(maxzoom) = &self.maxzoom {
tilejson_builder.maxzoom(*maxzoom);
};
if let Some(bounds) = &self.bounds {
tilejson_builder.bounds(bounds.to_vec());
};
@ -168,6 +216,8 @@ pub fn get_table_sources(conn: &mut Connection) -> Result<TableSources, io::Erro
id_column: None,
geometry_column,
bounds,
minzoom: None,
maxzoom: None,
srid: srid as u32,
extent: Some(DEFAULT_EXTENT),
buffer: Some(DEFAULT_BUFFER),

View File

@ -20,28 +20,52 @@ watch: false
# Trust invalid certificates. This introduces significant vulnerabilities, and should only be used as a last resort.
danger_accept_invalid_certs: false
# associative arrays of table sources
# Associative arrays of table sources
table_sources:
public.table_source:
# table source id
# Table source id (required)
id: public.table_source
# table schema
# Table schema (required)
schema: public
# table name
# Table name (required)
table: table_source
# geometry column name
geometry_column: geom
# geometry srid
# Geometry SRID (required)
srid: 4326
# tile extent in tile coordinate space
# Geometry column name (required)
geometry_column: geom
# Feature id column name
id_column: ~
# An integer specifying the minimum zoom level
minzoom: 0
# An integer specifying the maximum zoom level. MUST be >= minzoom
maxzoom: 30
# The maximum extent of available map tiles. Bounds MUST define an area
# covered by all zoom levels. The bounds are represented in WGS:84
# latitude and longitude values, in the order left, bottom, right, top.
# Values may be integers or floating point numbers.
bounds: [-180.0, -90.0, 180.0, 90.0]
# Tile extent in tile coordinate space
extent: 4096
# buffer distance in tile coordinate space to optionally clip geometries
# Buffer distance in tile coordinate space to optionally clip geometries
buffer: 64
# boolean to control if geometries should be clipped or encoded as is
# Boolean to control if geometries should be clipped or encoded as is
clip_geom: true
# geometry type
# Geometry type
geometry_type: GEOMETRY
# list of columns, that should be encoded as tile properties
# List of columns, that should be encoded as tile properties (required)
properties:
gid: int4
@ -49,6 +73,9 @@ table_sources:
id: public.points1
schema: public
table: points1
minzoom: 0
maxzoom: 30
bounds: [-180.0, -90.0, 180.0, 90.0]
id_column: ~
geometry_column: geom
srid: 4326
@ -63,6 +90,9 @@ table_sources:
id: public.points2
schema: public
table: points2
minzoom: 0
maxzoom: 30
bounds: [-180.0, -90.0, 180.0, 90.0]
id_column: ~
geometry_column: geom
srid: 4326
@ -77,6 +107,9 @@ table_sources:
id: public.points3857
schema: public
table: points3857
minzoom: 0
maxzoom: 30
bounds: [-180.0, -90.0, 180.0, 90.0]
id_column: ~
geometry_column: geom
srid: 3857
@ -87,17 +120,34 @@ table_sources:
properties:
gid: int4
# associative arrays of function sources
# Associative arrays of function sources
function_sources:
public.function_source:
# function source id
# Function source id (required)
id: public.function_source
# schema name
# Schema name (required)
schema: public
# function name
# Function name (required)
function: function_source
# An integer specifying the minimum zoom level
minzoom: 0
# An integer specifying the maximum zoom level. MUST be >= minzoom
maxzoom: 30
# The maximum extent of available map tiles. Bounds MUST define an area
# covered by all zoom levels. The bounds are represented in WGS:84
# latitude and longitude values, in the order left, bottom, right, top.
# Values may be integers or floating point numbers.
bounds: [-180.0, -90.0, 180.0, 90.0]
public.function_source_query_params:
id: public.function_source_query_params
schema: public
function: function_source_query_params
minzoom: 0
maxzoom: 30
bounds: [-180.0, -90.0, 180.0, 90.0]

View File

@ -31,8 +31,8 @@
position: absolute;
z-index: 1;
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
border-radius: 3px;
width: 120px;
font-family: 'Open Sans', sans-serif;
@ -112,32 +112,42 @@
case 'MULTISURFACE':
return 'fill';
default:
throw new Error(`Unknown geometry_type ${source.geometry_type}`);
return 'circle';
}
}
map.on('load', function () {
fetch('http://0.0.0.0:3000/index.json')
.then((r) => r.json())
.then((sources) => {
map.on('load', async function () {
const table_sources = await fetch(
'http://0.0.0.0:3000/index.json'
).then((r) => r.json());
const function_sources = await fetch(
'http://0.0.0.0:3000/rpc/index.json'
).then((r) => r.json());
const sources = Object.values(table_sources).concat(
Object.values(function_sources)
);
// Set up the corresponding toggle button for each layer.
for (const sourceId of Object.keys(sources).sort()) {
for (const source of sources) {
// Skip layers that already have a button set up.
if (document.getElementById(sourceId)) {
if (document.getElementById(source.id)) {
continue;
}
const source = sources[sourceId];
const layerType = geometryTypeToLayerType(source.geometry_type);
map.addLayer({
id: sourceId,
id: source.id,
type: layerType,
source: {
type: 'vector',
url: `http://0.0.0.0:3000/${sourceId}.json`
url: source.hasOwnProperty('table')
? `http://0.0.0.0:3000/${source.id}.json`
: `http://0.0.0.0:3000/rpc/${source.id}.json`
},
'source-layer': sourceId,
'source-layer': source.id,
layout: {
visibility: 'none'
},
@ -148,10 +158,10 @@
// Create a link.
const link = document.createElement('a');
link.id = sourceId;
link.id = source.id;
link.href = '#';
link.textContent = sourceId;
link.title = sourceId;
link.textContent = source.id;
link.title = source.id;
// Show or hide layer when the toggle is clicked.
link.onclick = function (e) {
@ -178,7 +188,6 @@
layers.appendChild(link);
}
});
});
</script>
</body>
</html>

View File

@ -1,11 +1,14 @@
use std::collections::HashMap;
extern crate log;
use actix_web::{http, test, App};
use tilejson::TileJSON;
use martin::dev::{mock_function_sources, mock_state, mock_table_sources};
use martin::function_source::FunctionSources;
use martin::dev;
use martin::function_source::{FunctionSource, FunctionSources};
use martin::server::router;
use martin::table_source::TableSources;
use martin::table_source::{TableSource, TableSources};
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
@ -15,7 +18,7 @@ fn init() {
async fn test_get_table_sources_ok() {
init();
let state = mock_state(mock_table_sources(), None, false);
let state = dev::mock_state(Some(dev::mock_default_table_sources()), None, false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get().uri("/index.json").to_request();
@ -31,7 +34,7 @@ async fn test_get_table_sources_ok() {
async fn test_get_table_sources_watch_mode_ok() {
init();
let state = mock_state(mock_table_sources(), None, true);
let state = dev::mock_state(Some(dev::mock_default_table_sources()), None, true);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get().uri("/index.json").to_request();
@ -47,7 +50,28 @@ async fn test_get_table_sources_watch_mode_ok() {
async fn test_get_table_source_ok() {
init();
let state = mock_state(mock_table_sources(), None, false);
let table_source = TableSource {
id: "public.table_source".to_owned(),
schema: "public".to_owned(),
table: "table_source".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
minzoom: Some(0),
maxzoom: Some(30),
srid: 4326,
extent: Some(4096),
buffer: Some(64),
clip_geom: Some(true),
geometry_type: None,
properties: HashMap::new(),
};
let state = dev::mock_state(
Some(dev::mock_table_sources(vec![table_source])),
None,
false,
);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get()
@ -61,15 +85,25 @@ async fn test_get_table_source_ok() {
.uri("/public.table_source.json")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
let result: TileJSON = test::read_response_json(&mut app, req).await;
println!("{:?}", result);
assert_eq!(result.name, Some(String::from("public.table_source")));
assert_eq!(
result.tiles,
vec!["http://localhost:8080/public.table_source/{z}/{x}/{y}.pbf"]
);
assert_eq!(result.minzoom, Some(0));
assert_eq!(result.maxzoom, Some(30));
assert_eq!(result.bounds, Some(vec![-180.0, -90.0, 180.0, 90.0]));
}
#[actix_rt::test]
async fn test_get_table_source_tile_ok() {
init();
let state = mock_state(mock_table_sources(), None, false);
let state = dev::mock_state(Some(dev::mock_default_table_sources()), None, false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get()
@ -87,11 +121,193 @@ async fn test_get_table_source_tile_ok() {
assert!(response.status().is_success());
}
#[actix_rt::test]
async fn test_get_table_source_tile_minmax_zoom_ok() {
init();
let points1 = TableSource {
id: "public.points1".to_owned(),
schema: "public".to_owned(),
table: "points1".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
minzoom: Some(6),
maxzoom: Some(12),
srid: 4326,
extent: Some(4096),
buffer: Some(64),
clip_geom: Some(true),
geometry_type: None,
properties: HashMap::new(),
};
let points2 = TableSource {
id: "public.points2".to_owned(),
schema: "public".to_owned(),
table: "points2".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
minzoom: None,
maxzoom: None,
srid: 4326,
extent: Some(4096),
buffer: Some(64),
clip_geom: Some(true),
geometry_type: None,
properties: HashMap::new(),
};
let points3857 = 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]),
minzoom: Some(6),
maxzoom: None,
srid: 4326,
extent: Some(4096),
buffer: Some(64),
clip_geom: Some(true),
geometry_type: None,
properties: HashMap::new(),
};
let table_source = TableSource {
id: "public.table_source".to_owned(),
schema: "public".to_owned(),
table: "table_source".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
minzoom: None,
maxzoom: Some(6),
srid: 4326,
extent: Some(4096),
buffer: Some(64),
clip_geom: Some(true),
geometry_type: None,
properties: HashMap::new(),
};
let state = dev::mock_state(
Some(dev::mock_table_sources(vec![
points1,
points2,
points3857,
table_source,
])),
None,
false,
);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
// zoom = 0 (nothing)
let req = test::TestRequest::get()
.uri("/public.points1/0/0/0.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
// zoom = 6 (public.points1)
let req = test::TestRequest::get()
.uri("/public.points1/6/38/20.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 12 (public.points1)
let req = test::TestRequest::get()
.uri("/public.points1/12/2476/1280.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 13 (nothing)
let req = test::TestRequest::get()
.uri("/public.points1/13/4952/2560.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
// zoom = 0 (public.points2)
let req = test::TestRequest::get()
.uri("/public.points2/0/0/0.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 6 (public.points2)
let req = test::TestRequest::get()
.uri("/public.points2/6/38/20.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 12 (public.points2)
let req = test::TestRequest::get()
.uri("/public.points2/12/2476/1280.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 13 (public.points2)
let req = test::TestRequest::get()
.uri("/public.points2/13/4952/2560.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 0 (nothing)
let req = test::TestRequest::get()
.uri("/public.points3857/0/0/0.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
// zoom = 12 (public.points3857)
let req = test::TestRequest::get()
.uri("/public.points3857/12/2476/1280.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 0 (public.table_source)
let req = test::TestRequest::get()
.uri("/public.table_source/0/0/0.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 12 (nothing)
let req = test::TestRequest::get()
.uri("/public.table_source/12/2476/1280.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_get_composite_source_ok() {
init();
let state = mock_state(mock_table_sources(), None, false);
let state = dev::mock_state(Some(dev::mock_default_table_sources()), None, false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get()
@ -113,7 +329,7 @@ async fn test_get_composite_source_ok() {
async fn test_get_composite_source_tile_ok() {
init();
let state = mock_state(mock_table_sources(), None, false);
let state = dev::mock_state(Some(dev::mock_default_table_sources()), None, false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get()
@ -131,11 +347,116 @@ async fn test_get_composite_source_tile_ok() {
assert!(response.status().is_success());
}
#[actix_rt::test]
async fn test_get_composite_source_tile_minmax_zoom_ok() {
init();
let public_points1 = TableSource {
id: "public.points1".to_owned(),
schema: "public".to_owned(),
table: "points1".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
minzoom: Some(6),
maxzoom: Some(13),
srid: 4326,
extent: Some(4096),
buffer: Some(64),
clip_geom: Some(true),
geometry_type: None,
properties: HashMap::new(),
};
let public_points2 = TableSource {
id: "public.points2".to_owned(),
schema: "public".to_owned(),
table: "points2".to_owned(),
id_column: None,
geometry_column: "geom".to_owned(),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
minzoom: Some(13),
maxzoom: Some(20),
srid: 4326,
extent: Some(4096),
buffer: Some(64),
clip_geom: Some(true),
geometry_type: None,
properties: HashMap::new(),
};
let state = dev::mock_state(
Some(dev::mock_table_sources(vec![
public_points1,
public_points2,
])),
None,
false,
);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
// zoom = 0 (nothing)
let req = test::TestRequest::get()
.uri("/public.points1,public.points2/0/0/0.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
// zoom = 6 (public.points1)
let req = test::TestRequest::get()
.uri("/public.points1,public.points2/6/38/20.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 12 (public.points1)
let req = test::TestRequest::get()
.uri("/public.points1,public.points2/12/2476/1280.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 13 (public.points1, public.points2)
let req = test::TestRequest::get()
.uri("/public.points1,public.points2/13/4952/2560.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 14 (public.points2)
let req = test::TestRequest::get()
.uri("/public.points1,public.points2/14/9904/5121.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 20 (public.points2)
let req = test::TestRequest::get()
.uri("/public.points1,public.points2/20/633856/327787.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 21 (nothing)
let req = test::TestRequest::get()
.uri("/public.points1,public.points2/21/1267712/655574.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_get_function_sources_ok() {
init();
let state = mock_state(None, mock_function_sources(), false);
let state = dev::mock_state(None, Some(dev::mock_default_function_sources()), false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get().uri("/rpc/index.json").to_request();
@ -151,7 +472,7 @@ async fn test_get_function_sources_ok() {
async fn test_get_function_sources_watch_mode_ok() {
init();
let state = mock_state(None, mock_function_sources(), true);
let state = dev::mock_state(None, Some(dev::mock_default_function_sources()), true);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get().uri("/rpc/index.json").to_request();
@ -167,7 +488,7 @@ async fn test_get_function_sources_watch_mode_ok() {
async fn test_get_function_source_ok() {
init();
let state = mock_state(None, mock_function_sources(), false);
let state = dev::mock_state(None, Some(dev::mock_default_function_sources()), false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get()
@ -189,7 +510,7 @@ async fn test_get_function_source_ok() {
async fn test_get_function_source_tile_ok() {
init();
let state = mock_state(None, mock_function_sources(), false);
let state = dev::mock_state(None, Some(dev::mock_default_function_sources()), false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get()
@ -200,11 +521,109 @@ async fn test_get_function_source_tile_ok() {
assert!(response.status().is_success());
}
#[actix_rt::test]
async fn test_get_function_source_tile_minmax_zoom_ok() {
init();
let function_source1 = FunctionSource {
id: "public.function_source1".to_owned(),
schema: "public".to_owned(),
function: "function_source".to_owned(),
minzoom: None,
maxzoom: None,
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
};
let function_source2 = FunctionSource {
id: "public.function_source2".to_owned(),
schema: "public".to_owned(),
function: "function_source".to_owned(),
minzoom: Some(6),
maxzoom: Some(12),
bounds: Some(vec![-180.0, -90.0, 180.0, 90.0]),
};
let state = dev::mock_state(
None,
Some(dev::mock_function_sources(vec![
function_source1,
function_source2,
])),
false,
);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
// zoom = 0 (public.function_source1)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source1/0/0/0.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 6 (public.function_source1)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source1/6/38/20.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 12 (public.function_source1)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source1/12/2476/1280.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 13 (public.function_source1)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source1/13/4952/2560.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 0 (nothing)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source2/0/0/0.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
// zoom = 6 (public.function_source2)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source2/6/38/20.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 12 (public.function_source2)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source2/12/2476/1280.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert!(response.status().is_success());
// zoom = 13 (nothing)
let req = test::TestRequest::get()
.uri("/rpc/public.function_source2/13/4952/2560.pbf")
.to_request();
let response = test::call_service(&mut app, req).await;
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_get_function_source_query_params_ok() {
init();
let state = mock_state(None, mock_function_sources(), false);
let state = dev::mock_state(None, Some(dev::mock_default_function_sources()), false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get()
@ -227,7 +646,7 @@ async fn test_get_function_source_query_params_ok() {
async fn test_get_health_returns_ok() {
init();
let state = mock_state(None, mock_function_sources(), false);
let state = dev::mock_state(None, Some(dev::mock_default_function_sources()), false);
let mut app = test::init_service(App::new().data(state).configure(router)).await;
let req = test::TestRequest::get().uri("/healthz").to_request();