Skip to content

Commit

Permalink
Merge pull request #28 from NREL/rjf/rtree-distance-tolerance
Browse files Browse the repository at this point in the history
optional distance tolerance paramter for rtree plugin
  • Loading branch information
robfitzgerald authored Nov 10, 2023
2 parents d1468e7 + 83e7354 commit ab15523
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ distance_unit = "miles"
[plugin]
input_plugins = [
{ type = "grid_search" },
{ type = "vertex_rtree", vertices_input_file = "vertices-compass.csv.gz" },
{ type = "vertex_rtree", distance_tolerance = 0.2, distance_unit = "kilometers", vertices_input_file = "vertices-compass.csv.gz" },
]
output_plugins = [
{ type = "summary" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ verbose = true
[plugin]
input_plugins = [
{ type = "grid_search" },
{ type = "vertex_rtree", vertices_input_file = "vertices-compass.csv.gz" },
{ type = "vertex_rtree", distance_tolerance = 0.2, distance_unit = "kilometers", vertices_input_file = "vertices-compass.csv.gz" },
]
output_plugins = [
{ type = "summary" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ output_time_unit = "minutes"
[plugin]
input_plugins = [
{ type = "grid_search" },
{ type = "vertex_rtree", vertices_input_file = "vertices-compass.csv.gz" },
{ type = "vertex_rtree", distance_tolerance = 0.2, distance_unit = "kilometers", vertices_input_file = "vertices-compass.csv.gz" },
]
output_plugins = [
{ type = "summary" },
Expand Down
4 changes: 3 additions & 1 deletion rust/routee-compass-core/src/util/unit/distance_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ impl DistanceUnit {

impl std::fmt::Display for DistanceUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
let s = serde_json::to_string(self)
.map_err(|_| std::fmt::Error)?
.replace("\"", "");
write!(f, "{}", s)
}
}
Expand Down
4 changes: 3 additions & 1 deletion rust/routee-compass-core/src/util/unit/energy_rate_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ impl EnergyRateUnit {

impl std::fmt::Display for EnergyRateUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
let s = serde_json::to_string(self)
.map_err(|_| std::fmt::Error)?
.replace("\"", "");
write!(f, "{}", s)
}
}
4 changes: 3 additions & 1 deletion rust/routee-compass-core/src/util/unit/energy_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ impl EnergyUnit {}

impl std::fmt::Display for EnergyUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
let s = serde_json::to_string(self)
.map_err(|_| std::fmt::Error)?
.replace("\"", "");
write!(f, "{}", s)
}
}
4 changes: 3 additions & 1 deletion rust/routee-compass-core/src/util/unit/grade_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ impl GradeUnit {

impl std::fmt::Display for GradeUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
let s = serde_json::to_string(self)
.map_err(|_| std::fmt::Error)?
.replace("\"", "");
write!(f, "{}", s)
}
}
Expand Down
4 changes: 3 additions & 1 deletion rust/routee-compass-core/src/util/unit/speed_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pub enum SpeedUnit {

impl std::fmt::Display for SpeedUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
let s = serde_json::to_string(self)
.map_err(|_| std::fmt::Error)?
.replace("\"", "");
write!(f, "{}", s)
}
}
Expand Down
4 changes: 3 additions & 1 deletion rust/routee-compass-core/src/util/unit/time_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ impl TimeUnit {

impl std::fmt::Display for TimeUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
let s = serde_json::to_string(self)
.map_err(|_| std::fmt::Error)?
.replace("\"", "");
write!(f, "{}", s)
}
}
Expand Down
18 changes: 13 additions & 5 deletions rust/routee-compass/src/plugin/input/default/rtree/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use routee_compass_core::util::unit::{Distance, DistanceUnit};

