use crate::engine::Router;
use crate::error;
use crate::service::RoutingService;
use actix_web::{web, HttpResponse};
use bbox_core::service::ServiceEndpoints;
use log::info;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Deserialize, Serialize)]
pub struct RouteDefinition {
pub name: Option<String>,
pub preference: Option<String>,
pub waypoints: Waypoints,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Waypoints {
pub coordinates: Vec<Vec<f64>>,
#[serde(rename = "type")]
pub value_type: Type,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum Type {
MultiPoint,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SyncMode {
Sync,
Async,
}
#[derive(Debug, Deserialize)]
pub struct RouteParams {
mode: Option<SyncMode>,
}
async fn compute_route(
router: web::Data<Router>,
route_params: web::Query<RouteParams>,
route_def: web::Json<RouteDefinition>,
) -> HttpResponse {
if let Some(mode) = &route_params.mode {
if *mode == SyncMode::Async {
return HttpResponse::UnprocessableEntity().json("Async mode not supported");
}
}
dbg!(&route_def);
let coords = &route_def.waypoints.coordinates;
let shortest_path =
router.calc_path((coords[0][0], coords[0][1]), (coords[1][0], coords[1][1]));
let route = match shortest_path {
Ok(p) => {
router.path_to_geojson(vec![p])
}
Err(_) => {
json!({
"type": "FeatureCollection",
"status": "failed",
"features": []
})
}
};
HttpResponse::Ok().json(route)
}
#[derive(Debug, Deserialize, Serialize)]
pub struct BasicQuery {
pub profile: Option<String>,
pub from_pos: String,
pub to_pos: String,
}
async fn basic_route(
router: web::Data<Router>,
query: web::Query<BasicQuery>,
) -> actix_web::Result<HttpResponse, actix_web::Error> {
fn split_pair(numlist: &str) -> error::Result<(f64, f64)> {
let arr: Vec<f64> = numlist
.split(',')
.map(|v| {
v.parse()
.expect("Error parsing `{numlist}` as list of float values")
})
.collect();
if arr.len() != 2 {
return Err(error::Error::ArgumentError(
"Error parsing `{numlist}` as list of float values".to_string(),
));
}
Ok((arr[0], arr[1]))
}
let (from_lon, from_lat) = split_pair(&query.from_pos).unwrap();
let (to_lon, to_lat) = split_pair(&query.to_pos).unwrap();
let shortest_path = router.calc_path((from_lon, from_lat), (to_lon, to_lat));
let route = match shortest_path {
Ok(p) => router.path_to_geojson(vec![p]),
Err(e) => {
info!("{e}");
json!({
"type": "FeatureCollection",
"status": "failed",
"features": []
})
}
};
Ok(HttpResponse::Ok().json(route))
}
#[derive(Debug, Deserialize, Serialize)]
pub struct VahlhallaQuery {
pub locations: Vec<LonLat>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct LonLat {
pub lon: f64,
pub lat: f64,
}
async fn valhalla_route(
router: web::Data<Router>,
query: web::Json<VahlhallaQuery>,
) -> HttpResponse {
dbg!(&query.locations);
let loc0 = &query.locations[0];
let loc1 = &query.locations[1];
let shortest_path = router.calc_path((loc0.lon, loc0.lat), (loc1.lon, loc1.lat));
let route = match shortest_path {
Ok(p) => router.path_to_valhalla_json(vec![p]),
Err(e) => {
json!({"error_code":171,"error":e.to_string(),"status_code":400,"status":"Bad Request"})
}
};
HttpResponse::Ok().json(route)
}
impl ServiceEndpoints for RoutingService {
fn register_endpoints(&self, cfg: &mut web::ServiceConfig) {
if let Some(router) = &self.router {
cfg.app_data(web::Data::new(router.clone()));
}
cfg.service(web::resource("/routes").route(web::post().to(compute_route)));
cfg.service(web::resource("/routes/basic").route(web::get().to(basic_route)));
cfg.service(web::resource("/routes/valhalla/route").route(web::post().to(valhalla_route)));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::tests::router;
use actix_web::{body, dev::Service, http, test, App, Error};
#[actix_web::test]
async fn test_route() -> Result<(), Error> {
let router = router("../assets/railway-test.gpkg", "flows", "geom").await;
let app = test::init_service(
App::new()
.app_data(web::Data::new(router))
.service(web::resource("/routes").route(web::post().to(compute_route))),
)
.await;
let req = test::TestRequest::post()
.uri("/routes")
.set_json(RouteDefinition {
name: Some("A to B".to_string()),
preference: None,
waypoints: Waypoints {
value_type: Type::MultiPoint,
coordinates: vec![vec![9.35213353, 47.0935012], vec![9.3422712, 47.1011887]],
},
})
.to_request();
let resp = app.call(req).await.unwrap();
assert_eq!(resp.status(), http::StatusCode::OK);
let response_body = body::to_bytes(resp.into_body()).await?;
assert_eq!(response_body, "{\"features\":[{\"geometry\":{\"coordinates\":[[9.351943003846154,47.093613230769236],[9.348591366666666,47.096161],[9.343048573684209,47.100490268421055]],\"type\":\"LineString\"},\"type\":\"Feature\"}],\"type\":\"FeatureCollection\"}");
Ok(())
}
}