#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
pub mod costing;
pub mod elevation;
pub mod matrix;
pub mod route;
pub mod shapes;
pub mod status;
pub mod trace_attributes;
use log::trace;
use serde::{Deserialize, Serialize};
pub type Coordinate = (f32, f32);
impl From<Coordinate> for shapes::ShapePoint {
fn from((lon, lat): Coordinate) -> Self {
Self {
lon: lon as f64,
lat: lat as f64,
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct CodedDescription {
pub code: u64,
pub description: String,
}
pub(crate) fn serialize_naive_date_time_opt<S>(
value: &Option<chrono::NaiveDateTime>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match value {
None => serializer.serialize_none(),
Some(value) => serialize_naive_date_time(value, serializer),
}
}
fn serialize_naive_date_time<S>(
value: &chrono::NaiveDateTime,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&value.format("%Y-%m-%dT%H:%M").to_string())
}
#[derive(Serialize, Deserialize, Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Units {
#[default]
#[serde(rename = "kilometers")]
Metric,
#[serde(rename = "miles")]
Imperial,
}
#[derive(Serialize, Debug)]
pub struct DateTime {
r#type: MatrixDateTimeType,
#[serde(serialize_with = "serialize_naive_date_time")]
value: chrono::NaiveDateTime,
}
impl DateTime {
pub fn from_current_departure_time() -> Self {
Self {
r#type: MatrixDateTimeType::CurrentDeparture,
value: chrono::Local::now().naive_local(),
}
}
pub fn from_departure_time(depart_after: chrono::NaiveDateTime) -> Self {
Self {
r#type: MatrixDateTimeType::SpecifiedDeparture,
value: depart_after,
}
}
pub fn from_arrival_time(arrive_by: chrono::NaiveDateTime) -> Self {
Self {
r#type: MatrixDateTimeType::SpecifiedArrival,
value: arrive_by,
}
}
}
#[derive(serde_repr::Serialize_repr, Debug, Clone, Copy)]
#[repr(u8)]
enum MatrixDateTimeType {
CurrentDeparture = 0,
SpecifiedDeparture,
SpecifiedArrival,
}
#[derive(Debug)]
pub enum Error {
Reqwest(reqwest::Error),
Url(url::ParseError),
Serde(serde_json::Error),
RemoteError(RemoteError),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Reqwest(e) => write!(f, "reqwest error: {e}"),
Self::Url(e) => write!(f, "url error: {e}"),
Self::Serde(e) => write!(f, "serde error: {e}"),
Self::RemoteError(e) => write!(f, "remote error: {e:?}"),
}
}
}
impl std::error::Error for Error {}
#[derive(Debug, Deserialize)]
pub struct RemoteError {
pub error_code: isize,
pub error: String,
pub status_code: isize,
pub status: String,
}
#[cfg(feature = "blocking")]
pub mod blocking {
use crate::{
elevation, matrix, route, status, trace_attributes, Error, VALHALLA_PUBLIC_API_URL,
};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Valhalla {
runtime: Arc<tokio::runtime::Runtime>,
client: super::Valhalla,
}
impl Valhalla {
pub fn new(base_url: url::Url) -> Self {
Self::with_client(base_url.clone(), reqwest::Client::new())
}
pub fn with_client(base_url: url::Url, client: reqwest::Client) -> Self {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
.expect("tokio runtime can be created");
Self {
runtime: Arc::new(runtime),
client: super::Valhalla::with_client(base_url, client),
}
}
pub fn route(&self, manifest: route::Manifest) -> Result<route::Trip, Error> {
self.runtime
.block_on(async move { self.client.route(manifest).await })
}
pub fn matrix(&self, manifest: matrix::Manifest) -> Result<matrix::Response, Error> {
self.runtime
.block_on(async move { self.client.matrix(manifest).await })
}
pub fn elevation(
&self,
manifest: elevation::Manifest,
) -> Result<elevation::Response, Error> {
self.runtime
.block_on(async move { self.client.elevation(manifest).await })
}
pub fn status(&self, manifest: status::Manifest) -> Result<status::Response, Error> {
self.runtime
.block_on(async move { self.client.status(manifest).await })
}
pub fn trace_attributes(
&self,
manifest: trace_attributes::Manifest,
) -> Result<trace_attributes::Response, Error> {
self.runtime
.block_on(async move { self.client.trace_attributes(manifest).await })
}
}
impl Default for Valhalla {
fn default() -> Self {
Self::new(
url::Url::parse(VALHALLA_PUBLIC_API_URL)
.expect("VALHALLA_PUBLIC_API_URL is not a valid url"),
)
}
}
}
pub const VALHALLA_PUBLIC_API_URL: &str = "https://valhalla1.openstreetmap.de/";
#[derive(Debug, Clone)]
pub struct Valhalla {
client: reqwest::Client,
base_url: url::Url,
}
impl Valhalla {
pub fn new(base_url: url::Url) -> Self {
Self::with_client(base_url, reqwest::Client::new())
}
pub fn with_client(base_url: url::Url, client: reqwest::Client) -> Self {
Self { client, base_url }
}
pub async fn route(&self, manifest: route::Manifest) -> Result<route::Trip, Error> {
let response: route::Response = self.do_request(manifest, "route", "route").await?;
Ok(response.trip)
}
pub async fn matrix(&self, manifest: matrix::Manifest) -> Result<matrix::Response, Error> {
debug_assert_ne!(
manifest.targets.len(),
0,
"a matrix route needs at least one target specified"
);
debug_assert_ne!(
manifest.sources.len(),
0,
"a matrix route needs at least one source specified"
);
self.do_request(manifest, "sources_to_targets", "matrix")
.await
}
pub async fn elevation(
&self,
manifest: elevation::Manifest,
) -> Result<elevation::Response, Error> {
self.do_request(manifest, "height", "elevation").await
}
pub async fn status(&self, manifest: status::Manifest) -> Result<status::Response, Error> {
self.do_request(manifest, "status", "status").await
}
pub async fn trace_attributes(
&self,
manifest: trace_attributes::Manifest,
) -> Result<trace_attributes::Response, Error> {
self.do_request(manifest, "trace_attributes", "trace_attributes")
.await
}
async fn do_request<Resp: for<'de> serde::Deserialize<'de>>(
&self,
manifest: impl serde::Serialize,
path: &'static str,
name: &'static str,
) -> Result<Resp, Error> {
if log::log_enabled!(log::Level::Trace) {
let request = serde_json::to_string(&manifest).map_err(Error::Serde)?;
trace!("Sending {name} request: {request}");
}
let mut url = self.base_url.clone();
url.path_segments_mut()
.expect("base_url is not a valid base url")
.push(path);
let response = self
.client
.post(url)
.json(&manifest)
.send()
.await
.map_err(Error::Reqwest)?;
if response.status().is_client_error() {
return Err(Error::RemoteError(
response.json().await.map_err(Error::Reqwest)?,
));
}
response.error_for_status_ref().map_err(Error::Reqwest)?;
let text = response.text().await.map_err(Error::Reqwest)?;
trace!("{name} responded: {text}");
let response: Resp = serde_json::from_str(&text).map_err(Error::Serde)?;
Ok(response)
}
}
impl Default for Valhalla {
fn default() -> Self {
Self::new(
url::Url::parse(VALHALLA_PUBLIC_API_URL)
.expect("VALHALLA_PUBLIC_API_URL is not a valid url"),
)
}
}