From f117a019b942a2d7d4581f91ae290422b9fbfa16 Mon Sep 17 00:00:00 2001 From: rfitzger Date: Thu, 7 Dec 2023 15:12:05 -0700 Subject: [PATCH 1/9] basic graph lookup operations from CompassApp --- python/nrel/routee/compass/compass_app.py | 15 +++ .../src/algorithm/search/direction.rs | 5 +- rust/routee-compass-py/Cargo.toml | 1 + rust/routee-compass-py/src/app_graph_ops.rs | 98 +++++++++++++++++++ rust/routee-compass-py/src/app_wrapper.rs | 37 ++++++- rust/routee-compass-py/src/lib.rs | 1 + rust/routee-compass/src/app/search/mod.rs | 1 + .../src/app/search/search_app.rs | 4 + .../src/app/search/search_app_graph_ops.rs | 91 +++++++++++++++++ 9 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 rust/routee-compass-py/src/app_graph_ops.rs create mode 100644 rust/routee-compass/src/app/search/search_app_graph_ops.rs diff --git a/python/nrel/routee/compass/compass_app.py b/python/nrel/routee/compass/compass_app.py index 6d443edd..df32bc1e 100644 --- a/python/nrel/routee/compass/compass_app.py +++ b/python/nrel/routee/compass/compass_app.py @@ -109,6 +109,21 @@ def run(self, query: Union[Query, List[Query]]) -> Result: if single_query and len(results) == 1: return results[0] return results + + def graph_edge_origin(self, edge_id: int) -> int: + return self._app.graph_edge_origin(edge_id) + + def graph_edge_destination(self, edge_id: int) -> int: + return self._app.graph_edge_destination(edge_id) + + def graph_edge_distance(self, edge_id: int, distance_unit: Optional[str]) -> float: + return self._app.graph_edge_distance(edge_id, distance_unit) + + def graph_get_out_edge_ids(self, vertex_id: int) -> List[int]: + return self._app.graph_get_out_edge_ids(vertex_id) + + def graph_get_in_edge_ids(self, vertex_id: int) -> List[int]: + return self._app.graph_get_in_edge_ids(vertex_id) def inject_to_disk_plugin(output_file: str, toml_config: dict) -> dict: diff --git a/rust/routee-compass-core/src/algorithm/search/direction.rs b/rust/routee-compass-core/src/algorithm/search/direction.rs index ee327db3..73ceb4af 100644 --- a/rust/routee-compass-core/src/algorithm/search/direction.rs +++ b/rust/routee-compass-core/src/algorithm/search/direction.rs @@ -1,4 +1,7 @@ -#[derive(Copy, Clone)] +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Serialize, Deserialize)] +#[serde(rename = "snake_case")] pub enum Direction { Forward, Reverse, diff --git a/rust/routee-compass-py/Cargo.toml b/rust/routee-compass-py/Cargo.toml index bf5fd24d..380dbcf2 100644 --- a/rust/routee-compass-py/Cargo.toml +++ b/rust/routee-compass-py/Cargo.toml @@ -11,6 +11,7 @@ documentation = "https://docs.rs/routee-compass" [dependencies] routee-compass = { path = "../routee-compass", version = "0.4.0" } +routee-compass-core = { path = "../routee-compass-core", version = "0.4.0" } pyo3 = { version = "0.20.0", features = ["extension-module"] } serde_json = { workspace = true } config = { workspace = true } diff --git a/rust/routee-compass-py/src/app_graph_ops.rs b/rust/routee-compass-py/src/app_graph_ops.rs new file mode 100644 index 00000000..dc88e5cd --- /dev/null +++ b/rust/routee-compass-py/src/app_graph_ops.rs @@ -0,0 +1,98 @@ +use pyo3::{exceptions::PyException, PyErr, PyResult}; +use routee_compass::app::search::search_app_graph_ops::SearchAppGraphOps; +use routee_compass_core::{ + algorithm::search::direction::Direction, + model::road_network::{edge_id::EdgeId, vertex_id::VertexId}, + util::unit::{as_f64::AsF64, DistanceUnit}, +}; + +use crate::app_wrapper::CompassAppWrapper; + +pub fn graph_edge_origin(app: &CompassAppWrapper, edge_id: usize) -> PyResult { + let edge_id_internal = EdgeId(edge_id); + app.routee_compass + .search_app + .get_edge_origin(edge_id_internal) + .map(|o| o.0) + .map_err(|e| { + PyException::new_err(format!( + "error retrieving edge origin for edge_id {}: {}", + edge_id, e + )) + }) +} + +pub fn graph_edge_destination(app: &CompassAppWrapper, edge_id: usize) -> PyResult { + let edge_id_internal = EdgeId(edge_id); + app.routee_compass + .search_app + .get_edge_destination(edge_id_internal) + .map(|o| o.0) + .map_err(|e| { + PyException::new_err(format!( + "error retrieving edge destination for edge_id {}: {}", + edge_id, e + )) + }) +} + +pub fn graph_edge_distance( + app: &CompassAppWrapper, + edge_id: usize, + distance_unit: Option, +) -> PyResult { + let du_internal_result: PyResult> = match distance_unit { + Some(du_str) => { + let mut enquoted = du_str.clone(); + enquoted.insert(0, '"'); + enquoted.push('"'); + let du = serde_json::from_str::(enquoted.as_str()).map_err(|_| { + PyException::new_err(format!("could not deserialize distance unit '{}'", du_str)) + })?; + + Ok(Some(du)) + } + + None => Ok(None), + }; + let du_internal = du_internal_result?; + let edge_id_internal = EdgeId(edge_id); + app.routee_compass + .search_app + .get_edge_distance(edge_id_internal, du_internal) + .map(|o| o.as_f64()) + .map_err(|e| { + PyException::new_err(format!( + "error retrieving edge destination for edge_id {}: {}", + edge_id, e + )) + }) +} + +pub fn get_out_edge_ids(app: &CompassAppWrapper, vertex_id: usize) -> PyResult> { + let vertex_id_internal = VertexId(vertex_id); + app.routee_compass + .search_app + .get_incident_edge_ids(vertex_id_internal, Direction::Forward) + .map(|es| es.iter().map(|e| e.0).collect()) + .map_err(|e| { + PyException::new_err(format!( + "error retrieving out edges for vertex_id {}: {}", + vertex_id, e + )) + }) +} + +pub fn get_in_edge_ids(app: &CompassAppWrapper, vertex_id: usize) -> PyResult> { + let vertex_id_internal = VertexId(vertex_id); + app.routee_compass + .search_app + .get_incident_edge_ids(vertex_id_internal, Direction::Reverse) + .map(|es| es.iter().map(|e| e.0).collect()) + .map_err(|e| { + PyException::new_err(format!( + "error retrieving in edges for vertex_id {}: {}", + vertex_id, e + )) + }) +} diff --git a/rust/routee-compass-py/src/app_wrapper.rs b/rust/routee-compass-py/src/app_wrapper.rs index a01fe828..c7cbf576 100644 --- a/rust/routee-compass-py/src/app_wrapper.rs +++ b/rust/routee-compass-py/src/app_wrapper.rs @@ -1,18 +1,47 @@ use std::path::Path; +use crate::app_graph_ops as ops; use pyo3::{exceptions::PyException, prelude::*, types::PyType}; -use routee_compass::app::compass::{ - compass_app::CompassApp, compass_app_ops::read_config_from_string, - config::compass_app_builder::CompassAppBuilder, +use routee_compass::app::{ + compass::{ + compass_app::CompassApp, compass_app_ops::read_config_from_string, + config::compass_app_builder::CompassAppBuilder, + }, + search::search_app_graph_ops::SearchAppGraphOps, }; +use routee_compass_core::model::road_network::edge_id::EdgeId; #[pyclass] pub struct CompassAppWrapper { - routee_compass: CompassApp, + pub routee_compass: CompassApp, } #[pymethods] impl CompassAppWrapper { + pub fn graph_edge_origin(&self, edge_id: usize) -> PyResult { + ops::graph_edge_origin(self, edge_id) + } + + pub fn graph_edge_destination(&self, edge_id: usize) -> PyResult { + ops::graph_edge_destination(self, edge_id) + } + + pub fn graph_edge_distance( + &self, + edge_id: usize, + distance_unit: Option, + ) -> PyResult { + ops::graph_edge_distance(self, edge_id, distance_unit) + } + + pub fn graph_get_out_edge_ids(&self, vertex_id: usize) -> PyResult> { + ops::get_out_edge_ids(self, vertex_id) + } + + pub fn graph_get_in_edge_ids(&self, vertex_id: usize) -> PyResult> { + ops::get_in_edge_ids(self, vertex_id) + } + #[classmethod] pub fn _from_config_toml_string( _cls: &PyType, diff --git a/rust/routee-compass-py/src/lib.rs b/rust/routee-compass-py/src/lib.rs index e2adc0c2..3faa5b60 100644 --- a/rust/routee-compass-py/src/lib.rs +++ b/rust/routee-compass-py/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("doc.md")] +pub mod app_graph_ops; pub mod app_wrapper; use app_wrapper::CompassAppWrapper; diff --git a/rust/routee-compass/src/app/search/mod.rs b/rust/routee-compass/src/app/search/mod.rs index 6c0565d9..e2b62628 100644 --- a/rust/routee-compass/src/app/search/mod.rs +++ b/rust/routee-compass/src/app/search/mod.rs @@ -1,2 +1,3 @@ pub mod search_app; +pub mod search_app_graph_ops; pub mod search_app_result; diff --git a/rust/routee-compass/src/app/search/search_app.rs b/rust/routee-compass/src/app/search/search_app.rs index 7adf7671..b2b6a8d6 100644 --- a/rust/routee-compass/src/app/search/search_app.rs +++ b/rust/routee-compass/src/app/search/search_app.rs @@ -198,4 +198,8 @@ impl SearchApp { .build(query)?; Ok(tm) } + + pub fn get_graph_reference(&self) -> Arc> { + Arc::new(self.graph.read_only()) + } } diff --git a/rust/routee-compass/src/app/search/search_app_graph_ops.rs b/rust/routee-compass/src/app/search/search_app_graph_ops.rs new file mode 100644 index 00000000..a7d63665 --- /dev/null +++ b/rust/routee-compass/src/app/search/search_app_graph_ops.rs @@ -0,0 +1,91 @@ +use routee_compass_core::{ + algorithm::search::direction::Direction, + model::road_network::{edge_id::EdgeId, graph::Graph, vertex_id::VertexId}, + util::unit::{Distance, DistanceUnit}, +}; + +use crate::app::compass::compass_app_error::CompassAppError; + +use super::search_app::SearchApp; + +pub trait SearchAppGraphOps { + fn get_edge_origin(&self, edge_id: EdgeId) -> Result; + fn get_edge_destination(&self, edge_id: EdgeId) -> Result; + fn get_edge_distance( + &self, + edge_id: EdgeId, + distance_unit: Option, + ) -> Result; + fn get_incident_edge_ids( + &self, + vertex_id: VertexId, + direction: Direction, + ) -> Result, CompassAppError>; +} + +impl SearchAppGraphOps for SearchApp { + fn get_edge_origin(&self, edge_id: EdgeId) -> Result { + let op = move |g: &Graph| { + let edge = g.get_edge(edge_id).map_err(CompassAppError::GraphError)?; + Ok(edge.src_vertex_id) + }; + let result: VertexId = graph_op(self, &op)?; + Ok(result) + } + + fn get_edge_destination(&self, edge_id: EdgeId) -> Result { + let op = move |g: &Graph| { + let edge = g.get_edge(edge_id).map_err(CompassAppError::GraphError)?; + Ok(edge.dst_vertex_id) + }; + let result: VertexId = graph_op(self, &op)?; + Ok(result) + } + + fn get_edge_distance( + &self, + edge_id: EdgeId, + distance_unit: Option, + ) -> Result { + let op = move |g: &Graph| { + let edge = g.get_edge(edge_id).map_err(CompassAppError::GraphError)?; + Ok(edge.distance) + }; + let result_base: Distance = graph_op(self, &op)?; + let result = match distance_unit { + Some(du) => DistanceUnit::Meters.convert(result_base, du), + None => result_base, + }; + Ok(result) + } + + fn get_incident_edge_ids( + &self, + vertex_id: VertexId, + direction: Direction, + ) -> Result, CompassAppError> { + let op = move |g: &Graph| { + let incident_edges = g + .incident_edges(vertex_id, direction) + .map_err(CompassAppError::GraphError)?; + Ok(incident_edges) + }; + let result: Vec = graph_op(self, &op)?; + Ok(result) + } +} + +fn graph_op( + app: &SearchApp, + op: &dyn Fn(&Graph) -> Result, +) -> Result +where + T: Send, +{ + let g_ref = app.get_graph_reference(); + let g = g_ref + .read() + .map_err(|e| CompassAppError::ReadOnlyPoisonError(e.to_string()))?; + let result = op(&g)?; + Ok(result) +} From af8295902393bcfe6a8557612b4e9652424de126 Mon Sep 17 00:00:00 2001 From: rfitzger Date: Thu, 7 Dec 2023 15:13:30 -0700 Subject: [PATCH 2/9] fmt --- python/nrel/routee/compass/compass_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/nrel/routee/compass/compass_app.py b/python/nrel/routee/compass/compass_app.py index df32bc1e..a7e14bdf 100644 --- a/python/nrel/routee/compass/compass_app.py +++ b/python/nrel/routee/compass/compass_app.py @@ -109,7 +109,7 @@ def run(self, query: Union[Query, List[Query]]) -> Result: if single_query and len(results) == 1: return results[0] return results - + def graph_edge_origin(self, edge_id: int) -> int: return self._app.graph_edge_origin(edge_id) From f19aad00d57c656e357dec38e0f8b2c7f738c419 Mon Sep 17 00:00:00 2001 From: rfitzger Date: Fri, 8 Dec 2023 09:05:45 -0700 Subject: [PATCH 3/9] provide default behavior of distance in meters --- python/nrel/routee/compass/compass_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/nrel/routee/compass/compass_app.py b/python/nrel/routee/compass/compass_app.py index a7e14bdf..83c9079d 100644 --- a/python/nrel/routee/compass/compass_app.py +++ b/python/nrel/routee/compass/compass_app.py @@ -116,7 +116,7 @@ def graph_edge_origin(self, edge_id: int) -> int: def graph_edge_destination(self, edge_id: int) -> int: return self._app.graph_edge_destination(edge_id) - def graph_edge_distance(self, edge_id: int, distance_unit: Optional[str]) -> float: + def graph_edge_distance(self, edge_id: int, distance_unit: Optional[str] = None) -> float: return self._app.graph_edge_distance(edge_id, distance_unit) def graph_get_out_edge_ids(self, vertex_id: int) -> List[int]: From 9aeeccdfc6120674ed3727971c45059fb833a3ea Mon Sep 17 00:00:00 2001 From: rfitzger Date: Fri, 8 Dec 2023 09:09:31 -0700 Subject: [PATCH 4/9] docstring comments for graph ops --- python/nrel/routee/compass/compass_app.py | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/python/nrel/routee/compass/compass_app.py b/python/nrel/routee/compass/compass_app.py index 83c9079d..98f53429 100644 --- a/python/nrel/routee/compass/compass_app.py +++ b/python/nrel/routee/compass/compass_app.py @@ -111,18 +111,64 @@ def run(self, query: Union[Query, List[Query]]) -> Result: return results def graph_edge_origin(self, edge_id: int) -> int: + """ + get the origin vertex id for some edge + + Args: + edge_id (int): the id of the edge + + Returns: + int: the vertex id at the source of the edge + """ return self._app.graph_edge_origin(edge_id) def graph_edge_destination(self, edge_id: int) -> int: + """ + get the destination vertex id for some edge + + Args: + edge_id (int): the id of the edge + + Returns: + int: the vertex id at the destination of the edge + """ return self._app.graph_edge_destination(edge_id) def graph_edge_distance(self, edge_id: int, distance_unit: Optional[str] = None) -> float: + """ + get the distance for some edge + + Args: + edge_id (int): the id of the edge + distance_unit (Optional[str]): distance unit, by default meters + + Returns: + int: the distance covered by traversing the edge + """ return self._app.graph_edge_distance(edge_id, distance_unit) def graph_get_out_edge_ids(self, vertex_id: int) -> List[int]: + """ + get the list of edge ids that depart from some vertex + + Args: + vertex_id (int): the id of the vertex + + Returns: + List[int]: the edge ids of edges departing from this vertex + """ return self._app.graph_get_out_edge_ids(vertex_id) def graph_get_in_edge_ids(self, vertex_id: int) -> List[int]: + """ + get the list of edge ids that arrive from some vertex + + Args: + vertex_id (int): the id of the vertex + + Returns: + List[int]: the edge ids of edges arriving at this vertex + """ return self._app.graph_get_in_edge_ids(vertex_id) From d6bddebe3ec656e2dfbb2a53a605490bfc03f7e8 Mon Sep 17 00:00:00 2001 From: rfitzger Date: Fri, 8 Dec 2023 09:55:31 -0700 Subject: [PATCH 5/9] formatting --- python/nrel/routee/compass/compass_app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/nrel/routee/compass/compass_app.py b/python/nrel/routee/compass/compass_app.py index 98f53429..b13dcf8b 100644 --- a/python/nrel/routee/compass/compass_app.py +++ b/python/nrel/routee/compass/compass_app.py @@ -131,10 +131,12 @@ def graph_edge_destination(self, edge_id: int) -> int: Returns: int: the vertex id at the destination of the edge - """ + """ return self._app.graph_edge_destination(edge_id) - def graph_edge_distance(self, edge_id: int, distance_unit: Optional[str] = None) -> float: + def graph_edge_distance( + self, edge_id: int, distance_unit: Optional[str] = None + ) -> float: """ get the distance for some edge From 0ef43d2d698ce9ae7ddddafb7385a93513731f50 Mon Sep 17 00:00:00 2001 From: rfitzger Date: Fri, 8 Dec 2023 10:14:26 -0700 Subject: [PATCH 6/9] explain deserialize hack --- rust/routee-compass-py/src/app_graph_ops.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust/routee-compass-py/src/app_graph_ops.rs b/rust/routee-compass-py/src/app_graph_ops.rs index dc88e5cd..bd3a57f3 100644 --- a/rust/routee-compass-py/src/app_graph_ops.rs +++ b/rust/routee-compass-py/src/app_graph_ops.rs @@ -43,6 +43,9 @@ pub fn graph_edge_distance( ) -> PyResult { let du_internal_result: PyResult> = match distance_unit { Some(du_str) => { + // `DistanceUnit` is a non-parameterized enum with a snake-case deserializer. + // by surrounding the incoming string with quotes, it becomes valid JSON, which + // we can deserialize via serde_json. let mut enquoted = du_str.clone(); enquoted.insert(0, '"'); enquoted.push('"'); From 65648b41ae97c04d8673b07f47e6c938b71d9eae Mon Sep 17 00:00:00 2001 From: rfitzger Date: Fri, 8 Dec 2023 10:26:46 -0700 Subject: [PATCH 7/9] helper for string deserializing --- rust/routee-compass-core/src/util/mod.rs | 1 + .../routee-compass-core/src/util/serde_ops.rs | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 rust/routee-compass-core/src/util/serde_ops.rs diff --git a/rust/routee-compass-core/src/util/mod.rs b/rust/routee-compass-core/src/util/mod.rs index b0305cdc..23b962ed 100644 --- a/rust/routee-compass-core/src/util/mod.rs +++ b/rust/routee-compass-core/src/util/mod.rs @@ -5,4 +5,5 @@ pub mod geo; pub mod io_utils; pub mod multiset; pub mod read_only_lock; +pub mod serde_ops; pub mod unit; diff --git a/rust/routee-compass-core/src/util/serde_ops.rs b/rust/routee-compass-core/src/util/serde_ops.rs new file mode 100644 index 00000000..32070f7c --- /dev/null +++ b/rust/routee-compass-core/src/util/serde_ops.rs @@ -0,0 +1,21 @@ +use serde::de; + +/// hack-ish trick for types which can be deserialized from a string +/// representation, such as enums where all variants have no arguments. +/// +/// # Arguments +/// +/// * `input` - string to deserialize +/// +/// # Returns +/// +/// the deserialized value or a deserialization error +pub fn string_deserialize(input: &str) -> Result +where + T: de::DeserializeOwned, +{ + let mut enquoted = input.to_owned(); + enquoted.insert(0, '"'); + enquoted.push('"'); + serde_json::from_str::(enquoted.as_str()) +} From 5ff2f3e4486892254510be2051001f674c8c9b9c Mon Sep 17 00:00:00 2001 From: rfitzger Date: Fri, 8 Dec 2023 10:26:54 -0700 Subject: [PATCH 8/9] FromStr implementation --- .../src/util/unit/distance_unit.rs | 10 ++++++++++ .../src/util/unit/energy_rate_unit.rs | 10 ++++++++++ .../src/util/unit/energy_unit.rs | 13 +++++++++++-- .../routee-compass-core/src/util/unit/grade_unit.rs | 10 ++++++++++ .../routee-compass-core/src/util/unit/speed_unit.rs | 10 ++++++++++ rust/routee-compass-core/src/util/unit/time_unit.rs | 10 ++++++++++ 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/rust/routee-compass-core/src/util/unit/distance_unit.rs b/rust/routee-compass-core/src/util/unit/distance_unit.rs index a14c6377..508a7cb6 100644 --- a/rust/routee-compass-core/src/util/unit/distance_unit.rs +++ b/rust/routee-compass-core/src/util/unit/distance_unit.rs @@ -1,5 +1,7 @@ use super::Distance; +use crate::util::serde_ops::string_deserialize; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[serde(rename_all = "snake_case")] @@ -35,6 +37,14 @@ impl std::fmt::Display for DistanceUnit { } } +impl FromStr for DistanceUnit { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + string_deserialize(s) + } +} + #[cfg(test)] mod test { diff --git a/rust/routee-compass-core/src/util/unit/energy_rate_unit.rs b/rust/routee-compass-core/src/util/unit/energy_rate_unit.rs index f7cd9b5f..48a58328 100644 --- a/rust/routee-compass-core/src/util/unit/energy_rate_unit.rs +++ b/rust/routee-compass-core/src/util/unit/energy_rate_unit.rs @@ -1,5 +1,7 @@ use super::{DistanceUnit, EnergyUnit}; +use crate::util::serde_ops::string_deserialize; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Copy)] #[serde(rename_all = "snake_case")] @@ -47,3 +49,11 @@ impl std::fmt::Display for EnergyRateUnit { write!(f, "{}", s) } } + +impl FromStr for EnergyRateUnit { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + string_deserialize(s) + } +} diff --git a/rust/routee-compass-core/src/util/unit/energy_unit.rs b/rust/routee-compass-core/src/util/unit/energy_unit.rs index cc20328a..74253636 100644 --- a/rust/routee-compass-core/src/util/unit/energy_unit.rs +++ b/rust/routee-compass-core/src/util/unit/energy_unit.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; - use super::Energy; +use crate::util::serde_ops::string_deserialize; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)] #[serde(rename_all = "snake_case")] @@ -36,3 +37,11 @@ impl std::fmt::Display for EnergyUnit { write!(f, "{}", s) } } + +impl FromStr for EnergyUnit { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + string_deserialize(s) + } +} diff --git a/rust/routee-compass-core/src/util/unit/grade_unit.rs b/rust/routee-compass-core/src/util/unit/grade_unit.rs index 53e590d6..f401fe9a 100644 --- a/rust/routee-compass-core/src/util/unit/grade_unit.rs +++ b/rust/routee-compass-core/src/util/unit/grade_unit.rs @@ -1,5 +1,7 @@ use super::Grade; +use crate::util::serde_ops::string_deserialize; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[serde(rename_all = "snake_case")] @@ -35,6 +37,14 @@ impl std::fmt::Display for GradeUnit { } } +impl FromStr for GradeUnit { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + string_deserialize(s) + } +} + #[cfg(test)] mod test { diff --git a/rust/routee-compass-core/src/util/unit/speed_unit.rs b/rust/routee-compass-core/src/util/unit/speed_unit.rs index a282bcb8..cc363c5b 100644 --- a/rust/routee-compass-core/src/util/unit/speed_unit.rs +++ b/rust/routee-compass-core/src/util/unit/speed_unit.rs @@ -1,6 +1,8 @@ use super::Speed; use super::{DistanceUnit, TimeUnit}; +use crate::util::serde_ops::string_deserialize; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[serde(rename_all = "snake_case")] @@ -19,6 +21,14 @@ impl std::fmt::Display for SpeedUnit { } } +impl FromStr for SpeedUnit { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + string_deserialize(s) + } +} + impl From<(DistanceUnit, TimeUnit)> for SpeedUnit { fn from(value: (DistanceUnit, TimeUnit)) -> Self { use DistanceUnit as D; diff --git a/rust/routee-compass-core/src/util/unit/time_unit.rs b/rust/routee-compass-core/src/util/unit/time_unit.rs index 8d0ce76f..a1a6f7d9 100644 --- a/rust/routee-compass-core/src/util/unit/time_unit.rs +++ b/rust/routee-compass-core/src/util/unit/time_unit.rs @@ -1,5 +1,7 @@ use super::Time; +use crate::util::serde_ops::string_deserialize; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "snake_case")] @@ -43,6 +45,14 @@ impl std::fmt::Display for TimeUnit { } } +impl FromStr for TimeUnit { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + string_deserialize(s) + } +} + #[cfg(test)] mod test { From 201511831792c4c8c17f40523cee5fbeb4d6e3e1 Mon Sep 17 00:00:00 2001 From: rfitzger Date: Fri, 8 Dec 2023 10:27:02 -0700 Subject: [PATCH 9/9] use DistanceUnit.from_str --- rust/routee-compass-py/src/app_graph_ops.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/rust/routee-compass-py/src/app_graph_ops.rs b/rust/routee-compass-py/src/app_graph_ops.rs index bd3a57f3..146d9653 100644 --- a/rust/routee-compass-py/src/app_graph_ops.rs +++ b/rust/routee-compass-py/src/app_graph_ops.rs @@ -1,12 +1,12 @@ -use pyo3::{exceptions::PyException, PyErr, PyResult}; +use crate::app_wrapper::CompassAppWrapper; +use pyo3::{exceptions::PyException, PyResult}; use routee_compass::app::search::search_app_graph_ops::SearchAppGraphOps; use routee_compass_core::{ algorithm::search::direction::Direction, model::road_network::{edge_id::EdgeId, vertex_id::VertexId}, util::unit::{as_f64::AsF64, DistanceUnit}, }; - -use crate::app_wrapper::CompassAppWrapper; +use std::str::FromStr; pub fn graph_edge_origin(app: &CompassAppWrapper, edge_id: usize) -> PyResult { let edge_id_internal = EdgeId(edge_id); @@ -43,13 +43,7 @@ pub fn graph_edge_distance( ) -> PyResult { let du_internal_result: PyResult> = match distance_unit { Some(du_str) => { - // `DistanceUnit` is a non-parameterized enum with a snake-case deserializer. - // by surrounding the incoming string with quotes, it becomes valid JSON, which - // we can deserialize via serde_json. - let mut enquoted = du_str.clone(); - enquoted.insert(0, '"'); - enquoted.push('"'); - let du = serde_json::from_str::(enquoted.as_str()).map_err(|_| { + let du = DistanceUnit::from_str(du_str.as_str()).map_err(|_| { PyException::new_err(format!("could not deserialize distance unit '{}'", du_str)) })?;