Implement routability more or less completely
This commit is contained in:
parent
4f36d2dce1
commit
bc56a1f8e6
@ -113,12 +113,12 @@ Feature: Car - Destination only, no passing through
|
||||
Scenario: Car - Routing around a way that becomes destination only
|
||||
Given the node map
|
||||
"""
|
||||
a---c---b
|
||||
+ \
|
||||
+ |
|
||||
d |
|
||||
1 |
|
||||
\___e
|
||||
a---c---b
|
||||
+ \
|
||||
+ |
|
||||
d |
|
||||
1 |
|
||||
\___e
|
||||
"""
|
||||
|
||||
And the ways
|
||||
@ -136,12 +136,12 @@ Feature: Car - Destination only, no passing through
|
||||
Scenario: Car - Routing through a parking lot tagged access=destination,service
|
||||
Given the node map
|
||||
"""
|
||||
a----c++++b+++g------h---i
|
||||
| + + + /
|
||||
| + + + /
|
||||
| + + + /
|
||||
| d++++e+f /
|
||||
z--------------y
|
||||
a----c++++b+++g------h---i
|
||||
| + + + /
|
||||
| + + + /
|
||||
| + + + /
|
||||
| d++++e+f /
|
||||
z--------------y
|
||||
"""
|
||||
|
||||
And the ways
|
||||
@ -165,12 +165,12 @@ Feature: Car - Destination only, no passing through
|
||||
Given a grid size of 20 meters
|
||||
Given the node map
|
||||
"""
|
||||
a---c---b
|
||||
:
|
||||
x
|
||||
:
|
||||
d
|
||||
\__e
|
||||
a---c---b
|
||||
:
|
||||
x
|
||||
:
|
||||
d
|
||||
\__e
|
||||
"""
|
||||
|
||||
And the ways
|
||||
|
17
tests/common/comparison.rs
Normal file
17
tests/common/comparison.rs
Normal file
@ -0,0 +1,17 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Offset {
|
||||
Absolute(f64),
|
||||
Percentage(f64),
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::extract_number_and_offset;
|
||||
|
||||
// #[test]
|
||||
// fn extract_number_and_offset() {
|
||||
// let (value, result) = extract_number_and_offset("m", "300 +- 1m");
|
||||
// assert_eq!(value, 300.);
|
||||
// assert_eq!(offset, 1);
|
||||
// }
|
||||
// }
|
@ -1,17 +1,24 @@
|
||||
pub fn approx_equal(a: f32, b: f32, dp: u8) -> bool {
|
||||
let p = 10f32.powi(-(dp as i32));
|
||||
use super::comparison::Offset;
|
||||
|
||||
pub fn approx_equal(a: f64, b: f64, dp: u8) -> bool {
|
||||
let p = 10f64.powi(-(dp as i32));
|
||||
(a - b).abs() < p
|
||||
}
|
||||
|
||||
pub fn aprox_equal_within_percentage_range(actual: f64, expectation: f64, percentage: f64) -> bool {
|
||||
fn aprox_equal_within_percentage_range(actual: f64, expectation: f64, percentage: f64) -> bool {
|
||||
assert!(percentage.is_sign_positive() && percentage <= 100.);
|
||||
let factor = 0.01 * percentage as f64;
|
||||
actual >= expectation - (factor * expectation) && actual <= expectation + (factor * expectation)
|
||||
}
|
||||
|
||||
pub fn approx_equal_within_offset_range(actual: f64, expectation: f64, offset: f64) -> bool {
|
||||
fn approx_equal_within_offset_range(actual: f64, expectation: f64, offset: f64) -> bool {
|
||||
assert!(offset >= 0., "offset must be positive");
|
||||
actual >= expectation - offset && actual <= expectation + offset
|
||||
}
|
||||
|
||||
// TODO: test coverage
|
||||
pub fn approximate_within_range(actual: f64, expectation: f64, offset: &Offset) -> bool{
|
||||
match offset {
|
||||
Offset::Absolute(a) => approx_equal_within_offset_range(actual, expectation, *a),
|
||||
Offset::Percentage(p) => aprox_equal_within_percentage_range(actual, expectation, *p),
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,6 @@ use serde::Deserialize;
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize)]
|
||||
pub struct Location {
|
||||
// Note: The order is important since we derive Deserialize
|
||||
pub longitude: f32,
|
||||
pub latitude: f32,
|
||||
pub longitude: f64,
|
||||
pub latitude: f64,
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
extern crate flatbuffers;
|
||||
|
||||
pub mod cli_arguments;
|
||||
pub mod comparison;
|
||||
pub mod dot_writer;
|
||||
pub mod f64_utils;
|
||||
pub mod file_util;
|
||||
|
@ -62,8 +62,8 @@ impl NearestResponse {
|
||||
let hint = wp.hint().expect("hint is missing").to_string();
|
||||
let location = wp.location().expect("waypoint must have a location");
|
||||
let location = Location {
|
||||
latitude: location.latitude(),
|
||||
longitude: location.longitude(),
|
||||
latitude: location.latitude() as f64,
|
||||
longitude: location.longitude() as f64,
|
||||
};
|
||||
let nodes = wp.nodes().expect("waypoint mus have nodes");
|
||||
let nodes = Some(vec![nodes.first(), nodes.second()]);
|
||||
|
@ -16,9 +16,12 @@ pub struct OSMNode {
|
||||
}
|
||||
|
||||
impl OSMNode {
|
||||
// pub fn add_tag(&mut self, key: &str, value: &str) {
|
||||
// self.tags.insert(key.into(), value.into());
|
||||
// }
|
||||
pub fn add_tag(&mut self, key: &str, value: &str) {
|
||||
if key.is_empty() || value.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.tags.insert(key.into(), value.into());
|
||||
}
|
||||
|
||||
// pub fn set_id_(&mut self, id: u64) {
|
||||
// self.id = id;
|
||||
@ -68,6 +71,13 @@ impl OSMWay {
|
||||
// self.tags = tags;
|
||||
// }
|
||||
|
||||
pub fn add_tag(&mut self, key: &str, value: &str) {
|
||||
if key.is_empty() || value.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.tags.insert(key.into(), value.into());
|
||||
}
|
||||
|
||||
pub fn to_xml(&self) -> XMLElement {
|
||||
let mut way = XMLElement::new("way");
|
||||
way.add_attribute("id", &self.id.to_string());
|
||||
|
@ -4,7 +4,7 @@ use xml_builder::{XMLBuilder, XMLElement, XMLVersion};
|
||||
// TODO: better error handling in XML creation
|
||||
#[derive(Debug, Default)]
|
||||
pub struct OSMDb {
|
||||
nodes: Vec<(char, OSMNode)>,
|
||||
nodes: Vec<(String, OSMNode)>,
|
||||
ways: Vec<OSMWay>,
|
||||
// relations: Vec<OSMRelation>,
|
||||
}
|
||||
@ -12,14 +12,14 @@ pub struct OSMDb {
|
||||
impl OSMDb {
|
||||
pub fn add_node(&mut self, node: OSMNode) {
|
||||
let name = node.tags.get("name").unwrap();
|
||||
assert!(
|
||||
name.len() == 1,
|
||||
"name needs to be of length 1, but was \"{name}\""
|
||||
);
|
||||
self.nodes.push((name.chars().next().unwrap(), node));
|
||||
// assert!(
|
||||
// name.len() == 1,
|
||||
// "name needs to be of length 1, but was \"{name}\""
|
||||
// );
|
||||
self.nodes.push((name.clone(), node));
|
||||
}
|
||||
|
||||
pub fn find_node(&self, search_name: char) -> Option<&(char, OSMNode)> {
|
||||
pub fn find_node(&self, search_name: String) -> Option<&(String, OSMNode)> {
|
||||
// TODO: this is a linear search.
|
||||
self.nodes.iter().find(|(name, _node)| search_name == *name)
|
||||
}
|
||||
|
17
tests/common/osrm_error.rs
Normal file
17
tests/common/osrm_error.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct OSRMError {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl OSRMError {
|
||||
pub fn from_json_reader(reader: impl std::io::Read) -> Self {
|
||||
let response = match serde_json::from_reader::<_, Self>(reader) {
|
||||
Ok(response) => response,
|
||||
Err(e) => panic!("parsing error {e}"),
|
||||
};
|
||||
response
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
nearest_response::NearestResponse, osm::OSMNode, osm_db::OSMDb, osrm_error::OSRMError,
|
||||
nearest_response::NearestResponse, osm::{OSMNode, OSMWay}, osm_db::OSMDb, osrm_error::OSRMError,
|
||||
route_response::RouteResponse,
|
||||
};
|
||||
use crate::{common::local_task::LocalTask, Location};
|
||||
@ -10,18 +10,18 @@ use reqwest::StatusCode;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{create_dir_all, File},
|
||||
io::{Read, Write},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
time::Duration,
|
||||
};
|
||||
// use ureq::Error;
|
||||
|
||||
const DEFAULT_ORIGIN: Location = Location {
|
||||
longitude: 1.0f32,
|
||||
latitude: 1.0f32,
|
||||
longitude: 1.0f64,
|
||||
latitude: 1.0f64,
|
||||
};
|
||||
const DEFAULT_GRID_SIZE: f32 = 100.;
|
||||
const WAY_SPACING: f32 = 100.;
|
||||
const DEFAULT_GRID_SIZE: f64 = 100.;
|
||||
const WAY_SPACING: f64 = 100.;
|
||||
const DEFAULT_PROFILE: &str = "bicycle";
|
||||
|
||||
#[derive(Debug, World)]
|
||||
pub struct OSRMWorld {
|
||||
@ -42,13 +42,12 @@ pub struct OSRMWorld {
|
||||
pub query_options: HashMap<String, String>,
|
||||
pub request_string: Option<String>,
|
||||
|
||||
pub grid_size: f32,
|
||||
pub grid_size: f64,
|
||||
pub origin: Location,
|
||||
pub way_spacing: f32,
|
||||
pub way_spacing: f64,
|
||||
|
||||
task: LocalTask,
|
||||
client: reqwest::blocking::Client,
|
||||
// agent: ureq::Agent,
|
||||
}
|
||||
|
||||
impl Default for OSRMWorld {
|
||||
@ -59,7 +58,7 @@ impl Default for OSRMWorld {
|
||||
feature_digest: Default::default(),
|
||||
osrm_digest: Default::default(),
|
||||
osm_id: Default::default(),
|
||||
profile: Default::default(),
|
||||
profile: DEFAULT_PROFILE.into(),
|
||||
known_osm_nodes: Default::default(),
|
||||
known_locations: Default::default(),
|
||||
osm_db: Default::default(),
|
||||
@ -69,7 +68,7 @@ impl Default for OSRMWorld {
|
||||
// default parameters // TODO: check if necessary
|
||||
("steps".into(), "true".into()),
|
||||
("alternatives".into(), "false".into()),
|
||||
("annotations".into(), "true".into()),
|
||||
// ("annotations".into(), "true".into()),
|
||||
]),
|
||||
request_string: Default::default(),
|
||||
|
||||
@ -77,10 +76,6 @@ impl Default for OSRMWorld {
|
||||
origin: DEFAULT_ORIGIN,
|
||||
way_spacing: WAY_SPACING,
|
||||
task: LocalTask::default(),
|
||||
// agent: ureq::AgentBuilder::new()
|
||||
// .timeout_read(Duration::from_secs(5))
|
||||
// .timeout_write(Duration::from_secs(5))
|
||||
// .build(),
|
||||
client: reqwest::blocking::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.no_proxy()
|
||||
@ -166,6 +161,14 @@ impl OSRMWorld {
|
||||
self.osm_db.add_node(node);
|
||||
}
|
||||
|
||||
pub fn add_osm_way(&mut self, way: OSMWay) {
|
||||
way.nodes.iter().for_each(|node| {
|
||||
self.osm_db.add_node(node.clone());
|
||||
});
|
||||
|
||||
self.osm_db.add_way(way);
|
||||
}
|
||||
|
||||
pub fn get_location(&self, name: char) -> Location {
|
||||
*match name {
|
||||
// TODO: move lookup to world
|
||||
@ -264,20 +267,6 @@ impl OSRMWorld {
|
||||
return Err((status.as_u16(), OSRMError::from_json_reader(bytes)));
|
||||
}
|
||||
}
|
||||
|
||||
// match call {
|
||||
// Ok(response) => {
|
||||
// let response = match self.request_with_flatbuffers {
|
||||
// true => NearestResponse::from_flatbuffer(response.into_reader()),
|
||||
// false => NearestResponse::from_json_reader(response.into_reader()),
|
||||
// };
|
||||
// Ok((200u16, response))
|
||||
// }
|
||||
// Err(Error::Status(code, response)) => {
|
||||
// return Err((code, OSRMError::from_json_reader(response.into_reader())));
|
||||
// }
|
||||
// Err(e) => panic!("http error: {e}"),
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn route(
|
||||
@ -295,7 +284,7 @@ impl OSRMWorld {
|
||||
let url = match &self.request_string {
|
||||
None => {
|
||||
let mut url = format!(
|
||||
"http://localhost:5000/route/v1/{}/{waypoint_string}",
|
||||
"http://127.0.0.1:5000/route/v1/{}/{waypoint_string}",
|
||||
self.profile,
|
||||
);
|
||||
if self.request_with_flatbuffers {
|
||||
@ -314,19 +303,11 @@ impl OSRMWorld {
|
||||
url
|
||||
}
|
||||
Some(request_string) => {
|
||||
let temp = format!("http://localhost:5000/{}", request_string);
|
||||
// if request_string == "?" {
|
||||
// panic!("s: {temp}");
|
||||
// }
|
||||
let temp = format!("http://127.0.0.1:5000/{}", request_string);
|
||||
temp
|
||||
}
|
||||
};
|
||||
// println!("url: {url}");
|
||||
// let request = self.agent.get(&url);
|
||||
// if url.ends_with("?") {
|
||||
// // request = request.query("", "");
|
||||
// }
|
||||
// let call = request.call();
|
||||
let response = match self.client.get(url).send() {
|
||||
Ok(response) => response,
|
||||
Err(e) => panic!("http error: {e}"),
|
||||
@ -347,25 +328,5 @@ impl OSRMWorld {
|
||||
return Err((status.as_u16(), OSRMError::from_json_reader(bytes)));
|
||||
}
|
||||
}
|
||||
|
||||
// match call {
|
||||
// Ok(response) => {
|
||||
// let text = std::io::read_to_string(response.into_reader()).unwrap();
|
||||
// let response = match self.request_with_flatbuffers {
|
||||
// true => unimplemented!("RouteResponse::from_flatbuffer(body)"),
|
||||
// false => RouteResponse::from_string(&text),
|
||||
// };
|
||||
// Ok((200u16, response))
|
||||
// }
|
||||
// Err(Error::Status(code, response)) => {
|
||||
// let result = Err((code, OSRMError::from_json_reader(response.into_reader())));
|
||||
// if url.ends_with("?") {
|
||||
// panic!("{url} {result:?}");
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
// Err(e) => panic!("http error: {e}"),
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
use std::default;
|
||||
|
||||
use serde::Deserialize;
|
||||
@ -6,11 +7,12 @@ use super::{location::Location, nearest_response::Waypoint};
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
pub struct Maneuver {
|
||||
pub bearing_after: f64,
|
||||
pub bearing_before: f64,
|
||||
pub bearing_after: u64,
|
||||
pub bearing_before: u64,
|
||||
pub location: Location,
|
||||
pub modifier: Option<String>, // TODO: should be an enum
|
||||
pub r#type: String, // TODO: should be an enum
|
||||
pub exit: Option<u64>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@ -29,6 +31,15 @@ impl Default for Geometry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
pub struct Intersection {
|
||||
pub r#in: Option<u64>,
|
||||
pub out: Option<u64>,
|
||||
pub entry: Vec<bool>,
|
||||
pub bearings: Vec<u64>,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
pub struct Step {
|
||||
pub geometry: Geometry,
|
||||
@ -36,9 +47,11 @@ pub struct Step {
|
||||
pub maneuver: Maneuver,
|
||||
pub name: String,
|
||||
pub pronunciation: Option<String>,
|
||||
pub rotary_name: Option<String>,
|
||||
pub r#ref: Option<String>,
|
||||
pub duration: f64,
|
||||
pub distance: f64,
|
||||
pub intersections: Vec<Intersection>,
|
||||
}
|
||||
|
||||
// #[derive(Deserialize, Debug)]
|
||||
@ -92,15 +105,3 @@ impl RouteResponse {
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::RouteResponse;
|
||||
|
||||
// #[test]
|
||||
// fn parse_geojson() {
|
||||
// let input = r#"{"code":"Ok","routes":[{"geometry":{"coordinates":[[1.00009,1],[1.000269,1]],"type":"LineString"},"weight":1.9,"duration":1.9,"legs":[{"annotation":{"speed":[10.5],"weight":[1.9],"nodes":[1,2],"duration":[1.9],"distance":[19.92332315]},"summary":"abc","weight":1.9,"duration":1.9,"steps":[{"geometry":{"coordinates":[[1.00009,1],[1.000269,1]],"type":"LineString"},"maneuver":{"location":[1.00009,1],"bearing_after":90,"bearing_before":0,"modifier":"right","type":"depart"},"mode":"driving","name":"abc","intersections":[{"out":0,"entry":[true],"bearings":[90],"location":[1.00009,1]}],"driving_side":"right","weight":1.9,"duration":1.9,"distance":19.9},{"geometry":{"coordinates":[[1.000269,1],[1.000269,1]],"type":"LineString"},"maneuver":{"location":[1.000269,1],"bearing_after":0,"bearing_before":90,"modifier":"right","type":"arrive"},"mode":"driving","name":"abc","intersections":[{"in":0,"entry":[true],"bearings":[270],"location":[1.000269,1]}],"driving_side":"right","weight":0,"duration":0,"distance":0}],"distance":19.9}],"weight_name":"duration","distance":19.9}],"waypoints":[{"name":"abc","hint":"AAAAgAEAAIAKAAAAHgAAAAAAAAAoAAAA6kYgQWyG70EAAAAA6kYgQgoAAAAeAAAAAAAAACgAAAABAACAmkIPAEBCDwCaQg8Ai0EPAAAArwUAAAAA","distance":20.01400211,"location":[1.00009,1]},{"name":"abc","hint":"AAAAgAEAAIAdAAAACwAAAAAAAAAoAAAAbIbvQepGIEEAAAAA6kYgQh0AAAALAAAAAAAAACgAAAABAACATUMPAEBCDwBNQw8Ai0EPAAAArwUAAAAA","distance":20.01400211,"location":[1.000269,1]}]} "#;
|
||||
// let result = RouteResponse::from_string(&input);
|
||||
|
||||
// }
|
||||
// }
|
||||
|
@ -4,18 +4,18 @@ use cheap_ruler::CheapRuler;
|
||||
use clap::Parser;
|
||||
use common::{
|
||||
cli_arguments::Args,
|
||||
comparison::Offset,
|
||||
dot_writer::DotWriter,
|
||||
f64_utils::{
|
||||
approx_equal, approx_equal_within_offset_range, aprox_equal_within_percentage_range,
|
||||
},
|
||||
f64_utils::{approx_equal, approximate_within_range},
|
||||
hash_util::md5_of_osrm_executables,
|
||||
location::Location,
|
||||
osm::OSMWay,
|
||||
osm::{OSMNode, OSMWay},
|
||||
osrm_world::OSRMWorld,
|
||||
route_response::{self, RouteResponse},
|
||||
};
|
||||
use core::panic;
|
||||
use cucumber::{
|
||||
codegen::ParametersProvider,
|
||||
gherkin::{Step, Table},
|
||||
given, then, when,
|
||||
writer::summarize,
|
||||
@ -25,17 +25,16 @@ use futures::{future, FutureExt};
|
||||
use geo_types::Point;
|
||||
use log::debug;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
iter::zip,
|
||||
collections::{HashMap, HashSet}, fmt::format, iter::zip, process::ExitCode, result
|
||||
};
|
||||
|
||||
fn offset_origin_by(dx: f32, dy: f32, origin: Location, grid_size: f32) -> Location {
|
||||
fn offset_origin_by(dx: f64, dy: f64, origin: Location, grid_size: f64) -> Location {
|
||||
let ruler = CheapRuler::new(origin.latitude, cheap_ruler::DistanceUnit::Meters);
|
||||
let loc = ruler.offset(
|
||||
&Point::new(origin.longitude, origin.latitude),
|
||||
dx * grid_size,
|
||||
dy * grid_size,
|
||||
); //TODO: needs to be world's gridSize, not the local one
|
||||
);
|
||||
Location {
|
||||
latitude: loc.y(),
|
||||
longitude: loc.x(),
|
||||
@ -89,8 +88,12 @@ fn set_node_locations(world: &mut OSRMWorld, step: &Step) {
|
||||
let lon = &row[header_lookup["lon"]];
|
||||
let lat = &row[header_lookup["lat"]];
|
||||
let location = Location {
|
||||
latitude: lat.parse::<f32>().expect("lat {lat} needs to be a f64"),
|
||||
longitude: lon.parse::<f32>().expect("lon {lon} needs to be a f64"),
|
||||
latitude: lat
|
||||
.parse()
|
||||
.expect("lat {lat} needs to be a floating point number"),
|
||||
longitude: lon
|
||||
.parse()
|
||||
.expect("lon {lon} needs to be a floating point number"),
|
||||
};
|
||||
let id = match header_lookup.get("id") {
|
||||
Some(index) => {
|
||||
@ -117,22 +120,25 @@ fn set_node_map(world: &mut OSRMWorld, step: &Step) {
|
||||
.split('\n')
|
||||
.enumerate()
|
||||
.for_each(|(row_index, row)| {
|
||||
let row_index = row_index - 1;
|
||||
row.chars()
|
||||
.enumerate()
|
||||
.filter(|(_column_index, charater)| *charater != ' ')
|
||||
.filter(|(_column_index, character)| {
|
||||
*character >= '0' && *character <= '9'
|
||||
|| *character >= 'a' && *character <= 'z'
|
||||
})
|
||||
.for_each(|(column_index, name)| {
|
||||
// This ports the logic from previous implementations.
|
||||
// This ports the logic from previous JS/Ruby implementations.
|
||||
let location = offset_origin_by(
|
||||
column_index as f32 * 0.5,
|
||||
-(row_index as f32 - 1.),
|
||||
column_index as f64 * 0.5,
|
||||
-(row_index as f64),
|
||||
world.origin,
|
||||
world.grid_size,
|
||||
);
|
||||
match name {
|
||||
'0'...'9' => world.add_location(name, location),
|
||||
'a'...'z' => world.add_osm_node(name, location, None),
|
||||
_ => {} // TODO: unreachable!("node name not in [0..9][a..z]: {docstring}"),
|
||||
// tests contain random characters.
|
||||
_ => unreachable!("node name not in [0..9][a..z]: {docstring}"),
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -147,7 +153,7 @@ fn extra_parameters(world: &mut OSRMWorld, parameters: String) {
|
||||
}
|
||||
|
||||
#[given(expr = "a grid size of {float} meters")]
|
||||
fn set_grid_size(world: &mut OSRMWorld, meters: f32) {
|
||||
fn set_grid_size(world: &mut OSRMWorld, meters: f64) {
|
||||
world.grid_size = meters;
|
||||
}
|
||||
|
||||
@ -181,7 +187,7 @@ fn set_ways(world: &mut OSRMWorld, step: &Step) {
|
||||
// TODO: this check is probably not necessary since it is also checked below implicitly
|
||||
panic!("referenced unknown node {name} in way {token}");
|
||||
}
|
||||
if let Some((_, node)) = world.osm_db.find_node(name) {
|
||||
if let Some((_, node)) = world.osm_db.find_node(name.to_string()) {
|
||||
way.add_node(node.clone());
|
||||
} else {
|
||||
panic!("node is known, but not found in osm_db");
|
||||
@ -299,13 +305,7 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
|
||||
#[then(expr = "routability should be")]
|
||||
fn routability(world: &mut OSRMWorld, step: &Step) {
|
||||
world.write_osm_file();
|
||||
world.extract_osm_file();
|
||||
// TODO: preprocess
|
||||
|
||||
let (header, test_cases) = parse_table_from_steps(&step.table.as_ref());
|
||||
// TODO: rename forw/backw to forw/backw_speed
|
||||
let supported_headers = HashSet::<_>::from([
|
||||
let tested_headers = HashSet::<_>::from([
|
||||
"forw",
|
||||
"backw",
|
||||
"bothw",
|
||||
@ -313,9 +313,67 @@ fn routability(world: &mut OSRMWorld, step: &Step) {
|
||||
"backw_rate",
|
||||
"bothw_rate",
|
||||
]);
|
||||
if 0 == header
|
||||
let (headers, test_cases) = parse_table_from_steps(&step.table.as_ref());
|
||||
// add all non-empty, non-test headers as key=value pairs to OSM data
|
||||
test_cases
|
||||
.iter()
|
||||
.filter(|title| supported_headers.contains(title.as_str()))
|
||||
.enumerate()
|
||||
.for_each(|(index, test_case)| {
|
||||
let mut way = OSMWay {
|
||||
id: world.make_osm_id(),
|
||||
..Default::default()
|
||||
};
|
||||
way.add_tag("highway", "primary");
|
||||
way.add_tag("name", &format!("w{index}"));
|
||||
let source = offset_origin_by(
|
||||
world.way_spacing * index as f64,
|
||||
0.,
|
||||
world.origin,
|
||||
world.grid_size,
|
||||
);
|
||||
let mut source_node = OSMNode {
|
||||
id: world.make_osm_id(),
|
||||
location: source,
|
||||
..Default::default()
|
||||
};
|
||||
source_node.add_tag("name", &format!("a{index}"));
|
||||
way.add_node(source_node);
|
||||
|
||||
let target = offset_origin_by(
|
||||
4. + world.way_spacing * index as f64,
|
||||
0.,
|
||||
world.origin,
|
||||
world.grid_size,
|
||||
);
|
||||
let mut target_node = OSMNode {
|
||||
id: world.make_osm_id(),
|
||||
location: target,
|
||||
..Default::default()
|
||||
};
|
||||
target_node.add_tag("name", &format!("e{index}"));
|
||||
way.add_node(target_node);
|
||||
|
||||
test_case
|
||||
.iter()
|
||||
.filter(|(key, _value)| !tested_headers.iter().any(|header| header == key))
|
||||
.for_each(|(key, value)| {
|
||||
if key != "#" {
|
||||
// ignore comments
|
||||
way.add_tag(key, value);
|
||||
}
|
||||
});
|
||||
world.add_osm_way(way);
|
||||
});
|
||||
|
||||
world.write_osm_file();
|
||||
world.extract_osm_file();
|
||||
// TODO: preprocess
|
||||
|
||||
// TODO: rename forw/backw to forw/backw_speed (comment from JS implementation)
|
||||
|
||||
if 0 == headers
|
||||
.iter()
|
||||
.filter(|title| tested_headers.contains(title.as_str()))
|
||||
.count()
|
||||
{
|
||||
panic!(
|
||||
@ -328,96 +386,234 @@ fn routability(world: &mut OSRMWorld, step: &Step) {
|
||||
.enumerate()
|
||||
.for_each(|(index, test_case)| {
|
||||
let source = offset_origin_by(
|
||||
1. + world.way_spacing * index as f32,
|
||||
1. + world.way_spacing * index as f64,
|
||||
0.,
|
||||
world.origin,
|
||||
world.grid_size,
|
||||
);
|
||||
let target = offset_origin_by(
|
||||
3. + world.way_spacing * index as f32,
|
||||
3. + world.way_spacing * index as f64,
|
||||
0.,
|
||||
world.origin,
|
||||
world.grid_size,
|
||||
);
|
||||
|
||||
let expected_summary = format!("w{index}");
|
||||
|
||||
test_case
|
||||
.iter()
|
||||
.filter(|(title, _)| supported_headers.contains(title.as_str()))
|
||||
.filter(|(title, _)| tested_headers.contains(title.as_str()))
|
||||
.for_each(|(title, expectation)| {
|
||||
let route_results = vec![world.route(&vec![source, target])
|
||||
,world.route(&vec![target, source])];
|
||||
let forward = title.starts_with("forw");
|
||||
let route_result = match forward {
|
||||
true => world.route(&vec![source, target]),
|
||||
false => world.route(&vec![target, source]),
|
||||
true => &route_results[0],
|
||||
false => &route_results[1],
|
||||
};
|
||||
let (_, response) = route_result
|
||||
.as_ref()
|
||||
.expect("osrm-routed returned an unexpected error");
|
||||
if expectation.is_empty() {
|
||||
assert!(
|
||||
response.routes.is_empty()
|
||||
|| response.routes.first().unwrap().distance == 0.,
|
||||
"no route expected when result column {title} is unset"
|
||||
);
|
||||
} else if expectation.contains("km/h") {
|
||||
assert!(
|
||||
!response.routes.is_empty(),
|
||||
"route expected when result column is set"
|
||||
);
|
||||
match title.as_str() {
|
||||
"forw" | "backw" => {
|
||||
match expectation.as_str() {
|
||||
"" => {
|
||||
assert!(
|
||||
route_result.is_err() ||
|
||||
extract_summary_from_route_response(&route_result.as_ref().unwrap().1) != expected_summary,
|
||||
// || response.routes.first().unwrap().distance == 0.,
|
||||
"no route expected when result column {title} is unset"
|
||||
);
|
||||
}
|
||||
"x" => {
|
||||
let (_, response) = route_result
|
||||
.as_ref()
|
||||
.expect("osrm-routed returned an unexpected error");
|
||||
assert!(
|
||||
!response.routes.is_empty()
|
||||
&& response.routes.first().unwrap().distance >= 0.,
|
||||
"no route expected when result column {title} is set to {expectation}"
|
||||
);
|
||||
}
|
||||
_ if expectation.contains("km/h") => {
|
||||
assert!(route_result.is_ok());
|
||||
let (_, response) = route_result.as_ref().unwrap();
|
||||
assert!(
|
||||
!response.routes.is_empty(),
|
||||
"route expected when result column is set"
|
||||
);
|
||||
|
||||
let (expected_speed, offset) =
|
||||
extract_number_and_offset("km/h", expectation);
|
||||
let route = response.routes.first().unwrap();
|
||||
let actual_speed = route.distance / route.duration * 3.6;
|
||||
assert!(
|
||||
aprox_equal_within_percentage_range(
|
||||
actual_speed,
|
||||
expected_speed,
|
||||
offset
|
||||
),
|
||||
"{actual_speed} and {expected_speed} differ by more than {offset}"
|
||||
);
|
||||
} else if title.ends_with("_rate") {
|
||||
assert!(!response.routes.is_empty());
|
||||
let expected_rate = expectation
|
||||
.parse::<f64>()
|
||||
.expect("rate needs to be a number");
|
||||
let route = response.routes.first().unwrap();
|
||||
let actual_rate = route.distance / route.weight;
|
||||
assert!(
|
||||
aprox_equal_within_percentage_range(actual_rate, expected_rate, 1.),
|
||||
"{actual_rate} and {expected_rate} differ by more than 1%"
|
||||
);
|
||||
} else {
|
||||
unimplemented!("{title} = {expectation}");
|
||||
let (expected_speed, offset) =
|
||||
extract_number_and_offset("km/h", expectation);
|
||||
let route = response.routes.first().unwrap();
|
||||
let actual_speed = route.distance / route.duration * 3.6;
|
||||
assert!(
|
||||
approximate_within_range(
|
||||
actual_speed,
|
||||
expected_speed,
|
||||
&offset
|
||||
),
|
||||
"{actual_speed} and {expected_speed} differ by more than {offset:?}"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
let (_, response) = route_result
|
||||
.as_ref()
|
||||
.expect("osrm-routed returned an unexpected error");
|
||||
let mode = extract_mode_string_from_route_response(response);
|
||||
assert_eq!(&mode, expectation, "failed: {test_case:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
"forw_rate" | "backw_rate" => {
|
||||
assert!(route_result.is_ok());
|
||||
let (_, response) = route_result.as_ref().unwrap();
|
||||
assert!(!response.routes.is_empty());
|
||||
let expected_rate = expectation
|
||||
.parse::<f64>()
|
||||
.expect("rate needs to be a number");
|
||||
let route = response.routes.first().unwrap();
|
||||
let actual_rate = route.distance / route.weight;
|
||||
assert!(
|
||||
approximate_within_range(actual_rate, expected_rate, &Offset::Percentage(1.)),
|
||||
"{actual_rate} and {expected_rate} differ by more than 1%"
|
||||
);
|
||||
}
|
||||
"bothw" => {
|
||||
match expectation.as_str() {
|
||||
"x" => {
|
||||
for result in &route_results {
|
||||
let (_, response) = result
|
||||
.as_ref()
|
||||
.expect("osrm-routed returned an unexpected error");
|
||||
assert!(
|
||||
!response.routes.is_empty()
|
||||
&& response.routes.first().unwrap().distance >= 0.,
|
||||
"no forward route when result column {title}={expectation}"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ if expectation.contains("km/h") => {
|
||||
let (expected_speed, offset) =
|
||||
extract_number_and_offset("km/h", expectation);
|
||||
for result in &route_results {
|
||||
assert!(result.is_ok());
|
||||
let (_, response) = result.as_ref().unwrap();
|
||||
assert!(
|
||||
!response.routes.is_empty(),
|
||||
"route expected when result column is set"
|
||||
);
|
||||
let route = response.routes.first().unwrap();
|
||||
let actual_speed = route.distance / route.duration * 3.6;
|
||||
assert!(
|
||||
approximate_within_range(
|
||||
actual_speed,
|
||||
expected_speed,
|
||||
&offset
|
||||
),
|
||||
"{actual_speed} and {expected_speed} differ by more than {offset:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
// match expectation against mode
|
||||
for result in &route_results {
|
||||
match result {
|
||||
Ok((_,response)) => {
|
||||
let mode = extract_mode_string_from_route_response(response);
|
||||
let summary = extract_summary_from_route_response(response);
|
||||
if summary != expected_summary {
|
||||
assert!(expectation.is_empty());
|
||||
} else {
|
||||
assert_eq!(&mode, expectation, "failed: {source:?} -> {target:?}");
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
assert!(expectation.is_empty());
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
_ => {
|
||||
unreachable!("{title} = {expectation}");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// unimplemented!("{test_cases:#?}");
|
||||
}
|
||||
|
||||
fn extract_number_and_offset(unit: &str, expectation: &str) -> (f64, f64) {
|
||||
fn extract_summary_from_route_response(response: &RouteResponse) -> String {
|
||||
response
|
||||
.routes
|
||||
.first()
|
||||
.unwrap()
|
||||
.legs
|
||||
.first()
|
||||
.unwrap()
|
||||
.summary
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn extract_mode_string_from_route_response(response: &RouteResponse) -> String {
|
||||
// From JS test suite:
|
||||
// use the mode of the first step of the route
|
||||
// for routability table test, we can assume the mode is the same throughout the route,
|
||||
// since the route is just a single way
|
||||
assert!(
|
||||
!response.routes.is_empty(),
|
||||
"route expected when extracting mode"
|
||||
);
|
||||
response
|
||||
.routes
|
||||
.first()
|
||||
.unwrap()
|
||||
.legs
|
||||
.first()
|
||||
.unwrap()
|
||||
.steps
|
||||
.first()
|
||||
.unwrap()
|
||||
.mode
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn extract_number_and_offset(unit: &str, expectation: &str) -> (f64, Offset) {
|
||||
let expectation = expectation.replace(unit, "");
|
||||
let delimiter = if expectation.contains("+-") {
|
||||
"+-"
|
||||
} else if expectation.contains("~") {
|
||||
"~"
|
||||
} else {
|
||||
"unknown"
|
||||
};
|
||||
let tokens: Vec<_> = expectation
|
||||
.split(unit)
|
||||
.split(delimiter)
|
||||
.map(|token| token.trim())
|
||||
.filter(|token| !token.is_empty())
|
||||
.collect();
|
||||
// println!("{tokens:?}");
|
||||
let number = tokens[0]
|
||||
.parse::<f64>()
|
||||
.expect("{expectation} needs to define a speed");
|
||||
.expect(&format!("'{}' needs to denote a parseablespeed", tokens[0]));
|
||||
let offset = match tokens.len() {
|
||||
1 => 5., // TODO: the JS fuzzy matcher has a default margin of 5% for absolute comparsions. This is imprecise
|
||||
2 => tokens[1]
|
||||
.replace("~", "")
|
||||
.replace("+-", "")
|
||||
.replace("~", "")
|
||||
.replace("+-", "")
|
||||
.replace("%", "")
|
||||
.trim()
|
||||
.parse()
|
||||
.expect(&format!("{} needs to specify a number", tokens[1])),
|
||||
_ => unreachable!("expectations can't be parsed"),
|
||||
};
|
||||
(number, offset)
|
||||
if expectation.ends_with("%") {
|
||||
return (number, Offset::Percentage(offset));
|
||||
}
|
||||
(number, Offset::Absolute(offset))
|
||||
}
|
||||
|
||||
fn extract_number_vector_and_offset(unit: &str, expectation: &str) -> (Vec<f64>, u8) {
|
||||
fn extract_number_vector_and_offset(unit: &str, expectation: &str) -> (Vec<f64>, Offset) {
|
||||
let expectation = expectation.replace(",", "");
|
||||
let tokens: Vec<_> = expectation
|
||||
.split(unit)
|
||||
@ -446,7 +642,10 @@ fn extract_number_vector_and_offset(unit: &str, expectation: &str) -> (Vec<f64>,
|
||||
.expect(&format!("{} needs to specify a number", tokens[1])),
|
||||
// _ => unreachable!("expectations can't be parsed"),
|
||||
};
|
||||
(numbers, offset)
|
||||
if expectation.ends_with("%") {
|
||||
return (numbers, Offset::Percentage(offset.into()));
|
||||
}
|
||||
(numbers, Offset::Absolute(offset.into()))
|
||||
}
|
||||
|
||||
pub enum WaypointsOrLocation {
|
||||
@ -538,7 +737,7 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
.expect("to node name is one char long"),
|
||||
);
|
||||
vec![from_location, to_location]
|
||||
},
|
||||
}
|
||||
WaypointsOrLocation::Undefined => {
|
||||
world.request_string = test_case.get("request").cloned();
|
||||
// println!("setting request to: {:?}", world.request_string);
|
||||
@ -554,7 +753,6 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
}
|
||||
|
||||
let route_result = world.route(&waypoints);
|
||||
|
||||
test_case
|
||||
.iter()
|
||||
.map(|(column_title, expectation)| (column_title.as_str(), expectation.as_str()))
|
||||
@ -566,13 +764,14 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
Err(_) => &RouteResponse::default(),
|
||||
};
|
||||
let route = if expectation.is_empty() {
|
||||
assert!(route_result.is_err());
|
||||
assert!(response.routes.is_empty());
|
||||
String::new()
|
||||
} else {
|
||||
response
|
||||
.routes
|
||||
.first()
|
||||
.expect("no route returned")
|
||||
.expect("no route returned when checking 'route' column")
|
||||
.legs
|
||||
.iter()
|
||||
.map(|leg| {
|
||||
@ -633,26 +832,32 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
let (expected_speed, offset) = extract_number_and_offset("km/h", expectation);
|
||||
// println!("{actual_speed} == {expected_speed} +- {offset}");
|
||||
assert!(
|
||||
aprox_equal_within_percentage_range(actual_speed, expected_speed, offset),
|
||||
approximate_within_range(actual_speed, expected_speed, &offset),
|
||||
"actual time {actual_speed} not equal to expected value {expected_speed}"
|
||||
);
|
||||
},
|
||||
"modes" => {
|
||||
let (_, response) = route_result.as_ref().expect("osrm-routed returned an unexpected error");
|
||||
let route = response.routes.first().expect("no route returned");
|
||||
let actual_modes = route
|
||||
.legs
|
||||
.iter()
|
||||
.map(|leg| {
|
||||
leg.steps
|
||||
let actual = match &route_result {
|
||||
Ok((_, response)) => {
|
||||
let route = response.routes.first().expect("no route returned");
|
||||
let actual_modes = route
|
||||
.legs
|
||||
.iter()
|
||||
.map(|step| step.mode.clone())
|
||||
.map(|leg| {
|
||||
leg.steps
|
||||
.iter()
|
||||
.map(|step| step.mode.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
assert_eq!(actual_modes, expectation);
|
||||
.join(",");
|
||||
actual_modes
|
||||
},
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
|
||||
assert_eq!(actual, expectation);
|
||||
},
|
||||
"turns" => {
|
||||
let (_, response) = route_result.as_ref().expect("osrm-routed returned an unexpected error");
|
||||
@ -664,17 +869,43 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
leg.steps
|
||||
.iter()
|
||||
.map(|step| {
|
||||
let prefix = step.maneuver.r#type.clone();
|
||||
if prefix == "depart" || prefix == "arrive" {
|
||||
// TODO: this reimplements the behavior that depart and arrive are not checked for their modifier
|
||||
// check if tests shall be adapted, since this is reported by the engine
|
||||
return prefix;
|
||||
}
|
||||
let suffix = match &step.maneuver.modifier {
|
||||
Some(modifier) => " ".to_string() + &modifier,
|
||||
_ => "".into(),
|
||||
// NOTE: this is port of JS logic as is. Arguably, this should be replace by a simple join over all type/modifier pairs
|
||||
let r#type = step.maneuver.r#type.clone();
|
||||
let modifier = match &step.maneuver.modifier {
|
||||
Some(modifier) => modifier.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
prefix + &suffix
|
||||
match r#type.as_str() {
|
||||
"depart" | "arrive" => {
|
||||
r#type
|
||||
}
|
||||
"roundabout" => {
|
||||
let exit = match step.maneuver.exit {
|
||||
Some(x) => x,
|
||||
None => unreachable!("roundabout maneuver must come with an exit number"),
|
||||
};
|
||||
format!("roundabout-exit-{}", exit)
|
||||
},
|
||||
"rotary" => {
|
||||
let exit = match step.maneuver.exit {
|
||||
Some(x) => x,
|
||||
None => unreachable!("roundabout maneuver must come with an exit number"),
|
||||
};
|
||||
if let Some(rotary_name) = &step.rotary_name {
|
||||
format!("{rotary_name}-exit-{exit}")
|
||||
} else {
|
||||
format!("rotary-exit-{exit}")
|
||||
}
|
||||
},
|
||||
"roundabout turn" => {
|
||||
let exit = match step.maneuver.exit {
|
||||
Some(x) => x,
|
||||
None => unreachable!("roundabout maneuver must come with an exit number"),
|
||||
};
|
||||
format!("{} {} exit-{}", r#type, modifier, exit)
|
||||
}
|
||||
_ => format!("{} {}", r#type, &modifier),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
@ -689,7 +920,7 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
let (expected_time, offset) = extract_number_and_offset("s", expectation);
|
||||
// println!("{actual_time} == {expected_time} +- {offset}");
|
||||
assert!(
|
||||
approx_equal_within_offset_range(actual_time, expected_time, offset as f64),
|
||||
approximate_within_range(actual_time, expected_time, &offset),
|
||||
"actual time {actual_time} not equal to expected value {expected_time}"
|
||||
);
|
||||
},
|
||||
@ -700,10 +931,10 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
leg.steps.iter().filter(|step| step.duration > 0.).map(|step| step.duration).collect::<Vec<f64>>()
|
||||
}).flatten().collect();
|
||||
let (expected_times, offset) = extract_number_vector_and_offset("s", expectation);
|
||||
assert_eq!(actual_times.len(), expected_times.len(), "times mismatch: {actual_times:?} != {expected_times:?} +- {offset}");
|
||||
assert_eq!(actual_times.len(), expected_times.len(), "times mismatch: {actual_times:?} != {expected_times:?} +- {offset:?}");
|
||||
|
||||
zip(actual_times, expected_times).for_each(|(actual_time, expected_time)| {
|
||||
assert!(approx_equal_within_offset_range(actual_time, expected_time, offset as f64),
|
||||
assert!(approximate_within_range(actual_time, expected_time, &offset),
|
||||
"actual time {actual_time} not equal to expected value {expected_time}");
|
||||
});
|
||||
},
|
||||
@ -713,10 +944,10 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
leg.steps.iter().filter(|step| step.distance > 0.).map(|step| step.distance).collect::<Vec<f64>>()
|
||||
}).flatten().collect::<Vec<f64>>();
|
||||
let (expected_distances, offset) = extract_number_vector_and_offset("m", expectation);
|
||||
assert_eq!(expected_distances.len(), actual_distances.len(), "distances mismatch {expected_distances:?} != {actual_distances:?} +- {offset}");
|
||||
assert_eq!(expected_distances.len(), actual_distances.len(), "distances mismatch {expected_distances:?} != {actual_distances:?} +- {offset:?}");
|
||||
|
||||
zip(actual_distances, expected_distances).for_each(|(actual_distance, expected_distance)| {
|
||||
assert!(approx_equal_within_offset_range(actual_distance, expected_distance, offset as f64),
|
||||
assert!(approximate_within_range(actual_distance, expected_distance, &offset),
|
||||
"actual distance {actual_distance} not equal to expected value {expected_distance}");
|
||||
});
|
||||
},
|
||||
@ -725,26 +956,32 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
let actual_weight = response.routes.first().expect("no route returned").weight;
|
||||
let (expected_weight, offset) = extract_number_and_offset("s", expectation);
|
||||
assert!(
|
||||
approx_equal_within_offset_range(
|
||||
approximate_within_range(
|
||||
actual_weight,
|
||||
expected_weight,
|
||||
offset as f64
|
||||
&offset
|
||||
),
|
||||
"actual time {actual_weight} not equal to expected value {expected_weight}"
|
||||
"actual weight {actual_weight} not equal to expected value {expected_weight}"
|
||||
);
|
||||
},
|
||||
"distance" => {
|
||||
let (_, response) = route_result.as_ref().expect("osrm-routed returned an unexpected error");
|
||||
let actual_distance = response.routes.first().expect("no route returned").distance;
|
||||
let (expected_distance, offset) = extract_number_and_offset("m", expectation);
|
||||
assert!(
|
||||
approx_equal_within_offset_range(
|
||||
actual_distance,
|
||||
expected_distance,
|
||||
offset as f64
|
||||
),
|
||||
"actual time {actual_distance} not equal to expected value {expected_distance}"
|
||||
);
|
||||
match &route_result {
|
||||
Ok((_, response)) => {
|
||||
let actual_distance = response.routes.first().expect("no route returned").distance;
|
||||
let (expected_distance, offset) = extract_number_and_offset("m", expectation);
|
||||
assert!(
|
||||
approximate_within_range(
|
||||
actual_distance,
|
||||
expected_distance,
|
||||
&offset
|
||||
),
|
||||
"actual distance {actual_distance} not equal to expected value {expected_distance} +- {offset:?}"
|
||||
);
|
||||
},
|
||||
Err(_) => {
|
||||
assert_eq!("", expectation);
|
||||
}
|
||||
};
|
||||
},
|
||||
"summary" => {
|
||||
let (_, response) = route_result.as_ref().expect("osrm-routed returned an unexpected error");
|
||||
@ -817,6 +1054,79 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
assert_eq!(&actual_message, expected_message, "message does not match {test_case:?}");
|
||||
|
||||
},
|
||||
"bearing" => {
|
||||
let reverse_bearing = | bearing: u64 | -> u64 {
|
||||
assert!(bearing <= 360);
|
||||
if bearing >= 180 {
|
||||
return bearing - 180;
|
||||
}
|
||||
bearing + 180
|
||||
};
|
||||
|
||||
let actual_bearing = match &route_result {
|
||||
Ok((_,r)) => {
|
||||
r.routes.first().expect("no routes found in 'bearing' check").legs.iter().map(|leg| {
|
||||
leg.steps.iter().map(|step| {
|
||||
let intersection = step.intersections.first().expect("could not find intersection when checking bearing");
|
||||
let prefix = if let Some(r#in) = intersection.r#in {
|
||||
reverse_bearing(intersection.bearings[r#in as usize])
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let suffix = if let Some(out) = intersection.out {
|
||||
intersection.bearings[out as usize]
|
||||
} else {
|
||||
0
|
||||
};
|
||||
format!("{}->{}", prefix, suffix)
|
||||
}).collect::<Vec<String>>().join(",")
|
||||
}).collect::<Vec<_>>().join(",")
|
||||
},
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
let expected_bearing = test_case.get("bearing").expect("test case doesn't have bearing");
|
||||
assert_eq!(&actual_bearing, expected_bearing, "bearings don't match");
|
||||
},
|
||||
"lanes" => {
|
||||
let actual = match &route_result {
|
||||
Ok((_, response)) => {
|
||||
let route = response.routes.first().expect("no route returned");
|
||||
|
||||
/* if(i.lanes)
|
||||
{
|
||||
return i.lanes.map( l => {
|
||||
let indications = l.indications.join(';');
|
||||
return indications + ':' + (l.valid ? 'true' : 'false');
|
||||
}).join(' ');
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}).join(';');
|
||||
*/
|
||||
|
||||
let actual_lanes = route
|
||||
.legs
|
||||
.iter()
|
||||
.map(|leg| {
|
||||
leg.steps
|
||||
.iter()
|
||||
.map(|step| {//step.intersections.iter().map(|i|{
|
||||
"-".to_string()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(";");
|
||||
actual_lanes
|
||||
},
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
|
||||
assert_eq!(actual, expectation);
|
||||
}
|
||||
// TODO: more checks need to be implemented
|
||||
_ => {
|
||||
let msg = format!("case {case} = {expectation} not implemented");
|
||||
@ -843,9 +1153,9 @@ fn main() {
|
||||
future::ready(()).boxed()
|
||||
})
|
||||
// .with_writer(DotWriter::default().normalized())
|
||||
// .filter_run("features", |_, _, sc| {
|
||||
.filter_run("features/testbot/oneway_phantom.feature", |_, _, sc| {
|
||||
!sc.tags.iter().any(|t| t == "todo")
|
||||
// .filter_run("features/", |fe, _, sc| {
|
||||
.filter_run("features/guidance/anticipate-lanes.feature", |fe, _, sc| {
|
||||
!sc.tags.iter().any(|t| t == "todo") && !fe.tags.iter().any(|t| t == "todo")
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user