use crate::{
app::compass::config::{
builders::InputPluginBuilder, compass_configuration_error::CompassConfigurationError,
Expand All @@ -15,13 +17,19 @@ impl InputPluginBuilder for VertexRTreeBuilder {
&self,
parameters: &serde_json::Value,
) -> Result<Box<dyn InputPlugin>, CompassConfigurationError> {
let parent_key = String::from("Vertex RTree Input Plugin");
let vertex_filename_key = String::from("vertices_input_file");
let vertex_path = parameters.get_config_path(
vertex_filename_key,
String::from("Vertex RTree Input Plugin"),
let vertex_path = parameters.get_config_path(vertex_filename_key, parent_key.clone())?;
let tolerance_distance = parameters.get_config_serde_optional::<Distance>(
String::from("distance_tolerance"),
parent_key.clone(),
)?;
let distance_unit = parameters.get_config_serde_optional::<DistanceUnit>(
String::from("distance_unit"),
parent_key.clone(),
)?;
let rtree =
RTreePlugin::from_file(&vertex_path).map_err(CompassConfigurationError::PluginError)?;
let rtree = RTreePlugin::new(&vertex_path, tolerance_distance, distance_unit)
.map_err(CompassConfigurationError::PluginError)?;
let m: Box<dyn InputPlugin> = Box::new(rtree);
return Ok(m);
}
Expand Down
139 changes: 113 additions & 26 deletions rust/routee-compass/src/plugin/input/default/rtree/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ use std::path::Path;
use crate::plugin::input::input_json_extensions::InputJsonExtensions;
use crate::plugin::input::input_plugin::InputPlugin;
use crate::plugin::plugin_error::PluginError;
use geo::{coord, Coord};
use geo::{coord, Coord, Point};
use routee_compass_core::{
model::{graph::graph::Graph, property::vertex::Vertex},
util::fs::read_utils,
util::{
fs::read_utils,
geo::haversine,
unit::{Distance, DistanceUnit, BASE_DISTANCE_UNIT},
},
};
use rstar::{PointDistance, RTree, RTreeObject, AABB};

Expand Down Expand Up @@ -88,49 +92,132 @@ impl PointDistance for RTreeVertex {
/// * An input plugin that uses an RTree to find the nearest vertex to the origin and destination coordinates.
pub struct RTreePlugin {
vertex_rtree: VertexRTree,
tolerance: Option<(Distance, DistanceUnit)>,
}

impl RTreePlugin {
pub fn new(vertices: Box<[Vertex]>) -> Self {
Self {
vertex_rtree: VertexRTree::new(vertices.to_vec()),
}
}
pub fn from_file(vertex_file: &Path) -> Result<Self, PluginError> {
/// creates a new R Tree input plugin instance.
///
/// # Arguments
///
/// * `vertex_file` - file containing vertices
/// * `tolerance_distance` - optional max distance to nearest vertex (assumed infinity if not included)
/// * `distance_unit` - distance unit for tolerance, assumed BASE_DISTANCE_UNIT if not provided
///
/// # Returns
///
/// * a plugin instance or an error from file loading
pub fn new(
vertex_file: &Path,
tolerance_distance: Option<Distance>,
distance_unit: Option<DistanceUnit>,
) -> Result<Self, PluginError> {
let vertices: Box<[Vertex]> =
read_utils::from_csv(&vertex_file, true, None).map_err(PluginError::CsvReadError)?;
Ok(Self::new(vertices))
let vertex_rtree = VertexRTree::new(vertices.to_vec());
let tolerance = match (tolerance_distance, distance_unit) {
(None, None) => None,
(None, Some(_)) => None,
(Some(t), None) => Some((t, BASE_DISTANCE_UNIT)),
(Some(t), Some(u)) => Some((t, u)),
};
Ok(RTreePlugin {
vertex_rtree,
tolerance,
})
}
}

impl InputPlugin for RTreePlugin {
fn process(&self, input: &serde_json::Value) -> Result<Vec<serde_json::Value>, PluginError> {
let mut updated = input.clone();
let origin_coord = input.get_origin_coordinate()?;
let destination_coord_option = input.get_destination_coordinate()?;
/// finds the nearest graph vertex to the user-provided origin (and optionally, destination) coordinates.
///
/// # Arguments
///
/// * `query` - search query assumed to have at least an origin coordinate entry
///
/// # Returns
///
/// * either vertex ids for the nearest coordinates to the the origin (and optionally destination),
/// or, an error if not found or not within tolerance
fn process(&self, query: &serde_json::Value) -> Result<Vec<serde_json::Value>, PluginError> {
let mut updated = query.clone();
let src_coord = query.get_origin_coordinate()?;
let dst_coord_option = query.get_destination_coordinate()?;

let origin_vertex = self
.vertex_rtree
.nearest_vertex(origin_coord)
.ok_or(PluginError::NearestVertexNotFound(origin_coord))?;
let src_vertex =
self.vertex_rtree
.nearest_vertex(src_coord)
.ok_or(PluginError::PluginFailed(format!(
"nearest vertex not found for origin coordinate {:?}",
src_coord
)))?;

updated.add_origin_vertex(origin_vertex.vertex_id)?;
validate_tolerance(src_coord, src_vertex.coordinate, &self.tolerance)?;
updated.add_origin_vertex(src_vertex.vertex_id)?;

match destination_coord_option {
match dst_coord_option {
None => {}
Some(destination_coord) => {
let destination_vertex = self
.vertex_rtree
.nearest_vertex(destination_coord)
.ok_or(PluginError::NearestVertexNotFound(destination_coord))?;
updated.add_destination_vertex(destination_vertex.vertex_id)?;
Some(dst_coord) => {
let dst_vertex = self.vertex_rtree.nearest_vertex(dst_coord).ok_or(
PluginError::PluginFailed(format!(
"nearest vertex not found for destination coordinate {:?}",
dst_coord
)),
)?;
validate_tolerance(dst_coord, dst_vertex.coordinate, &self.tolerance)?;
updated.add_destination_vertex(dst_vertex.vertex_id)?;
}
}

Ok(vec![updated])
}
}

/// confirms that two coordinates are within some stated distance tolerance.
/// if no tolerance is provided, the dst coordinate is assumed to be a valid distance.
///
/// # Arguments
///
/// * `src` - source coordinate
/// * `dst` - destination coordinate that may or may not be within some distance
/// tolerance of the src coordinate
/// * `tolerance` - tolerance parameters set by user for the rtree plugin. if this is None,
/// all coordinate pairs are assumed to be within distance tolerance, but this
/// may lead to unexpected behavior where far away coordinates are considered "matched".
///
/// # Returns
///
/// * nothing, or an error if the coordinates are not within tolerance
fn validate_tolerance(
src: Coord,
dst: Coord,
tolerance: &Option<(Distance, DistanceUnit)>,
) -> Result<(), PluginError> {
match tolerance {
Some((tolerance_distance, tolerance_distance_unit)) => {
let distance_meters = haversine::coord_distance_meters(src, dst)
.map_err(|s| PluginError::PluginFailed(s))?;
let distance = DistanceUnit::Meters.convert(distance_meters, *tolerance_distance_unit);
if &distance >= tolerance_distance {
Err(PluginError::PluginFailed(
format!(
"coord {:?} nearest vertex coord is {:?} which is {} {} away, exceeding the distance tolerance of {} {}",
src,
dst,
distance,
tolerance_distance_unit,
tolerance_distance,
tolerance_distance_unit,
)
))
} else {
Ok(())
}
}
None => Ok(()),
}
}

#[cfg(test)]
mod test {
use std::{
Expand Down Expand Up @@ -160,7 +247,7 @@ mod test {
.join("test")
.join("rtree_query.json");
let query_str = fs::read_to_string(query_filepath).unwrap();
let rtree_plugin = RTreePlugin::from_file(&vertices_filepath).unwrap();
let rtree_plugin = RTreePlugin::new(&vertices_filepath, None, None).unwrap();
let query: serde_json::Value = serde_json::from_str(&query_str).unwrap();
let processed_query = rtree_plugin.process(&query).unwrap();

Expand Down
4 changes: 2 additions & 2 deletions rust/routee-compass/src/plugin/plugin_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ pub enum PluginError {
InputError(String),
#[error("error with building plugin")]
BuildError,
#[error("nearest vertex not found for coord {0:?}")]
NearestVertexNotFound(Coord),
#[error("{0}")]
PluginFailed(String),
#[error("unable to read file {0} due to {1}")]
FileReadError(PathBuf, String),
#[error(transparent)]
Expand Down

0 comments on commit ab15523

Please sign in to comment.