Implement geometry comparison
This commit is contained in:
parent
85eb7a9383
commit
772f9ccc21
@ -200,7 +200,7 @@ curl 'http://router.project-osrm.org/nearest/v1/driving/13.388860,52.517037?numb
|
||||
Finds the fastest route between coordinates in the supplied order.
|
||||
|
||||
```endpoint
|
||||
GET /route/v1/{profile}/{coordinates}?alternatives={true|false|number}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false}
|
||||
GET /route/v1/{profile}/{coordinates}?alternatives={true|false|number}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false}&continue_straight={default|true|false}
|
||||
```
|
||||
|
||||
In addition to the [general options](#general-options) the following options are supported for this service:
|
||||
|
@ -29,8 +29,6 @@ module.exports = function () {
|
||||
var params = this.paramsToString(parameters);
|
||||
this.query = baseUri + (params.length ? '/' + params : '');
|
||||
|
||||
console.log(this.query);
|
||||
|
||||
request(this.query, (err, res, body) => {
|
||||
if (err && err.code === 'ECONNREFUSED') {
|
||||
return cb(new Error('*** osrm-routed is not running.'));
|
||||
|
@ -24,7 +24,7 @@ impl LocalTask {
|
||||
// TODO: also check that process is running
|
||||
self.ready
|
||||
}
|
||||
pub fn arg(&mut self, argument: &str) -> &mut Self {
|
||||
pub fn arg(mut self, argument: &str) -> Self {
|
||||
self.arguments.push(argument.into());
|
||||
self
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ pub struct OSRMWorld {
|
||||
pub extraction_parameters: Vec<String>,
|
||||
|
||||
pub request_with_flatbuffers: bool,
|
||||
pub bearings: Option<String>,
|
||||
pub query_options: HashMap<String, String>,
|
||||
|
||||
pub grid_size: f32,
|
||||
pub origin: Location,
|
||||
@ -60,7 +60,13 @@ impl Default for OSRMWorld {
|
||||
osm_db: Default::default(),
|
||||
extraction_parameters: Default::default(),
|
||||
request_with_flatbuffers: Default::default(),
|
||||
bearings: None,
|
||||
query_options: HashMap::from([
|
||||
// default parameters // TODO: check if necessary
|
||||
("steps".into(), "true".into()),
|
||||
("alternatives".into(), "false".into()),
|
||||
("annotations".into(), "true".into()),
|
||||
]),
|
||||
|
||||
grid_size: DEFAULT_GRID_SIZE,
|
||||
origin: DEFAULT_ORIGIN,
|
||||
way_spacing: WAY_SPACING,
|
||||
@ -206,9 +212,7 @@ impl OSRMWorld {
|
||||
.artefact_cache_path()
|
||||
.join(self.scenario_id.to_owned() + ".osrm");
|
||||
|
||||
// TODO: this should not require a temporary and behave like the API of std::process
|
||||
self.task = LocalTask::new(self.routed_path().to_string_lossy().into());
|
||||
self.task
|
||||
self.task = LocalTask::new(self.routed_path().to_string_lossy().into())
|
||||
.arg(data_path.to_str().expect("data path unwrappable"));
|
||||
self.task
|
||||
.spawn_wait_till_ready("running and waiting for requests");
|
||||
@ -229,6 +233,19 @@ impl OSRMWorld {
|
||||
if self.request_with_flatbuffers {
|
||||
url += ".flatbuffers";
|
||||
}
|
||||
|
||||
if !self.query_options.is_empty() {
|
||||
let options = self
|
||||
.query_options
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{key}={value}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("&");
|
||||
url += "?";
|
||||
url += &options;
|
||||
}
|
||||
|
||||
// panic!("url: {url}");
|
||||
let call = self.agent.get(&url).call();
|
||||
|
||||
let body = match call {
|
||||
@ -253,15 +270,21 @@ impl OSRMWorld {
|
||||
.join(";");
|
||||
|
||||
let mut url = format!(
|
||||
"http://localhost:5000/route/v1/{}/{waypoint_string}?steps=true&alternatives=false",
|
||||
"http://localhost:5000/route/v1/{}/{waypoint_string}",
|
||||
self.profile,
|
||||
);
|
||||
if self.request_with_flatbuffers {
|
||||
url += ".flatbuffers";
|
||||
}
|
||||
if let Some(bearings) = &self.bearings {
|
||||
url += "&bearings=";
|
||||
url += bearings;
|
||||
if !self.query_options.is_empty() {
|
||||
let options = self
|
||||
.query_options
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{key}={value}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("&");
|
||||
url += "?";
|
||||
url += &options;
|
||||
}
|
||||
// println!("url: {url}");
|
||||
let call = self.agent.get(&url).call();
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::default;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{location::Location, nearest_response::Waypoint};
|
||||
@ -11,9 +13,25 @@ pub struct Maneuver {
|
||||
pub r#type: String, // TODO: should be an enum
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Geometry {
|
||||
A(String),
|
||||
B {
|
||||
coordinates: Vec<Location>,
|
||||
r#type: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for Geometry {
|
||||
fn default() -> Self {
|
||||
Geometry::A("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
pub struct Step {
|
||||
pub geometry: String,
|
||||
pub geometry: Geometry,
|
||||
pub mode: String,
|
||||
pub maneuver: Maneuver,
|
||||
pub name: String,
|
||||
@ -38,9 +56,9 @@ pub struct Leg {
|
||||
// pub annotation: Option<Vec<Annotation>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Route {
|
||||
pub geometry: String,
|
||||
pub geometry: Geometry,
|
||||
pub weight: f64,
|
||||
pub duration: f64,
|
||||
pub legs: Vec<Leg>,
|
||||
@ -52,7 +70,7 @@ pub struct Route {
|
||||
pub struct RouteResponse {
|
||||
pub code: String,
|
||||
pub routes: Vec<Route>,
|
||||
pub waypoints: Vec<Waypoint>,
|
||||
pub waypoints: Option<Vec<Waypoint>>,
|
||||
pub data_version: Option<String>,
|
||||
}
|
||||
|
||||
@ -66,7 +84,6 @@ impl RouteResponse {
|
||||
}
|
||||
|
||||
pub fn from_string(input: &str) -> Self {
|
||||
// println!("{input}");
|
||||
let response = match serde_json::from_str(input) {
|
||||
Ok(response) => response,
|
||||
Err(e) => panic!("parsing error {e} => {input}"),
|
||||
@ -74,3 +91,15 @@ 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);
|
||||
|
||||
// }
|
||||
// }
|
||||
|
@ -12,11 +12,14 @@ use common::{
|
||||
location::Location,
|
||||
osm::OSMWay,
|
||||
osrm_world::OSRMWorld,
|
||||
route_response,
|
||||
};
|
||||
use core::panic;
|
||||
use cucumber::{
|
||||
gherkin::{Step, Table},
|
||||
given, then, when, World, WriterExt,
|
||||
given, then, when,
|
||||
writer::summarize,
|
||||
World, WriterExt,
|
||||
};
|
||||
use futures::{future, FutureExt};
|
||||
use geo_types::Point;
|
||||
@ -48,6 +51,12 @@ fn set_profile(world: &mut OSRMWorld, profile: String) {
|
||||
world.profile = profile;
|
||||
}
|
||||
|
||||
#[given(expr = "the query options")]
|
||||
fn set_query_options(world: &mut OSRMWorld, step: &Step) {
|
||||
let table = parse_option_table(&step.table.as_ref());
|
||||
world.query_options.extend(table.into_iter());
|
||||
}
|
||||
|
||||
#[given(expr = "the node locations")]
|
||||
fn set_node_locations(world: &mut OSRMWorld, step: &Step) {
|
||||
let table = step.table().expect("cannot get table");
|
||||
@ -189,6 +198,19 @@ fn set_ways(world: &mut OSRMWorld, step: &Step) {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_option_table(table: &Option<&Table>) -> HashMap<String, String> {
|
||||
let table = table.expect("no query table specified");
|
||||
let result = table
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
assert_eq!(2, row.len());
|
||||
(row[0].clone(), row[1].clone())
|
||||
})
|
||||
.collect();
|
||||
result
|
||||
}
|
||||
|
||||
fn parse_table_from_steps(table: &Option<&Table>) -> (Vec<String>, Vec<HashMap<String, String>>) {
|
||||
// parse query data
|
||||
let table = table.expect("no query table specified");
|
||||
@ -456,6 +478,15 @@ pub fn get_location_specification(test_case: &HashMap<String, String>) -> Waypoi
|
||||
// WaypointsOrLocation::Undefined
|
||||
}
|
||||
|
||||
#[given(expr = r"skip waypoints")]
|
||||
fn skip_waypoints(world: &mut OSRMWorld, step: &Step) {
|
||||
// TODO: adapt test to use query options
|
||||
// only used in features/testbot/basic.feature
|
||||
world
|
||||
.query_options
|
||||
.insert("skip_waypoints".into(), "true".into());
|
||||
}
|
||||
|
||||
#[when(regex = r"^I route( with flatbuffers|) I should get$")]
|
||||
fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
world.request_with_flatbuffers = state == " with flatbuffers";
|
||||
@ -500,8 +531,11 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(bearing) = test_case.get("bearings").cloned() {
|
||||
world.bearings = Some(bearing.replace(" ", ";"));
|
||||
if let Some(bearings) = test_case.get("bearings").cloned() {
|
||||
// TODO: change test cases to provide proper query options
|
||||
world
|
||||
.query_options
|
||||
.insert("bearings".into(), bearings.replace(" ", ";"));
|
||||
}
|
||||
|
||||
let response = world.route(&waypoints);
|
||||
@ -510,7 +544,7 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
.iter()
|
||||
.map(|(column_title, expectation)| (column_title.as_str(), expectation.as_str()))
|
||||
.for_each(|(case, expectation)| match case {
|
||||
"from" | "to" | "bearings"=> {}, // ignore input columns
|
||||
"from" | "to" | "bearings" | "waypoints" | "#" => {}, // ignore input and comment columns
|
||||
"route" => {
|
||||
let route = if expectation.is_empty() {
|
||||
assert!(response.routes.is_empty());
|
||||
@ -607,9 +641,14 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
.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,
|
||||
None => "".into(),
|
||||
_ => "".into(),
|
||||
};
|
||||
prefix + &suffix
|
||||
})
|
||||
@ -633,10 +672,10 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
// TODO: go over steps
|
||||
|
||||
let actual_times : Vec<f64>= response.routes.first().expect("no route returned").legs.iter().map(|leg| {
|
||||
leg.steps.iter().map(|step| step.duration).collect::<Vec<f64>>()
|
||||
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);
|
||||
println!("{actual_times:?} == {expected_times:?} +- {offset}");
|
||||
// println!("{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)| {
|
||||
@ -645,29 +684,20 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
});
|
||||
},
|
||||
"distances" => {
|
||||
|
||||
println!("{:?}",response.routes.first().expect("no route returned"));
|
||||
// TODO: go over steps
|
||||
let actual_distances : Vec<f64> = response.routes.first().expect("no route returned").legs.iter().map(|leg| leg.distance).collect();
|
||||
let actual_distances = response.routes.first().expect("no route returned").legs.iter().map(|leg| {
|
||||
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);
|
||||
println!("{expected_distances:?} == {actual_distances:?}");
|
||||
println!("!");
|
||||
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),
|
||||
"actual distance {actual_distance} not equal to expected value {expected_distance}");
|
||||
});
|
||||
// // println!("{actual_time} == {expected_time} +- {offset}");
|
||||
// assert!(
|
||||
// approx_equal_within_offset_range(actual_time, expected_time, offset as f64),
|
||||
// "actual time {actual_time} not equal to expected value {expected_time}"
|
||||
// );
|
||||
},
|
||||
"weight" => {
|
||||
let actual_weight = response.routes.first().expect("no route returned").weight;
|
||||
let (expected_weight, offset) = extract_number_and_offset("s", expectation);
|
||||
// println!("{actual_weight} == {expected_weight} +- {offset}");
|
||||
assert!(
|
||||
approx_equal_within_offset_range(
|
||||
actual_weight,
|
||||
@ -689,7 +719,54 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
"actual time {actual_distance} not equal to expected value {expected_distance}"
|
||||
);
|
||||
},
|
||||
"waypoints" => {},
|
||||
"summary" => {
|
||||
let actual_summary = response.routes.first().expect("no route returned").legs.iter().map(|leg| {
|
||||
leg.summary.clone()
|
||||
}).collect::<Vec<String>>().join(",");
|
||||
assert_eq!(actual_summary,expectation, "summary mismatch");
|
||||
},
|
||||
"data_version" => {
|
||||
let expected_data_version = match test_case.get("data_version") {
|
||||
Some(s) if !s.is_empty() => Some(s),
|
||||
_ => None,
|
||||
};
|
||||
assert_eq!(
|
||||
expected_data_version,
|
||||
response.data_version.as_ref(),
|
||||
"data_version does not match"
|
||||
);
|
||||
},
|
||||
"waypoints_count" => {
|
||||
let expected_waypoint_count = match test_case.get("waypoints_count") {
|
||||
Some(s) if !s.is_empty() => s.parse::<usize>().expect("waypoint_count is a number"),
|
||||
_ => 0,
|
||||
};
|
||||
let actual_waypoint_count = match &response.waypoints {
|
||||
Some(w) => w.len(),
|
||||
None => 0,
|
||||
};
|
||||
assert_eq!(
|
||||
expected_waypoint_count,
|
||||
actual_waypoint_count,
|
||||
"waypoint_count does not match"
|
||||
);
|
||||
},
|
||||
"geometry" => {
|
||||
let expected_geometry = test_case.get("geometry").expect("no geometry found");
|
||||
match &response.routes.first().expect("no route").geometry {
|
||||
route_response::Geometry::A(actual_geometry) => {
|
||||
assert_eq!(
|
||||
expected_geometry,
|
||||
actual_geometry,
|
||||
"geometry does not match"
|
||||
);
|
||||
},
|
||||
route_response::Geometry::B { coordinates: _, r#type: _ } => unimplemented!("geojson comparison"),
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
// "classes" => {},
|
||||
// TODO: more checks need to be implemented
|
||||
_ => {
|
||||
let msg = format!("case {case} = {expectation} not implemented");
|
||||
@ -700,8 +777,7 @@ fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
}
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
debug!("arguments: {:?}", args);
|
||||
|
||||
debug!("{args:?}");
|
||||
let digest = md5_of_osrm_executables().digest().to_hex_lowercase();
|
||||
|
||||
futures::executor::block_on(
|
||||
@ -717,8 +793,7 @@ fn main() {
|
||||
future::ready(()).boxed()
|
||||
})
|
||||
// .with_writer(DotWriter::default().normalized())
|
||||
.filter_run("features/testbot/time.feature", |_, _, sc| {
|
||||
!sc.tags.iter().any(|t| t == "todo")
|
||||
}),
|
||||
// .filter_run("features/testbot/geometry.feature", |_, _, sc| {
|
||||
.filter_run("features", |_, _, sc| !sc.tags.iter().any(|t| t == "todo")),
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user