API: handle multi pts

Signed-off-by: Frank Villaro-Dixon <frank@villaro-dixon.eu>
This commit is contained in:
Frank Villaro-Dixon 2024-04-19 13:28:40 +02:00
parent ce3610bf45
commit 61259dc4fb
4 changed files with 150 additions and 20 deletions

51
Cargo.lock generated
View file

@ -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"

View file

@ -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"]}

View file

@ -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();

View file

@ -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,