API: handle multi pts
Signed-off-by: Frank Villaro-Dixon <frank@villaro-dixon.eu>
This commit is contained in:
parent
ce3610bf45
commit
61259dc4fb
4 changed files with 150 additions and 20 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
@ -36,6 +36,7 @@ dependencies = [
|
||||||
"moka",
|
"moka",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_qs",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
@ -361,6 +362,21 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
@ -368,6 +384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -376,6 +393,23 @@ version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
@ -405,9 +439,13 @@ version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
|
@ -1078,6 +1116,19 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_qs"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"futures",
|
||||||
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|
|
@ -13,6 +13,7 @@ gdal = { version = "0.16.0", features = ["bindgen"] }
|
||||||
moka = { version = "0.12.5", features = ["future"] }
|
moka = { version = "0.12.5", features = ["future"] }
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
serde_json = "1.0.115"
|
serde_json = "1.0.115"
|
||||||
|
serde_qs = { version = "0.13.0", features = ["axum"] }
|
||||||
tokio = { version = "1.37.0", features = ["full"] }
|
tokio = { version = "1.37.0", features = ["full"] }
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
tower-http = { version = "0.5.2", features = ["fs", "trace"]}
|
tower-http = { version = "0.5.2", features = ["fs", "trace"]}
|
||||||
|
|
|
@ -61,18 +61,18 @@ impl Clone for DatasetRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn elevation_from_coordinates(dr: DatasetRepository, lat: f64, lon: f64) -> Option<f64> {
|
pub async fn elevation_from_coordinates(dr: &DatasetRepository, lat: f64, lon: f64) -> Option<f64> {
|
||||||
let span = debug_span!("req", lat=%lat, lon=%lon);
|
let span = debug_span!("req", lat=%lat, lon=%lon);
|
||||||
let _guard = span.enter();
|
let _guard = span.enter();
|
||||||
|
|
||||||
let filename = get_filename_from_latlon(lat, lon);
|
let filename = get_filename_from_latlon(lat, lon);
|
||||||
debug!(filename, "filename");
|
debug!(filename, "filename");
|
||||||
|
|
||||||
|
|
||||||
let ds = &match dr.get(filename).await {
|
let ds = &match dr.get(filename).await {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return None,
|
None => return None,
|
||||||
}.ds;
|
}
|
||||||
|
.ds;
|
||||||
|
|
||||||
let (px, py) = geo_to_pixel(ds, lat, lon).unwrap();
|
let (px, py) = geo_to_pixel(ds, lat, lon).unwrap();
|
||||||
|
|
||||||
|
|
112
src/main.rs
112
src/main.rs
|
@ -1,28 +1,68 @@
|
||||||
mod dem;
|
mod dem;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Extension, Path, Query, State}, http::{response, StatusCode}, response::{IntoResponse, Response}, routing::get, Json, Router
|
||||||
http::StatusCode,
|
|
||||||
response::IntoResponse,
|
|
||||||
routing::get,
|
|
||||||
Router,
|
|
||||||
};
|
};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use std::env;
|
use std::{borrow::Borrow, fmt};
|
||||||
|
use std::{env, str::FromStr};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
services::ServeDir,
|
services::ServeDir,
|
||||||
trace::{self, DefaultMakeSpan},
|
trace::{self, DefaultMakeSpan},
|
||||||
};
|
};
|
||||||
|
use tracing::{info, Level, Span};
|
||||||
|
use serde_qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection};
|
||||||
|
|
||||||
|
|
||||||
use dem::DatasetRepository;
|
use dem::DatasetRepository;
|
||||||
|
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
use tracing::{info, Level, Span};
|
|
||||||
|
|
||||||
const DEFAULT_DATA_DIR: &str = "/data";
|
const DEFAULT_DATA_DIR: &str = "/data";
|
||||||
const DEFAULT_PORT: &str = "3000";
|
const DEFAULT_PORT: &str = "3000";
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Opts {
|
||||||
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||||
|
json: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct JsParams{
|
||||||
|
#[serde(default, deserialize_with = "deserialize_array")]
|
||||||
|
pts: Vec<(f64, f64)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
struct JsResult {
|
||||||
|
elevations: Vec<Option<f64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_array<'de, D>(deserializer: D) -> Result<Vec<(f64, f64)>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
let result: Result<Vec<(f64, f64)>, serde_json::Error> = serde_json::from_str(&s);
|
||||||
|
match result {
|
||||||
|
Ok(x) => Ok(x),
|
||||||
|
Err(e) => Err(serde::de::Error::custom("Invalid array: ".to_string() + &e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_string_as_none<'de, D>(de: D) -> Result<bool, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let opt = Option::<String>::deserialize(de)?;
|
||||||
|
match opt.as_deref() {
|
||||||
|
None => Ok(false),
|
||||||
|
Some("") => Ok(true),
|
||||||
|
Some(x) => Ok(x != "false"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// initialize tracing
|
// initialize tracing
|
||||||
|
@ -33,15 +73,19 @@ async fn main() {
|
||||||
|
|
||||||
let serve_dir = ServeDir::new("assets");
|
let serve_dir = ServeDir::new("assets");
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::<DatasetRepository>::new()
|
||||||
.route("/elevation/:lat/:lon", get(get_elevation))
|
.route("/elevation/:lat/:lon", get(get_elevation))
|
||||||
|
.route("/ele", get(get_elevation_js))
|
||||||
.nest_service("/", serve_dir)
|
.nest_service("/", serve_dir)
|
||||||
.with_state(cache)
|
.with_state(cache)
|
||||||
.layer(
|
.layer(
|
||||||
TraceLayer::new_for_http()
|
TraceLayer::new_for_http()
|
||||||
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
||||||
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
|
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
|
||||||
);
|
)
|
||||||
|
.layer(Extension(QsQueryConfig::new(5, false)
|
||||||
|
.error_handler(|err| {
|
||||||
|
QsQueryRejection::new(format!("Get fucked: {}", err), StatusCode::UNPROCESSABLE_ENTITY)})));
|
||||||
|
|
||||||
let host = format!("[::]:{}", config.port);
|
let host = format!("[::]:{}", config.port);
|
||||||
info!("Will start server on {host}");
|
info!("Will start server on {host}");
|
||||||
|
@ -53,13 +97,48 @@ async fn main() {
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn get_elevation(
|
async fn get_elevation(
|
||||||
State(dsr): State<DatasetRepository>,
|
State(dsr): State<DatasetRepository>,
|
||||||
|
query_opts: Query<Opts>,
|
||||||
Path((lat, lon)): Path<(f64, f64)>,
|
Path((lat, lon)): Path<(f64, f64)>,
|
||||||
) -> impl IntoResponse {
|
) -> Response {
|
||||||
let ele = dem::elevation_from_coordinates(dsr, lat, lon);
|
println!("lat: {}, lon: {}", lat, lon);
|
||||||
match ele.await {
|
println!("query_opts: {:?}", query_opts);
|
||||||
Some(ele) => (StatusCode::OK, format!("{ele}")),
|
|
||||||
None => (StatusCode::NOT_IMPLEMENTED, "".to_string()),
|
let ele;
|
||||||
|
match dem::elevation_from_coordinates(&dsr, lat, lon).await {
|
||||||
|
//None => return (StatusCode::NOT_IMPLEMENTED, "".to_string()),
|
||||||
|
None => {
|
||||||
|
return "".to_string().into_response();
|
||||||
}
|
}
|
||||||
|
Some(el) => ele = el,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Ele {
|
||||||
|
elevation: f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
if query_opts.json {
|
||||||
|
let r = Ele { elevation: ele };
|
||||||
|
return Json(r).into_response();
|
||||||
|
} else {
|
||||||
|
return format!("{}", ele).into_response();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn get_elevation_js(
|
||||||
|
State(dsr): State<DatasetRepository>,
|
||||||
|
Query(params): Query<JsParams>,
|
||||||
|
) -> Response {
|
||||||
|
let mut response = JsResult {
|
||||||
|
elevations: vec![],
|
||||||
|
};
|
||||||
|
for pt in params.pts {
|
||||||
|
let ele = dem::elevation_from_coordinates(&dsr, pt.0, pt.1).await;
|
||||||
|
response.elevations.push(ele);
|
||||||
|
}
|
||||||
|
Json(response).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_config() -> Result<Config, env::VarError> {
|
fn load_config() -> Result<Config, env::VarError> {
|
||||||
|
@ -68,7 +147,6 @@ fn load_config() -> Result<Config, env::VarError> {
|
||||||
port: env::var("HTTP_PORT").unwrap_or_else(|_| DEFAULT_PORT.to_string()),
|
port: env::var("HTTP_PORT").unwrap_or_else(|_| DEFAULT_PORT.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
basedir: String,
|
basedir: String,
|
||||||
port: String,
|
port: String,
|
||||||
|
|
Loading…
Reference in a new issue