Add js endpoint

Signed-off-by: Frank Villaro-Dixon <frank@villaro-dixon.eu>
This commit is contained in:
Frank Villaro-Dixon 2024-04-21 14:05:46 +02:00
parent cd4ccee05c
commit 8b85c31411
2 changed files with 65 additions and 25 deletions

View file

@ -61,7 +61,11 @@ 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,
) -> Result<Option<f64>, GdalError> {
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();
@ -70,17 +74,15 @@ pub async fn elevation_from_coordinates(dr: &DatasetRepository, lat: f64, lon: f
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 Ok(None),
} }
.ds; .ds;
let (px, py) = geo_to_pixel(ds, lat, lon).unwrap(); let (px, py) = geo_to_pixel(ds, lat, lon)?;
let raster_band = ds.rasterband(1).unwrap(); let raster_band = ds.rasterband(1)?;
let raster_value = raster_band let raster_value = raster_band.read_as::<f64>((px, py), (1, 1), (1, 1), None)?;
.read_as::<f64>((px, py), (1, 1), (1, 1), None) Ok(Some(raster_value.data[0]))
.unwrap();
Some(raster_value.data[0])
} }
fn get_filename_from_latlon(lat: f64, lon: f64) -> String { fn get_filename_from_latlon(lat: f64, lon: f64) -> String {

View file

@ -1,21 +1,26 @@
mod dem; mod dem;
use axum::{ use axum::{
extract::{Extension, Path, Query, State}, http::{response, StatusCode}, response::{IntoResponse, Response}, routing::get, Json, Router extract::{Extension, Path, Query, State},
http::{response, StatusCode},
response::{IntoResponse, Response},
routing::get,
Json, Router,
}; };
use axum_macros::debug_handler; use axum_macros::debug_handler;
use std::{borrow::Borrow, fmt}; use std::{borrow::Borrow, fmt};
use std::{env, str::FromStr}; use std::{env, str::FromStr};
use tokio::sync::mpsc::error;
use serde::ser::{SerializeSeq, Serializer};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use serde_qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection};
use tower_http::trace::TraceLayer; 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 tracing::{error, info, Level, Span};
use serde_qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection};
use dem::DatasetRepository; use dem::DatasetRepository;
@ -29,16 +34,36 @@ struct Opts {
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct JsParams{ struct JsParams {
#[serde(default, deserialize_with = "deserialize_array")] #[serde(default, deserialize_with = "deserialize_array")]
pts: Vec<(f64, f64)>, pts: Vec<(f64, f64)>,
} }
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
struct JsResult { struct JsResult {
#[serde(serialize_with = "serialize_vec_round")]
elevations: Vec<Option<f64>>, elevations: Vec<Option<f64>>,
} }
fn serialize_vec_round<S>(v: &Vec<Option<f64>>, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut sv = s.serialize_seq(Some(v.len()))?;
for e in v {
match e {
None => sv.serialize_element(&e)?,
Some(x) => {
// Round the f64 to 1 decimal place. This is ugly as shit.
let fmt = format!("{:.1}", x);
let xx = fmt.parse::<f64>().unwrap();
sv.serialize_element(&xx)?;
}
};
}
sv.end()
}
fn deserialize_array<'de, D>(deserializer: D) -> Result<Vec<(f64, f64)>, D::Error> fn deserialize_array<'de, D>(deserializer: D) -> Result<Vec<(f64, f64)>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -47,7 +72,9 @@ where
let result: Result<Vec<(f64, f64)>, serde_json::Error> = serde_json::from_str(&s); let result: Result<Vec<(f64, f64)>, serde_json::Error> = serde_json::from_str(&s);
match result { match result {
Ok(x) => Ok(x), Ok(x) => Ok(x),
Err(e) => Err(serde::de::Error::custom("Invalid array: ".to_string() + &e.to_string())), Err(e) => Err(serde::de::Error::custom(
"Invalid array: ".to_string() + &e.to_string(),
)),
} }
} }
@ -83,9 +110,14 @@ async fn main() {
.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) .layer(Extension(QsQueryConfig::new(5, false).error_handler(
.error_handler(|err| { |err| {
QsQueryRejection::new(format!("Get fucked: {}", err), StatusCode::UNPROCESSABLE_ENTITY)}))); 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}");
@ -106,10 +138,13 @@ async fn get_elevation(
let ele; let ele;
match dem::elevation_from_coordinates(&dsr, lat, lon).await { match dem::elevation_from_coordinates(&dsr, lat, lon).await {
//None => return (StatusCode::NOT_IMPLEMENTED, "".to_string()), //None => return (StatusCode::NOT_IMPLEMENTED, "".to_string()),
None => { Ok(x) => match x {
return "".to_string().into_response(); Some(el) => ele = el,
None => ele = -1.0,
},
Err(e) => {
return e.to_string().into_response();
} }
Some(el) => ele = el,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -125,18 +160,21 @@ async fn get_elevation(
} }
} }
#[debug_handler] #[debug_handler]
async fn get_elevation_js( async fn get_elevation_js(
State(dsr): State<DatasetRepository>, State(dsr): State<DatasetRepository>,
Query(params): Query<JsParams>, Query(params): Query<JsParams>,
) -> Response { ) -> Response {
let mut response = JsResult { let mut response = JsResult { elevations: vec![] };
elevations: vec![],
};
for pt in params.pts { for pt in params.pts {
let ele = dem::elevation_from_coordinates(&dsr, pt.0, pt.1).await; let ele = dem::elevation_from_coordinates(&dsr, pt.0, pt.1).await;
response.elevations.push(ele); match ele {
Ok(x) => response.elevations.push(x),
Err(e) => {
error!("Error: {e}");
response.elevations.push(None);
}
}
} }
Json(response).into_response() Json(response).into_response()
} }