From 8b85c3141146a19ac88b4f088291d0c57e185788 Mon Sep 17 00:00:00 2001 From: Frank Villaro-Dixon Date: Sun, 21 Apr 2024 14:05:46 +0200 Subject: [PATCH] Add js endpoint Signed-off-by: Frank Villaro-Dixon --- src/dem.rs | 18 ++++++++------ src/main.rs | 72 ++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/dem.rs b/src/dem.rs index a7f83c1..a988831 100644 --- a/src/dem.rs +++ b/src/dem.rs @@ -61,7 +61,11 @@ impl Clone for DatasetRepository { } } -pub async fn elevation_from_coordinates(dr: &DatasetRepository, lat: f64, lon: f64) -> Option { +pub async fn elevation_from_coordinates( + dr: &DatasetRepository, + lat: f64, + lon: f64, +) -> Result, GdalError> { let span = debug_span!("req", lat=%lat, lon=%lon); 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 { Some(x) => x, - None => return None, + None => return Ok(None), } .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_value = raster_band - .read_as::((px, py), (1, 1), (1, 1), None) - .unwrap(); - Some(raster_value.data[0]) + let raster_band = ds.rasterband(1)?; + let raster_value = raster_band.read_as::((px, py), (1, 1), (1, 1), None)?; + Ok(Some(raster_value.data[0])) } fn get_filename_from_latlon(lat: f64, lon: f64) -> String { diff --git a/src/main.rs b/src/main.rs index a9187a5..15422ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,26 @@ mod dem; 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 std::{borrow::Borrow, fmt}; use std::{env, str::FromStr}; +use tokio::sync::mpsc::error; +use serde::ser::{SerializeSeq, Serializer}; use serde::{Deserialize, Deserializer, Serialize}; +use serde_qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection}; use tower_http::trace::TraceLayer; use tower_http::{ services::ServeDir, trace::{self, DefaultMakeSpan}, }; -use tracing::{info, Level, Span}; -use serde_qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection}; - +use tracing::{error, info, Level, Span}; use dem::DatasetRepository; @@ -29,16 +34,36 @@ struct Opts { } #[derive(Deserialize, Debug)] -struct JsParams{ +struct JsParams { #[serde(default, deserialize_with = "deserialize_array")] pts: Vec<(f64, f64)>, } #[derive(Serialize, Debug)] struct JsResult { + #[serde(serialize_with = "serialize_vec_round")] elevations: Vec>, } +fn serialize_vec_round(v: &Vec>, s: S) -> Result +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::().unwrap(); + sv.serialize_element(&xx)?; + } + }; + } + sv.end() +} + fn deserialize_array<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -47,7 +72,9 @@ where let result: Result, 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())), + 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)) .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)}))); + .layer(Extension(QsQueryConfig::new(5, false).error_handler( + |err| { + QsQueryRejection::new( + format!("Get fucked: {}", err), + StatusCode::UNPROCESSABLE_ENTITY, + ) + }, + ))); let host = format!("[::]:{}", config.port); info!("Will start server on {host}"); @@ -106,10 +138,13 @@ async fn get_elevation( let ele; match dem::elevation_from_coordinates(&dsr, lat, lon).await { //None => return (StatusCode::NOT_IMPLEMENTED, "".to_string()), - None => { - return "".to_string().into_response(); + Ok(x) => match x { + Some(el) => ele = el, + None => ele = -1.0, + }, + Err(e) => { + return e.to_string().into_response(); } - Some(el) => ele = el, } #[derive(Serialize)] @@ -125,18 +160,21 @@ async fn get_elevation( } } - #[debug_handler] async fn get_elevation_js( State(dsr): State, Query(params): Query, ) -> Response { - let mut response = JsResult { - elevations: vec![], - }; + 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); + match ele { + Ok(x) => response.elevations.push(x), + Err(e) => { + error!("Error: {e}"); + response.elevations.push(None); + } + } } Json(response).into_response() }