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 {
|
if let Some((platform, compiler)) = match env::consts::OS {
|
||||||
"linux" if env::consts::ARCH == "x86_64" => Some((OS::Linux, ".clang++-15")),
|
"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 == "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, "")),
|
"windows" if env::consts::ARCH == "x86_64" => Some((OS::Windows, "")),
|
||||||
_ => None,
|
_ => 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);
|
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() {
|
let (flatc, location) = match Path::new(executable_path).exists() {
|
||||||
true => flatc_rust::Flatc::from_path(executable_path),
|
true => (flatc_rust::Flatc::from_path(executable_path), "local"),
|
||||||
false => flatc_rust::Flatc::from_env_path(),
|
false => (flatc_rust::Flatc::from_env_path(), "downloaded"),
|
||||||
};
|
};
|
||||||
assert!(flatc.check().is_ok());
|
assert!(flatc.check().is_ok());
|
||||||
|
let version = &flatc.version().unwrap();
|
||||||
|
build_println!(
|
||||||
|
"using {location} flatc v{} to compile schema files",
|
||||||
|
version.version()
|
||||||
|
);
|
||||||
flatc
|
flatc
|
||||||
.run(flatc_rust::Args {
|
.run(flatc_rust::Args {
|
||||||
extra: &["--gen-all"],
|
extra: &["--gen-all"],
|
||||||
|
@ -46,7 +46,7 @@ class OSRMBaseLoader{
|
|||||||
let retry = (err) => {
|
let retry = (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (retryCount < this.scope.OSRM_CONNECTION_RETRIES) {
|
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++;
|
retryCount++;
|
||||||
setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, timeoutMs);
|
setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, timeoutMs);
|
||||||
} else {
|
} else {
|
||||||
|
@ -29,6 +29,8 @@ module.exports = function () {
|
|||||||
var params = this.paramsToString(parameters);
|
var params = this.paramsToString(parameters);
|
||||||
this.query = baseUri + (params.length ? '/' + params : '');
|
this.query = baseUri + (params.length ? '/' + params : '');
|
||||||
|
|
||||||
|
console.log(this.query);
|
||||||
|
|
||||||
request(this.query, (err, res, body) => {
|
request(this.query, (err, res, body) => {
|
||||||
if (err && err.code === 'ECONNREFUSED') {
|
if (err && err.code === 'ECONNREFUSED') {
|
||||||
return cb(new Error('*** osrm-routed is not running.'));
|
return cb(new Error('*** osrm-routed is not running.'));
|
||||||
|
@ -37,7 +37,6 @@ impl Display for RoutingAlgorithm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: move to external file
|
// TODO: move to external file
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use cucumber::{cli, event, parser, Event};
|
use cucumber::{cli, event, parser, Event};
|
||||||
use std::{io::{self, Write}, time::Instant};
|
use std::{
|
||||||
|
io::{self, Write},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DotWriter {
|
pub struct DotWriter {
|
||||||
@ -75,8 +78,8 @@ impl<W: 'static> cucumber::Writer<W> for DotWriter {
|
|||||||
let p = format!("{} passed", self.step_passed).green();
|
let p = format!("{} passed", self.step_passed).green();
|
||||||
println!("{} steps ({f}, {s}, {p})", self.step_started);
|
println!("{} steps ({f}, {s}, {p})", self.step_started);
|
||||||
|
|
||||||
let elapsed = Instant::now() - self.start_time.unwrap();
|
let elapsed = Instant::now() - self.start_time.unwrap();
|
||||||
let minutes = elapsed.as_secs()/60;
|
let minutes = elapsed.as_secs() / 60;
|
||||||
let seconds = (elapsed.as_millis() % 60_000) as f64 / 1000.;
|
let seconds = (elapsed.as_millis() % 60_000) as f64 / 1000.;
|
||||||
println!("{}m{}s", minutes, seconds);
|
println!("{}m{}s", minutes, seconds);
|
||||||
}
|
}
|
||||||
@ -86,10 +89,10 @@ impl<W: 'static> cucumber::Writer<W> for DotWriter {
|
|||||||
scenarios: _,
|
scenarios: _,
|
||||||
steps: _,
|
steps: _,
|
||||||
parser_errors: _,
|
parser_errors: _,
|
||||||
} => {},
|
} => {}
|
||||||
event::Cucumber::Started => {
|
event::Cucumber::Started => {
|
||||||
self.start_time = Some(Instant::now());
|
self.start_time = Some(Instant::now());
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
Err(e) => println!("Error: {e}"),
|
Err(e) => println!("Error: {e}"),
|
||||||
}
|
}
|
||||||
|
@ -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;
|
use log::debug;
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@ use std::{env, fs, path::PathBuf};
|
|||||||
|
|
||||||
use log::debug;
|
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 {
|
pub fn md5_of_osrm_executables() -> chksum_md5::MD5 {
|
||||||
// create OSRM digest before any tests are executed since cucumber-rs doesn't have @beforeAll
|
// 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());
|
// debug!("md5: {}", md5.digest().to_hex_lowercase());
|
||||||
}
|
}
|
||||||
md5
|
md5
|
||||||
}
|
}
|
||||||
|
@ -2,23 +2,23 @@
|
|||||||
// functions to make nearest, route, etc calls
|
// functions to make nearest, route, etc calls
|
||||||
// fn nearest(arg1, ... argn) -> NearestResponse
|
// 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 {
|
// pub struct HttpRequest {
|
||||||
agent: Agent,
|
// agent: Agent,
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl HttpRequest {
|
// impl HttpRequest {
|
||||||
// pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {}
|
// // pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
// pub fn new() -> Self {
|
||||||
let agent = AgentBuilder::new()
|
// let agent = AgentBuilder::new()
|
||||||
.timeout_read(Duration::from_secs(5))
|
// .timeout_read(Duration::from_secs(5))
|
||||||
.timeout_write(Duration::from_secs(5))
|
// .timeout_write(Duration::from_secs(5))
|
||||||
.build();
|
// .build();
|
||||||
|
|
||||||
Self { agent }
|
// Self { agent }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
@ -3,24 +3,25 @@ use std::{
|
|||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TaskStarter {
|
pub struct LocalTask {
|
||||||
ready: bool,
|
ready: bool,
|
||||||
command: String,
|
command: String,
|
||||||
arguments: Vec<String>,
|
arguments: Vec<String>,
|
||||||
child: Option<Child>,
|
child: Option<Child>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskStarter {
|
impl LocalTask {
|
||||||
pub fn new(command: &str) -> Self {
|
pub fn new(command: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ready: false,
|
ready: false,
|
||||||
command: command.into(),
|
command: command,
|
||||||
arguments: Vec::new(),
|
arguments: Vec::new(),
|
||||||
child: None,
|
child: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_ready(&self) -> bool {
|
pub fn is_ready(&self) -> bool {
|
||||||
|
// TODO: also check that process is running
|
||||||
self.ready
|
self.ready
|
||||||
}
|
}
|
||||||
pub fn arg(&mut self, argument: &str) -> &mut Self {
|
pub fn arg(&mut self, argument: &str) -> &mut Self {
|
||||||
@ -39,6 +40,10 @@ impl TaskStarter {
|
|||||||
Err(e) => panic!("cannot spawn task: {e}"),
|
Err(e) => panic!("cannot spawn task: {e}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.child.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(output) = &mut self.child.as_mut().unwrap().stdout {
|
if let Some(output) = &mut self.child.as_mut().unwrap().stdout {
|
||||||
// implement with a timeout
|
// implement with a timeout
|
||||||
let mut reader = BufReader::new(output);
|
let mut reader = BufReader::new(output);
|
||||||
@ -54,10 +59,10 @@ impl TaskStarter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TaskStarter {
|
// impl Drop for LocalTask {
|
||||||
fn drop(&mut self) {
|
// fn drop(&mut self) {
|
||||||
if let Err(e) = self.child.as_mut().expect("can't access child").kill() {
|
// if let Err(e) = self.child.as_mut().expect("can't access child").kill() {
|
||||||
panic!("shutdown failed: {e}");
|
// panic!("shutdown failed: {e}");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
@ -8,27 +8,28 @@ pub mod file_util;
|
|||||||
pub mod hash_util;
|
pub mod hash_util;
|
||||||
pub mod http_request;
|
pub mod http_request;
|
||||||
pub mod lexicographic_file_walker;
|
pub mod lexicographic_file_walker;
|
||||||
|
pub mod local_task;
|
||||||
pub mod location;
|
pub mod location;
|
||||||
pub mod nearest_response;
|
pub mod nearest_response;
|
||||||
pub mod osm;
|
pub mod osm;
|
||||||
pub mod osm_db;
|
pub mod osm_db;
|
||||||
pub mod osrm_world;
|
pub mod osrm_world;
|
||||||
|
pub mod route_response;
|
||||||
pub mod scenario_id;
|
pub mod scenario_id;
|
||||||
pub mod task_starter;
|
|
||||||
|
|
||||||
// flatbuffer
|
// flatbuffer
|
||||||
#[allow(dead_code, unused_imports)]
|
#[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"]
|
#[path = "../../target/flatbuffers/position_generated.rs"]
|
||||||
pub mod position_flatbuffers;
|
pub mod position_flatbuffers;
|
||||||
#[allow(dead_code, unused_imports)]
|
#[allow(dead_code, unused_imports)]
|
||||||
#[path = "../../target/flatbuffers/waypoint_generated.rs"]
|
#[path = "../../target/flatbuffers/route_generated.rs"]
|
||||||
pub mod waypoint_flatbuffers;
|
pub mod route_flatbuffers;
|
||||||
#[allow(dead_code, unused_imports)]
|
#[allow(dead_code, unused_imports)]
|
||||||
#[path = "../../target/flatbuffers/table_generated.rs"]
|
#[path = "../../target/flatbuffers/table_generated.rs"]
|
||||||
pub mod table_flatbuffers;
|
pub mod table_flatbuffers;
|
||||||
#[allow(dead_code, unused_imports)]
|
#[allow(dead_code, unused_imports)]
|
||||||
#[path = "../../target/flatbuffers/route_generated.rs"]
|
#[path = "../../target/flatbuffers/waypoint_generated.rs"]
|
||||||
pub mod route_flatbuffers;
|
pub mod waypoint_flatbuffers;
|
||||||
#[allow(dead_code, unused_imports)]
|
|
||||||
#[path = "../../target/flatbuffers/fbresult_generated.rs"]
|
|
||||||
pub mod fbresult_flatbuffers;
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
|
|
||||||
use super::location::Location;
|
use super::location::Location;
|
||||||
|
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Waypoint {
|
pub struct Waypoint {
|
||||||
pub hint: String,
|
pub hint: String,
|
||||||
pub nodes: Vec<u64>,
|
pub nodes: Option<Vec<u64>>,
|
||||||
pub distance: f32,
|
pub distance: f32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
location: Location,
|
location: Location,
|
||||||
@ -66,7 +66,7 @@ impl NearestResponse {
|
|||||||
longitude: location.longitude(),
|
longitude: location.longitude(),
|
||||||
};
|
};
|
||||||
let nodes = wp.nodes().expect("waypoint mus have nodes");
|
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();
|
let distance = wp.distance();
|
||||||
|
|
||||||
Waypoint {
|
Waypoint {
|
||||||
|
@ -93,15 +93,19 @@ mod tests {
|
|||||||
|
|
||||||
let mut node1 = OSMNode {
|
let mut node1 = OSMNode {
|
||||||
id: 123,
|
id: 123,
|
||||||
lat: 50.1234,
|
location: Location {
|
||||||
lon: 8.9876,
|
longitude: 8.9876,
|
||||||
|
latitude: 50.1234,
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut node2 = OSMNode {
|
let mut node2 = OSMNode {
|
||||||
id: 321,
|
id: 321,
|
||||||
lat: 50.1234,
|
location: Location {
|
||||||
lon: 8.9876,
|
longitude: 8.9876,
|
||||||
|
latitude: 50.1234,
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
node1.add_tag("name", "a");
|
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 cucumber::World;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{create_dir_all, File},
|
fs::{create_dir_all, File},
|
||||||
|
io::Write,
|
||||||
path::PathBuf,
|
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 struct OSRMWorld {
|
||||||
pub feature_path: Option<PathBuf>,
|
pub feature_path: Option<PathBuf>,
|
||||||
pub scenario_id: String,
|
pub scenario_id: String,
|
||||||
@ -25,6 +35,36 @@ pub struct OSRMWorld {
|
|||||||
pub extraction_parameters: Vec<String>,
|
pub extraction_parameters: Vec<String>,
|
||||||
|
|
||||||
pub request_with_flatbuffers: bool,
|
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 {
|
impl OSRMWorld {
|
||||||
@ -59,7 +99,11 @@ impl OSRMWorld {
|
|||||||
.ancestors()
|
.ancestors()
|
||||||
.find(|p| p.ends_with("features"))
|
.find(|p| p.ends_with("features"))
|
||||||
.expect(".feature files reside in a directory tree with the root name '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");
|
assert!(routed_path.exists(), "osrm-routed binary not found");
|
||||||
routed_path
|
routed_path
|
||||||
}
|
}
|
||||||
@ -119,4 +163,112 @@ impl OSRMWorld {
|
|||||||
}
|
}
|
||||||
self.known_locations.insert(name, location);
|
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;
|
mod common;
|
||||||
|
|
||||||
use cheap_ruler::CheapRuler;
|
use cheap_ruler::CheapRuler;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use common::{
|
use common::{
|
||||||
cli_arguments::Args, dot_writer::DotWriter, f64_utils::approx_equal,
|
cli_arguments::Args, dot_writer::DotWriter, f64_utils::approx_equal,
|
||||||
hash_util::md5_of_osrm_executables, location::Location, nearest_response::NearestResponse,
|
hash_util::md5_of_osrm_executables, location::Location, osm::OSMWay, osrm_world::OSRMWorld,
|
||||||
osm::OSMWay, osrm_world::OSRMWorld, task_starter::TaskStarter,
|
|
||||||
};
|
};
|
||||||
use core::panic;
|
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 futures::{future, FutureExt};
|
||||||
use geo_types::point;
|
use geo_types::Point;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::{collections::HashMap, fs::File, io::Write, time::Duration};
|
use std::collections::HashMap;
|
||||||
use ureq::Agent;
|
|
||||||
|
|
||||||
const DEFAULT_ORIGIN: [f64; 2] = [1., 1.]; // TODO: move to world?
|
fn offset_origin_by(dx: f32, dy: f32, origin: Location, grid_size: f32) -> Location {
|
||||||
const DEFAULT_GRID_SIZE: f64 = 100.; // TODO: move to world?
|
let ruler = CheapRuler::new(origin.latitude, cheap_ruler::DistanceUnit::Meters);
|
||||||
|
|
||||||
fn offset_origin_by(dx: f64, dy: f64) -> Location {
|
|
||||||
let ruler = CheapRuler::new(DEFAULT_ORIGIN[1], cheap_ruler::DistanceUnit::Meters);
|
|
||||||
let loc = ruler.offset(
|
let loc = ruler.offset(
|
||||||
&point!(DEFAULT_ORIGIN),
|
&Point::new(origin.longitude, origin.latitude),
|
||||||
dx * DEFAULT_GRID_SIZE,
|
dx * grid_size,
|
||||||
dy * DEFAULT_GRID_SIZE,
|
dy * grid_size,
|
||||||
); //TODO: needs to be world's gridSize, not the local one
|
); //TODO: needs to be world's gridSize, not the local one
|
||||||
Location {
|
Location {
|
||||||
latitude: loc.y() as f32,
|
latitude: loc.y(),
|
||||||
longitude: loc.x() as f32,
|
longitude: loc.x(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,12 +91,16 @@ fn set_node_map(world: &mut OSRMWorld, step: &Step) {
|
|||||||
.filter(|(_column_index, charater)| *charater != ' ')
|
.filter(|(_column_index, charater)| *charater != ' ')
|
||||||
.for_each(|(column_index, name)| {
|
.for_each(|(column_index, name)| {
|
||||||
// This ports the logic from previous implementations.
|
// This ports the logic from previous implementations.
|
||||||
let location =
|
let location = offset_origin_by(
|
||||||
offset_origin_by(column_index as f64 * 0.5, -(row_index as f64 - 1.));
|
column_index as f32 * 0.5,
|
||||||
|
-(row_index as f32 - 1.),
|
||||||
|
world.origin,
|
||||||
|
world.grid_size,
|
||||||
|
);
|
||||||
match name {
|
match name {
|
||||||
'0'...'9' => world.add_location(name, location),
|
'0'...'9' => world.add_location(name, location),
|
||||||
'a'...'z' => world.add_osm_node(name, location, None),
|
'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);
|
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")]
|
#[given(regex = "the ways")]
|
||||||
fn set_ways(world: &mut OSRMWorld, step: &Step) {
|
fn set_ways(world: &mut OSRMWorld, step: &Step) {
|
||||||
// debug!("using profile: {profile}");
|
// debug!("using profile: {profile}");
|
||||||
@ -159,43 +164,13 @@ fn set_ways(world: &mut OSRMWorld, step: &Step) {
|
|||||||
} else {
|
} else {
|
||||||
debug!("no table found {step:#?}");
|
debug!("no table found {step:#?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug!("{}", world.osm_db.to_xml())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[when("I request nearest I should get")]
|
fn parse_table_from_steps(table: &Option<&Table>) -> Vec<HashMap<String, String>> {
|
||||||
#[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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse query data
|
// 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
|
// the following lookup allows to define lat lon columns in any order
|
||||||
let header = table.rows.first().expect("node locations table empty");
|
let header = table.rows.first().expect("node locations table empty");
|
||||||
// TODO: move to common functionality
|
|
||||||
let test_cases: Vec<_> = table
|
let test_cases: Vec<_> = table
|
||||||
.rows
|
.rows
|
||||||
.iter()
|
.iter()
|
||||||
@ -212,23 +187,22 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
|||||||
row_map
|
row_map
|
||||||
})
|
})
|
||||||
.collect();
|
.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
|
world.write_osm_file();
|
||||||
let mut task = TaskStarter::new(world.routed_path().to_str().expect("task can be started"));
|
world.extract_osm_file();
|
||||||
task.arg(data_path.to_str().expect("data path unwrappable"));
|
|
||||||
task.spawn_wait_till_ready("running and waiting for requests");
|
|
||||||
assert!(task.is_ready());
|
|
||||||
|
|
||||||
// TODO: move to generic http request handling struct
|
// parse query data
|
||||||
let agent: Agent = ureq::AgentBuilder::new()
|
let test_cases = parse_table_from_steps(&step.table.as_ref());
|
||||||
.timeout_read(Duration::from_secs(5))
|
|
||||||
.timeout_write(Duration::from_secs(5))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// parse and run test cases
|
// run test cases
|
||||||
for test_case in test_cases {
|
for test_case in &test_cases {
|
||||||
let query_location = world.get_location(
|
let query_location = world.get_location(
|
||||||
test_case
|
test_case
|
||||||
.get("in")
|
.get("in")
|
||||||
@ -237,7 +211,10 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
|
|||||||
.next()
|
.next()
|
||||||
.expect("node name is one char long"),
|
.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
|
test_case
|
||||||
.get("out")
|
.get("out")
|
||||||
.expect("node name is one char long")
|
.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"),
|
.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") {
|
if test_case.contains_key("out") {
|
||||||
// check that result node is (approximately) equivalent
|
// check that result node is (approximately) equivalent
|
||||||
let result_location = response.waypoints[0].location();
|
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() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
debug!("arguments: {:?}", args);
|
debug!("arguments: {:?}", args);
|
||||||
@ -307,6 +356,8 @@ fn main() {
|
|||||||
future::ready(()).boxed()
|
future::ready(()).boxed()
|
||||||
})
|
})
|
||||||
.with_writer(DotWriter::default().normalized())
|
.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