feat: add MVT handler

This commit is contained in:
Stepan Kuzmin 2017-11-16 21:14:38 +03:00
parent 8a44543478
commit 204b132a26
4 changed files with 41 additions and 93 deletions

44
Cargo.lock generated
View File

@ -2,14 +2,13 @@
name = "falcon"
version = "0.1.0"
dependencies = [
"fallible-iterator 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"iron-cors 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"persistent 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"postgres 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
"r2d2 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
"r2d2_postgres 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -98,11 +97,6 @@ dependencies = [
"generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "error"
version = "0.1.9"
@ -222,9 +216,13 @@ dependencies = [
]
[[package]]
name = "itoa"
version = "0.3.4"
name = "iron-cors"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
@ -291,11 +289,6 @@ name = "nodrop"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num-traits"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num_cpus"
version = "1.7.0"
@ -381,7 +374,6 @@ dependencies = [
"hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
"postgres-protocol 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -455,22 +447,6 @@ dependencies = [
"antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_json"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha2"
version = "0.6.0"
@ -630,7 +606,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
"checksum crypto-mac 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "779015233ac67d65098614aec748ac1c756ab6677fa2e14cf8b37c08dfed1198"
"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum fallible-iterator 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5d48ab1bc11a086628e8cc0cc2c2dc200b884ac05c4b48fb71d6036b6999ff1d"
@ -644,7 +619,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6e8b9c2247fcf6c6a1151f1156932be5606c9fd6f55a2d7f9fc1cb29386b2f7"
"checksum iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2440ae846e7a8c7f9b401db8f6e31b4ea5e7d3688b91761337da7e054520c75b"
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
"checksum iron-cors 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8a271fc9d686887269d23bb2b7dea4dd628fb640578d4d5d951368882208b88"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
"checksum lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9e5e58fa1a4c3b915a561a78a22ee0cac6ab97dca2504428bc1cb074375f8d5"
@ -656,7 +631,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
"checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0"
"checksum num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "514f0d73e64be53ff320680ca671b64fe3fb91da01e1ae2ddc99eb51d453b20d"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum persistent 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c9c94f2ef72dc272c6bcc8157ccf2bc7da14f4c58c69059ac2fc48492d6916"
@ -675,8 +649,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
"checksum scheduled-thread-pool 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d9fbe48ead32343b76f544c85953bf260ed39219a8bbbb62cd85f6a00f9644f"
"checksum serde 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "0c9cab69e16835717c9b8bd13c29f92b6aa34fe32ce2866b1ab481cf2da8442a"
"checksum serde_json 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e4586746d1974a030c48919731ecffd0ed28d0c40749d0d18d43b3a7d6c9b20e"
"checksum sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a"
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
"checksum stringprep 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"

View File

@ -10,10 +10,9 @@ path = "src/main.rs"
[dependencies]
url = "1.6.0"
regex = "0.2.2"
fallible-iterator = "0.1.3"
iron = "0.5.1"
iron-cors = "0.5.1"
persistent = "0.3.0"
serde_json = "1.0.6"
r2d2 = "0.7.4"
r2d2_postgres = "0.13.0"
postgres = { version = "0.15.1", features = ["with-serde_json", "with-time", "with-uuid"] }
postgres = { version = "0.15.1", features = ["with-time", "with-uuid"] }

View File

@ -2,7 +2,7 @@
[![Build Status](https://travis-ci.org/stepankuzmin/falcon.svg?branch=master)](https://travis-ci.org/stepankuzmin/falcon)
PostgreSQL RESTful API
PostGIS [Mapbox Vector Tiles](https://github.com/mapbox/vector-tile-spec) server.
**Warning: this is experimental**

View File

@ -1,9 +1,8 @@
extern crate url;
extern crate iron;
extern crate regex;
extern crate iron_cors;
extern crate persistent;
extern crate serde_json;
extern crate fallible_iterator;
extern crate r2d2;
extern crate r2d2_postgres;
@ -15,9 +14,8 @@ use iron::prelude::*;
use iron::mime;
use iron::status;
use iron::typemap::Key;
use iron_cors::CorsMiddleware;
use persistent::Read;
use serde_json::Value;
use fallible_iterator::FallibleIterator;
use r2d2::{Pool, PooledConnection};
use r2d2_postgres::{TlsMode, PostgresConnectionManager};
@ -36,7 +34,8 @@ fn setup_connection_pool(cn_str: &str, pool_size: u32) -> PostgresPool {
fn handler(req: &mut Request) -> IronResult<Response> {
let url: Url = req.url.clone().into();
let re = Regex::new(r"^/(?P<schema>\w*)/(?P<table>\w*).(?P<format>\w*)$").unwrap();
let tile_re = r"^/(?P<schema>\w*)/(?P<table>\w*)/(?P<z>\d*)/(?P<x>\d*)/(?P<y>\d*).(?P<format>\w*)$";
let re = Regex::new(tile_re).unwrap();
match re.captures(&url.path()) {
Some(caps) => {
println!("{} {} {}", req.method, req.version, req.url);
@ -44,60 +43,35 @@ fn handler(req: &mut Request) -> IronResult<Response> {
let pool = req.get::<Read<DB>>().unwrap();
let conn = pool.get().unwrap();
match &caps["format"] {
"json" => get_json(conn, &caps["schema"], &caps["table"]),
"geojson" => get_geojson(conn, &caps["schema"], &caps["table"]),
&_ => Ok(Response::with((status::NotFound)))
let query = format!(
"SELECT ST_AsMVT(q, '{1}', 4096, 'geom') FROM ( \
SELECT ST_AsMVTGeom( \
geom, \
TileBBox({2}, {3}, {4}, 4326), \
4096, \
256, \
true \
) AS geom FROM {0}.{1} \
) AS q;",
&caps["schema"], &caps["table"], &caps["z"], &caps["x"], &caps["y"]
);
match conn.query(&query, &[]) {
Ok(rows) => {
let content_type = "application/x-protobuf".parse::<mime::Mime>().unwrap();
let tile: Vec<u8> = rows.get(0).get("st_asmvt");
match tile.len() {
0 => Ok(Response::with((content_type, status::NoContent))),
_ => Ok(Response::with((content_type, status::Ok, tile)))
}
},
Err(e) => Ok(Response::with((status::InternalServerError, e.to_string())))
}
},
None => Ok(Response::with((status::NotFound)))
}
}
fn get_json(conn: PostgresPooledConnection, schema: &str, table: &str) -> IronResult<Response> {
let query = format!("select json_agg({1}) from {0}.{1}", schema, table);
let trans = conn.transaction().unwrap();
let stmt = trans.prepare(&query).unwrap();
let mut result = stmt.lazy_query(&trans, &[], 1000).unwrap();
let content_type = "application/json".parse::<mime::Mime>().unwrap();
match result.next() {
Ok(Some(rows)) => {
let result: Value = rows.get("json_agg");
let content = serde_json::to_string(&result).unwrap();
Ok(Response::with((content_type, status::Ok, content)))
},
Ok(None) => {
let content = "[]";
Ok(Response::with((content_type, status::Ok, content)))
},
Err(e) => Ok(Response::with((status::InternalServerError, e.to_string())))
}
}
fn get_geojson(conn: PostgresPooledConnection, schema: &str, table: &str) -> IronResult<Response> {
let query = format!("select json_agg({1}) from {0}.{1}", schema, table);
let trans = conn.transaction().unwrap();
let stmt = trans.prepare(&query).unwrap();
let mut result = stmt.lazy_query(&trans, &[], 1000).unwrap();
let content_type = "application/json".parse::<mime::Mime>().unwrap();
match result.next() {
Ok(Some(rows)) => {
let result: Value = rows.get("json_agg");
let content = serde_json::to_string(&result).unwrap();
Ok(Response::with((content_type, status::Ok, content)))
},
Ok(None) => {
let content = "[]";
Ok(Response::with((content_type, status::Ok, content)))
},
Err(e) => Ok(Response::with((status::InternalServerError, e.to_string())))
}
}
fn main() {
let conn_string: String = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
@ -108,6 +82,9 @@ fn main() {
let mut middleware = Chain::new(handler);
middleware.link(Read::<DB>::both(pool));
let cors_middleware = CorsMiddleware::with_allow_any(false);
middleware.link_around(cors_middleware);
let port = 3000;
let bind_addr = format!("0.0.0.0:{}", port);
println!("server has been started on {}.", bind_addr);