Implement support for car/names.feature
This commit is contained in:
parent
d985459696
commit
37f5780472
13
build.rs
13
build.rs
@ -66,7 +66,7 @@ fn main() {
|
||||
if let Some((platform, compiler)) = match env::consts::OS {
|
||||
"linux" if env::consts::ARCH == "x86_64" => Some((OS::Linux, ".clang++-15")),
|
||||
"macos" if env::consts::ARCH == "x86_64" => Some((OS::MacIntel, "")),
|
||||
"macos" if env::consts::ARCH == "arm" => Some((OS::Mac, "")),
|
||||
"macos" if env::consts::ARCH == "aarch64" => Some((OS::Mac, "")),
|
||||
"windows" if env::consts::ARCH == "x86_64" => Some((OS::Windows, "")),
|
||||
_ => None,
|
||||
} {
|
||||
@ -98,11 +98,16 @@ fn main() {
|
||||
build_println!("unsupported platform: {} {}. 'flatc' binary supporting version {} of the library needs to be in system path", env::consts::OS, env::consts::ARCH, version);
|
||||
}
|
||||
|
||||
let flatc = match Path::new(executable_path).exists() {
|
||||
true => flatc_rust::Flatc::from_path(executable_path),
|
||||
false => flatc_rust::Flatc::from_env_path(),
|
||||
let (flatc, location) = match Path::new(executable_path).exists() {
|
||||
true => (flatc_rust::Flatc::from_path(executable_path), "local"),
|
||||
false => (flatc_rust::Flatc::from_env_path(), "downloaded"),
|
||||
};
|
||||
assert!(flatc.check().is_ok());
|
||||
let version = &flatc.version().unwrap();
|
||||
build_println!(
|
||||
"using {location} flatc v{} to compile schema files",
|
||||
version.version()
|
||||
);
|
||||
flatc
|
||||
.run(flatc_rust::Args {
|
||||
extra: &["--gen-all"],
|
||||
|
@ -46,7 +46,7 @@ class OSRMBaseLoader{
|
||||
let retry = (err) => {
|
||||
if (err) {
|
||||
if (retryCount < this.scope.OSRM_CONNECTION_RETRIES) {
|
||||
const timeoutMs = 10 * Math.pow(this.scope.OSRM_CONNECTION_EXP_BACKOFF_COEF, retryCount);
|
||||
const timeoutMs = 10 * Math.pow(1.1, retryCount);
|
||||
retryCount++;
|
||||
setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, timeoutMs);
|
||||
} else {
|
||||
|
@ -29,6 +29,8 @@ 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.'));
|
||||
|
@ -37,7 +37,6 @@ impl Display for RoutingAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: move to external file
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
|
@ -1,6 +1,9 @@
|
||||
use colored::Colorize;
|
||||
use cucumber::{cli, event, parser, Event};
|
||||
use std::{io::{self, Write}, time::Instant};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DotWriter {
|
||||
@ -75,8 +78,8 @@ impl<W: 'static> cucumber::Writer<W> for DotWriter {
|
||||
let p = format!("{} passed", self.step_passed).green();
|
||||
println!("{} steps ({f}, {s}, {p})", self.step_started);
|
||||
|
||||
let elapsed = Instant::now() - self.start_time.unwrap();
|
||||
let minutes = elapsed.as_secs()/60;
|
||||
let elapsed = Instant::now() - self.start_time.unwrap();
|
||||
let minutes = elapsed.as_secs() / 60;
|
||||
let seconds = (elapsed.as_millis() % 60_000) as f64 / 1000.;
|
||||
println!("{}m{}s", minutes, seconds);
|
||||
}
|
||||
@ -86,10 +89,10 @@ impl<W: 'static> cucumber::Writer<W> for DotWriter {
|
||||
scenarios: _,
|
||||
steps: _,
|
||||
parser_errors: _,
|
||||
} => {},
|
||||
} => {}
|
||||
event::Cucumber::Started => {
|
||||
self.start_time = Some(Instant::now());
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(e) => println!("Error: {e}"),
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
pub fn approx_equal(a: f32, b: f32, dp: u8) -> bool {
|
||||
let p = 10f32.powi(-(dp as i32));
|
||||
(a - b).abs() < p
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
use std::{fs::{self, File}, io::Read, path::PathBuf};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Read,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use log::debug;
|
||||
|
||||
@ -13,4 +17,4 @@ pub fn get_file_as_byte_vec(path: &PathBuf) -> Vec<u8> {
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ use std::{env, fs, path::PathBuf};
|
||||
|
||||
use log::debug;
|
||||
|
||||
use crate::common::{file_util::get_file_as_byte_vec, lexicographic_file_walker::LexicographicFileWalker};
|
||||
use crate::common::{
|
||||
file_util::get_file_as_byte_vec, lexicographic_file_walker::LexicographicFileWalker,
|
||||
};
|
||||
|
||||
pub fn md5_of_osrm_executables() -> chksum_md5::MD5 {
|
||||
// create OSRM digest before any tests are executed since cucumber-rs doesn't have @beforeAll
|
||||
@ -75,4 +77,4 @@ pub fn md5_of_osrm_executables() -> chksum_md5::MD5 {
|
||||
// debug!("md5: {}", md5.digest().to_hex_lowercase());
|
||||
}
|
||||
md5
|
||||
}
|
||||
}
|
||||
|
@ -2,23 +2,23 @@
|
||||
// functions to make nearest, route, etc calls
|
||||
// fn nearest(arg1, ... argn) -> NearestResponse
|
||||
|
||||
use std::{path::Path, time::Duration};
|
||||
// use std::{path::Path, time::Duration};
|
||||
|
||||
use ureq::{Agent, AgentBuilder};
|
||||
// use ureq::{Agent, AgentBuilder};
|
||||
|
||||
pub struct HttpRequest {
|
||||
agent: Agent,
|
||||
}
|
||||
// pub struct HttpRequest {
|
||||
// agent: Agent,
|
||||
// }
|
||||
|
||||
impl HttpRequest {
|
||||
// pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {}
|
||||
// impl HttpRequest {
|
||||
// // pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let agent = AgentBuilder::new()
|
||||
.timeout_read(Duration::from_secs(5))
|
||||
.timeout_write(Duration::from_secs(5))
|
||||
.build();
|
||||
// pub fn new() -> Self {
|
||||
// let agent = AgentBuilder::new()
|
||||
// .timeout_read(Duration::from_secs(5))
|
||||
// .timeout_write(Duration::from_secs(5))
|
||||
// .build();
|
||||
|
||||
Self { agent }
|
||||
}
|
||||
}
|
||||
// Self { agent }
|
||||
// }
|
||||
// }
|
||||
|
@ -3,24 +3,25 @@ use std::{
|
||||
process::{Child, Command, Stdio},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TaskStarter {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LocalTask {
|
||||
ready: bool,
|
||||
command: String,
|
||||
arguments: Vec<String>,
|
||||
child: Option<Child>,
|
||||
}
|
||||
|
||||
impl TaskStarter {
|
||||
pub fn new(command: &str) -> Self {
|
||||
impl LocalTask {
|
||||
pub fn new(command: String) -> Self {
|
||||
Self {
|
||||
ready: false,
|
||||
command: command.into(),
|
||||
command: command,
|
||||
arguments: Vec::new(),
|
||||
child: None,
|
||||
}
|
||||
}
|
||||
pub fn is_ready(&self) -> bool {
|
||||
// TODO: also check that process is running
|
||||
self.ready
|
||||
}
|
||||
pub fn arg(&mut self, argument: &str) -> &mut Self {
|
||||
@ -39,6 +40,10 @@ impl TaskStarter {
|
||||
Err(e) => panic!("cannot spawn task: {e}"),
|
||||
}
|
||||
|
||||
if self.child.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(output) = &mut self.child.as_mut().unwrap().stdout {
|
||||
// implement with a timeout
|
||||
let mut reader = BufReader::new(output);
|
||||
@ -54,10 +59,10 @@ impl TaskStarter {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TaskStarter {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.child.as_mut().expect("can't access child").kill() {
|
||||
panic!("shutdown failed: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl Drop for LocalTask {
|
||||
// fn drop(&mut self) {
|
||||
// if let Err(e) = self.child.as_mut().expect("can't access child").kill() {
|
||||
// panic!("shutdown failed: {e}");
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1,6 +1,6 @@
|
||||
#![allow(clippy::derivable_impls, clippy::all)]
|
||||
extern crate flatbuffers;
|
||||
|
||||
|
||||
pub mod cli_arguments;
|
||||
pub mod dot_writer;
|
||||
pub mod f64_utils;
|
||||
@ -8,27 +8,28 @@ pub mod file_util;
|
||||
pub mod hash_util;
|
||||
pub mod http_request;
|
||||
pub mod lexicographic_file_walker;
|
||||
pub mod local_task;
|
||||
pub mod location;
|
||||
pub mod nearest_response;
|
||||
pub mod osm;
|
||||
pub mod osm_db;
|
||||
pub mod osrm_world;
|
||||
pub mod route_response;
|
||||
pub mod scenario_id;
|
||||
pub mod task_starter;
|
||||
|
||||
// flatbuffer
|
||||
#[allow(dead_code, unused_imports)]
|
||||
#[path = "../../target/flatbuffers/fbresult_generated.rs"]
|
||||
pub mod fbresult_flatbuffers;
|
||||
#[allow(dead_code, unused_imports)]
|
||||
#[path = "../../target/flatbuffers/position_generated.rs"]
|
||||
pub mod position_flatbuffers;
|
||||
#[allow(dead_code, unused_imports)]
|
||||
#[path = "../../target/flatbuffers/waypoint_generated.rs"]
|
||||
pub mod waypoint_flatbuffers;
|
||||
#[path = "../../target/flatbuffers/route_generated.rs"]
|
||||
pub mod route_flatbuffers;
|
||||
#[allow(dead_code, unused_imports)]
|
||||
#[path = "../../target/flatbuffers/table_generated.rs"]
|
||||
pub mod table_flatbuffers;
|
||||
#[allow(dead_code, unused_imports)]
|
||||
#[path = "../../target/flatbuffers/route_generated.rs"]
|
||||
pub mod route_flatbuffers;
|
||||
#[allow(dead_code, unused_imports)]
|
||||
#[path = "../../target/flatbuffers/fbresult_generated.rs"]
|
||||
pub mod fbresult_flatbuffers;
|
||||
#[path = "../../target/flatbuffers/waypoint_generated.rs"]
|
||||
pub mod waypoint_flatbuffers;
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
|
||||
use super::location::Location;
|
||||
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Waypoint {
|
||||
pub hint: String,
|
||||
pub nodes: Vec<u64>,
|
||||
pub nodes: Option<Vec<u64>>,
|
||||
pub distance: f32,
|
||||
pub name: String,
|
||||
location: Location,
|
||||
@ -66,7 +66,7 @@ impl NearestResponse {
|
||||
longitude: location.longitude(),
|
||||
};
|
||||
let nodes = wp.nodes().expect("waypoint mus have nodes");
|
||||
let nodes = vec![nodes.first(), nodes.second()];
|
||||
let nodes = Some(vec![nodes.first(), nodes.second()]);
|
||||
let distance = wp.distance();
|
||||
|
||||
Waypoint {
|
||||
|
@ -93,15 +93,19 @@ mod tests {
|
||||
|
||||
let mut node1 = OSMNode {
|
||||
id: 123,
|
||||
lat: 50.1234,
|
||||
lon: 8.9876,
|
||||
location: Location {
|
||||
longitude: 8.9876,
|
||||
latitude: 50.1234,
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut node2 = OSMNode {
|
||||
id: 321,
|
||||
lat: 50.1234,
|
||||
lon: 8.9876,
|
||||
location: Location {
|
||||
longitude: 8.9876,
|
||||
latitude: 50.1234,
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
node1.add_tag("name", "a");
|
||||
|
@ -1,15 +1,25 @@
|
||||
use crate::Location;
|
||||
use super::{
|
||||
nearest_response::NearestResponse, osm::OSMNode, osm_db::OSMDb, route_response::RouteResponse,
|
||||
};
|
||||
use crate::{common::local_task::LocalTask, Location};
|
||||
use core::panic;
|
||||
use cucumber::World;
|
||||
use log::debug;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{create_dir_all, File},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{osm::OSMNode, osm_db::OSMDb};
|
||||
const DEFAULT_ORIGIN: Location = Location {
|
||||
longitude: 1.0f32,
|
||||
latitude: 1.0f32,
|
||||
};
|
||||
const DEFAULT_GRID_SIZE: f32 = 100.;
|
||||
|
||||
#[derive(Debug, Default, World)]
|
||||
#[derive(Debug, World)]
|
||||
pub struct OSRMWorld {
|
||||
pub feature_path: Option<PathBuf>,
|
||||
pub scenario_id: String,
|
||||
@ -25,6 +35,36 @@ pub struct OSRMWorld {
|
||||
pub extraction_parameters: Vec<String>,
|
||||
|
||||
pub request_with_flatbuffers: bool,
|
||||
|
||||
pub grid_size: f32,
|
||||
pub origin: Location,
|
||||
task: LocalTask,
|
||||
agent: ureq::Agent,
|
||||
}
|
||||
|
||||
impl Default for OSRMWorld {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
feature_path: Default::default(),
|
||||
scenario_id: Default::default(),
|
||||
feature_digest: Default::default(),
|
||||
osrm_digest: Default::default(),
|
||||
osm_id: Default::default(),
|
||||
profile: Default::default(),
|
||||
known_osm_nodes: Default::default(),
|
||||
known_locations: Default::default(),
|
||||
osm_db: Default::default(),
|
||||
extraction_parameters: Default::default(),
|
||||
request_with_flatbuffers: Default::default(),
|
||||
grid_size: DEFAULT_GRID_SIZE,
|
||||
origin: DEFAULT_ORIGIN,
|
||||
task: LocalTask::default(),
|
||||
agent: ureq::AgentBuilder::new()
|
||||
.timeout_read(Duration::from_secs(5))
|
||||
.timeout_write(Duration::from_secs(5))
|
||||
.build(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OSRMWorld {
|
||||
@ -59,7 +99,11 @@ impl OSRMWorld {
|
||||
.ancestors()
|
||||
.find(|p| p.ends_with("features"))
|
||||
.expect(".feature files reside in a directory tree with the root name 'features'");
|
||||
let routed_path = path.parent().expect("cannot get parent path").join("build").join("osrm-routed");
|
||||
let routed_path = path
|
||||
.parent()
|
||||
.expect("cannot get parent path")
|
||||
.join("build")
|
||||
.join("osrm-routed");
|
||||
assert!(routed_path.exists(), "osrm-routed binary not found");
|
||||
routed_path
|
||||
}
|
||||
@ -119,4 +163,112 @@ impl OSRMWorld {
|
||||
}
|
||||
self.known_locations.insert(name, location);
|
||||
}
|
||||
|
||||
pub fn write_osm_file(&self) {
|
||||
let osm_file = self
|
||||
.feature_cache_path()
|
||||
.join(self.scenario_id.clone() + ".osm");
|
||||
if !osm_file.exists() {
|
||||
debug!("writing to osm file: {osm_file:?}");
|
||||
let mut file = File::create(osm_file).expect("could not create OSM file");
|
||||
file.write_all(self.osm_db.to_xml().as_bytes())
|
||||
.expect("could not write OSM file");
|
||||
} else {
|
||||
debug!("not writing to OSM file {osm_file:?}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_osm_file(&self) {
|
||||
let cache_path = self.artefact_cache_path();
|
||||
if cache_path.exists() {
|
||||
debug!("{cache_path:?} exists");
|
||||
} else {
|
||||
unimplemented!("{cache_path:?} does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn artefact_cache_path(&self) -> PathBuf {
|
||||
self.feature_cache_path().join(&self.osrm_digest)
|
||||
}
|
||||
|
||||
fn start_routed(&mut self) {
|
||||
if self.task.is_ready() {
|
||||
// task running already
|
||||
return;
|
||||
}
|
||||
let data_path = self
|
||||
.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
|
||||
.arg(data_path.to_str().expect("data path unwrappable"));
|
||||
self.task
|
||||
.spawn_wait_till_ready("running and waiting for requests");
|
||||
assert!(self.task.is_ready());
|
||||
}
|
||||
|
||||
pub fn nearest(
|
||||
&mut self,
|
||||
query_location: &Location,
|
||||
request_with_flatbuffers: bool,
|
||||
) -> NearestResponse {
|
||||
self.start_routed();
|
||||
|
||||
let mut url = format!(
|
||||
"http://localhost:5000/nearest/v1/{}/{:?},{:?}",
|
||||
self.profile, query_location.longitude, query_location.latitude
|
||||
);
|
||||
if request_with_flatbuffers {
|
||||
url += ".flatbuffers";
|
||||
}
|
||||
let call = self.agent.get(&url).call();
|
||||
|
||||
let body = match call {
|
||||
Ok(response) => response.into_reader(),
|
||||
Err(e) => panic!("http error: {e}"),
|
||||
};
|
||||
|
||||
let response = match request_with_flatbuffers {
|
||||
true => NearestResponse::from_flatbuffer(body),
|
||||
false => NearestResponse::from_json_reader(body),
|
||||
};
|
||||
response
|
||||
}
|
||||
|
||||
pub fn route(
|
||||
&mut self,
|
||||
from_location: &Location,
|
||||
to_location: &Location,
|
||||
request_with_flatbuffers: bool,
|
||||
) -> RouteResponse {
|
||||
self.start_routed();
|
||||
|
||||
let mut url = format!(
|
||||
"http://localhost:5000/route/v1/{}/{:?},{:?};{:?},{:?}?steps=true&alternatives=false",
|
||||
self.profile,
|
||||
from_location.longitude,
|
||||
from_location.latitude,
|
||||
to_location.longitude,
|
||||
to_location.latitude,
|
||||
);
|
||||
if request_with_flatbuffers {
|
||||
url += ".flatbuffers";
|
||||
}
|
||||
// println!("url: {url}");
|
||||
let call = self.agent.get(&url).call();
|
||||
|
||||
let body = match call {
|
||||
Ok(response) => response.into_reader(),
|
||||
Err(e) => panic!("http error: {e}"),
|
||||
};
|
||||
|
||||
let text = std::io::read_to_string(body).unwrap();
|
||||
let response = match request_with_flatbuffers {
|
||||
true => unimplemented!("RouteResponse::from_flatbuffer(body)"),
|
||||
false => RouteResponse::from_string(&text),
|
||||
};
|
||||
response
|
||||
}
|
||||
}
|
||||
|
62
tests/common/route_response.rs
Normal file
62
tests/common/route_response.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::nearest_response::Waypoint;
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
pub struct Step {
|
||||
pub name: String,
|
||||
pub pronunciation: Option<String>,
|
||||
pub r#ref: Option<String>,
|
||||
}
|
||||
|
||||
// #[derive(Deserialize, Debug)]
|
||||
// pub struct Annotation {
|
||||
// pub nodes: Option<Vec<u64>>,
|
||||
// }
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Leg {
|
||||
pub summary: String,
|
||||
pub weight: f64,
|
||||
pub duration: f64,
|
||||
pub steps: Vec<Step>,
|
||||
pub distance: f64,
|
||||
// pub annotation: Option<Vec<Annotation>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Route {
|
||||
pub geometry: String,
|
||||
pub weight: f64,
|
||||
pub duration: f64,
|
||||
pub legs: Vec<Leg>,
|
||||
pub weight_name: String,
|
||||
pub distance: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct RouteResponse {
|
||||
pub code: String,
|
||||
pub routes: Vec<Route>,
|
||||
pub waypoints: Vec<Waypoint>,
|
||||
pub data_version: Option<String>,
|
||||
}
|
||||
|
||||
impl RouteResponse {
|
||||
// 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
|
||||
// }
|
||||
|
||||
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}"),
|
||||
};
|
||||
response
|
||||
}
|
||||
}
|
@ -1,35 +1,31 @@
|
||||
// extern crate clap;
|
||||
|
||||
mod common;
|
||||
|
||||
use cheap_ruler::CheapRuler;
|
||||
use clap::Parser;
|
||||
use common::{
|
||||
cli_arguments::Args, dot_writer::DotWriter, f64_utils::approx_equal,
|
||||
hash_util::md5_of_osrm_executables, location::Location, nearest_response::NearestResponse,
|
||||
osm::OSMWay, osrm_world::OSRMWorld, task_starter::TaskStarter,
|
||||
hash_util::md5_of_osrm_executables, location::Location, osm::OSMWay, osrm_world::OSRMWorld,
|
||||
};
|
||||
use core::panic;
|
||||
use cucumber::{gherkin::Step, given, when, World, WriterExt};
|
||||
use cucumber::{
|
||||
gherkin::{Step, Table},
|
||||
given, when, World, WriterExt,
|
||||
};
|
||||
use futures::{future, FutureExt};
|
||||
use geo_types::point;
|
||||
use geo_types::Point;
|
||||
use log::debug;
|
||||
use std::{collections::HashMap, fs::File, io::Write, time::Duration};
|
||||
use ureq::Agent;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const DEFAULT_ORIGIN: [f64; 2] = [1., 1.]; // TODO: move to world?
|
||||
const DEFAULT_GRID_SIZE: f64 = 100.; // TODO: move to world?
|
||||
|
||||
fn offset_origin_by(dx: f64, dy: f64) -> Location {
|
||||
let ruler = CheapRuler::new(DEFAULT_ORIGIN[1], cheap_ruler::DistanceUnit::Meters);
|
||||
fn offset_origin_by(dx: f32, dy: f32, origin: Location, grid_size: f32) -> Location {
|
||||
let ruler = CheapRuler::new(origin.latitude, cheap_ruler::DistanceUnit::Meters);
|
||||
let loc = ruler.offset(
|
||||
&point!(DEFAULT_ORIGIN),
|
||||
dx * DEFAULT_GRID_SIZE,
|
||||
dy * DEFAULT_GRID_SIZE,
|
||||
&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() as f32,
|
||||
longitude: loc.x() as f32,
|
||||
latitude: loc.y(),
|
||||
longitude: loc.x(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,12 +91,16 @@ fn set_node_map(world: &mut OSRMWorld, step: &Step) {
|
||||
.filter(|(_column_index, charater)| *charater != ' ')
|
||||
.for_each(|(column_index, name)| {
|
||||
// This ports the logic from previous implementations.
|
||||
let location =
|
||||
offset_origin_by(column_index as f64 * 0.5, -(row_index as f64 - 1.));
|
||||
let location = offset_origin_by(
|
||||
column_index as f32 * 0.5,
|
||||
-(row_index as f32 - 1.),
|
||||
world.origin,
|
||||
world.grid_size,
|
||||
);
|
||||
match name {
|
||||
'0'...'9' => world.add_location(name, location),
|
||||
'a'...'z' => world.add_osm_node(name, location, None),
|
||||
_ => unreachable!("node name not in [0..9][a..z]"),
|
||||
_ => unreachable!("node name not in [0..9][a..z]: {docstring}"),
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -114,6 +114,11 @@ fn extra_parameters(world: &mut OSRMWorld, parameters: String) {
|
||||
world.extraction_parameters.push(parameters);
|
||||
}
|
||||
|
||||
#[given(expr = "a grid size of {float} meters")]
|
||||
fn set_grid_size(world: &mut OSRMWorld, meters: f32) {
|
||||
world.grid_size = meters;
|
||||
}
|
||||
|
||||
#[given(regex = "the ways")]
|
||||
fn set_ways(world: &mut OSRMWorld, step: &Step) {
|
||||
// debug!("using profile: {profile}");
|
||||
@ -159,43 +164,13 @@ fn set_ways(world: &mut OSRMWorld, step: &Step) {
|
||||
} else {
|
||||
debug!("no table found {step:#?}");
|
||||
}
|
||||
|
||||
// debug!("{}", world.osm_db.to_xml())
|
||||
}
|
||||
|
||||
// #[when("I request nearest I should get")]
|
||||
#[when(regex = r"^I request nearest( with flatbuffers|) I should get$")]
|
||||
fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
let request_with_flatbuffers = state == " with flatbuffers";
|
||||
// if .osm file does not exist
|
||||
// write osm file
|
||||
|
||||
// TODO: the OSRMWorld instance should have a function to write the .osm file
|
||||
let osm_file = world
|
||||
.feature_cache_path()
|
||||
.join(world.scenario_id.clone() + ".osm");
|
||||
if !osm_file.exists() {
|
||||
debug!("writing to osm file: {osm_file:?}");
|
||||
let mut file = File::create(osm_file).expect("could not create OSM file");
|
||||
file.write_all(world.osm_db.to_xml().as_bytes())
|
||||
.expect("could not write OSM file");
|
||||
} else {
|
||||
debug!("not writing to OSM file {osm_file:?}");
|
||||
}
|
||||
|
||||
// if extracted file does not exist
|
||||
let cache_path = world.feature_cache_path().join(&world.osrm_digest);
|
||||
if cache_path.exists() {
|
||||
debug!("{cache_path:?} exists");
|
||||
} else {
|
||||
debug!("{cache_path:?} does not exist");
|
||||
}
|
||||
|
||||
fn parse_table_from_steps(table: &Option<&Table>) -> Vec<HashMap<String, String>> {
|
||||
// parse query data
|
||||
let table = &step.table.as_ref().expect("no query table specified");
|
||||
let table = table.expect("no query table specified");
|
||||
// the following lookup allows to define lat lon columns in any order
|
||||
let header = table.rows.first().expect("node locations table empty");
|
||||
// TODO: move to common functionality
|
||||
let test_cases: Vec<_> = table
|
||||
.rows
|
||||
.iter()
|
||||
@ -212,23 +187,22 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
row_map
|
||||
})
|
||||
.collect();
|
||||
test_cases
|
||||
// TODO: also return the header
|
||||
}
|
||||
|
||||
let data_path = cache_path.join(world.scenario_id.to_owned() + ".osrm");
|
||||
#[when(regex = r"^I request nearest( with flatbuffers|) I should get$")]
|
||||
fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
let request_with_flatbuffers = state == " with flatbuffers";
|
||||
|
||||
// TODO: this should not require a temporary and behave like the API of std::process
|
||||
let mut task = TaskStarter::new(world.routed_path().to_str().expect("task can be started"));
|
||||
task.arg(data_path.to_str().expect("data path unwrappable"));
|
||||
task.spawn_wait_till_ready("running and waiting for requests");
|
||||
assert!(task.is_ready());
|
||||
world.write_osm_file();
|
||||
world.extract_osm_file();
|
||||
|
||||
// TODO: move to generic http request handling struct
|
||||
let agent: Agent = ureq::AgentBuilder::new()
|
||||
.timeout_read(Duration::from_secs(5))
|
||||
.timeout_write(Duration::from_secs(5))
|
||||
.build();
|
||||
// parse query data
|
||||
let test_cases = parse_table_from_steps(&step.table.as_ref());
|
||||
|
||||
// parse and run test cases
|
||||
for test_case in test_cases {
|
||||
// run test cases
|
||||
for test_case in &test_cases {
|
||||
let query_location = world.get_location(
|
||||
test_case
|
||||
.get("in")
|
||||
@ -237,7 +211,10 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
.next()
|
||||
.expect("node name is one char long"),
|
||||
);
|
||||
let expected_location = world.get_location(
|
||||
|
||||
let response = world.nearest(&query_location, request_with_flatbuffers);
|
||||
|
||||
let expected_location = &world.get_location(
|
||||
test_case
|
||||
.get("out")
|
||||
.expect("node name is one char long")
|
||||
@ -246,27 +223,6 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
.expect("node name is one char long"),
|
||||
);
|
||||
|
||||
// debug!("{query_location:?} => {expected_location:?}");
|
||||
// run queries
|
||||
let mut url = format!(
|
||||
"http://localhost:5000/nearest/v1/{}/{:?},{:?}",
|
||||
world.profile, query_location.longitude, query_location.latitude
|
||||
);
|
||||
if request_with_flatbuffers {
|
||||
url += ".flatbuffers";
|
||||
}
|
||||
let call = agent.get(&url).call();
|
||||
|
||||
let body = match call {
|
||||
Ok(response) => response.into_reader(),
|
||||
Err(e) => panic!("http error: {e}"),
|
||||
};
|
||||
|
||||
let response = match request_with_flatbuffers {
|
||||
true => NearestResponse::from_flatbuffer(body),
|
||||
false => NearestResponse::from_json_reader(body),
|
||||
};
|
||||
|
||||
if test_case.contains_key("out") {
|
||||
// check that result node is (approximately) equivalent
|
||||
let result_location = response.waypoints[0].location();
|
||||
@ -290,6 +246,99 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
}
|
||||
}
|
||||
|
||||
#[when(regex = r"^I route( with flatbuffers|) I should get$")]
|
||||
fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
|
||||
let request_with_flatbuffers = state == " with flatbuffers";
|
||||
world.write_osm_file();
|
||||
world.extract_osm_file();
|
||||
// TODO: preprocess
|
||||
|
||||
let test_cases = parse_table_from_steps(&step.table.as_ref());
|
||||
for test_case in &test_cases {
|
||||
let from_location = world.get_location(
|
||||
test_case
|
||||
.get("from")
|
||||
.expect("node name is one char long")
|
||||
.chars()
|
||||
.next()
|
||||
.expect("node name is one char long"),
|
||||
);
|
||||
let to_location = world.get_location(
|
||||
test_case
|
||||
.get("to")
|
||||
.expect("node name is one char long")
|
||||
.chars()
|
||||
.next()
|
||||
.expect("node name is one char long"),
|
||||
);
|
||||
|
||||
let response = world.route(&from_location, &to_location, request_with_flatbuffers);
|
||||
|
||||
if test_case.contains_key("route") {
|
||||
// NOTE: the following code ports logic from JavaScript that checks only properties of the first route
|
||||
let route = response
|
||||
.routes
|
||||
.first()
|
||||
.expect("no route returned")
|
||||
.legs
|
||||
.first()
|
||||
.expect("legs required")
|
||||
.steps
|
||||
.iter()
|
||||
.map(|step| step.name.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
assert_eq!(*test_case.get("route").expect("msg"), route);
|
||||
}
|
||||
|
||||
if test_case.contains_key("pronunciations") {
|
||||
let pronunciations = response
|
||||
.routes
|
||||
.first()
|
||||
.expect("no route returned")
|
||||
.legs
|
||||
.first()
|
||||
.expect("legs required")
|
||||
.steps
|
||||
.iter()
|
||||
.map(|step| match &step.pronunciation {
|
||||
Some(p) => p.clone(),
|
||||
None => "".to_string(),
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
assert_eq!(
|
||||
*test_case.get("pronunciations").expect("msg"),
|
||||
pronunciations
|
||||
);
|
||||
}
|
||||
|
||||
if test_case.contains_key("ref") {
|
||||
let refs = response
|
||||
.routes
|
||||
.first()
|
||||
.expect("no route returned")
|
||||
.legs
|
||||
.first()
|
||||
.expect("legs required")
|
||||
.steps
|
||||
.iter()
|
||||
.map(|step| match &step.r#ref {
|
||||
Some(p) => p.clone(),
|
||||
None => "".to_string(),
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
assert_eq!(*test_case.get("ref").expect("msg"), refs);
|
||||
}
|
||||
// TODO: more checks need to be implemented
|
||||
|
||||
// TODO: check for unchecked test columns
|
||||
}
|
||||
|
||||
// unimplemented!("route");
|
||||
}
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
debug!("arguments: {:?}", args);
|
||||
@ -307,6 +356,8 @@ fn main() {
|
||||
future::ready(()).boxed()
|
||||
})
|
||||
.with_writer(DotWriter::default().normalized())
|
||||
.run("features/nearest/"),
|
||||
.filter_run("features/car/names.feature", |_, _, sc| {
|
||||
!sc.tags.iter().any(|t| t == "todo")
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user