Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d6c27762c | |||
| 2596a9354f | |||
| eb15e5bde4 | |||
| 51c7421ebc | |||
| bc56a1f8e6 | |||
| 4f36d2dce1 | |||
| 772f9ccc21 | |||
| 85eb7a9383 | |||
| 9457f36f49 | |||
| 37f5780472 | |||
| d985459696 | |||
| edffbae777 | |||
| 77477a86dd | |||
| eba856af17 | |||
| c9b8462754 | |||
| a7956f65ca | |||
| 38bd6b547d | |||
| 926a6c1849 | |||
| aa1b47c596 | |||
| 9003881a77 | |||
| f1ad997a4b | |||
| 20708b0ff8 | |||
| 995eaec555 | |||
| beaaa597d4 | |||
| 96e90ce321 | |||
| 7f7aa3dc2c | |||
| 9fff420a1a | |||
| b04f3200b1 | |||
| 5cf37a0c7c | |||
| 59cbb08c0e | |||
| 9b88056062 | |||
| 3808617142 | |||
| 32dffeb54d | |||
| 3f14453f5b | |||
| 3ba8001807 | |||
| 6960bd42c5 | |||
| 699ac31383 | |||
| 797db7a097 | |||
| 63c9d599f9 | |||
| 6faa5f45e8 | |||
| 86d4db97ca | |||
| 844685102d | |||
| 97bd968b2a | |||
| 80eb71ded5 |
@@ -653,7 +653,7 @@ jobs:
|
|||||||
benchmarks:
|
benchmarks:
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
needs: [format-taginfo-docs]
|
needs: [format-taginfo-docs]
|
||||||
runs-on: self-hosted
|
runs-on: ubuntu-24.04
|
||||||
env:
|
env:
|
||||||
CCOMPILER: clang-16
|
CCOMPILER: clang-16
|
||||||
CXXCOMPILER: clang++-16
|
CXXCOMPILER: clang++-16
|
||||||
@@ -664,17 +664,37 @@ jobs:
|
|||||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
RUN_BIG_BENCHMARK: ${{ contains(github.event.pull_request.labels.*.name, 'Performance') }}
|
RUN_BIG_BENCHMARK: ${{ contains(github.event.pull_request.labels.*.name, 'Performance') }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Enable data.osm.pbf cache
|
||||||
|
if: ${{ ! env.RUN_BIG_BENCHMARK }}
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/data.osm.pbf
|
||||||
|
key: v1-data-osm-pbf
|
||||||
|
restore-keys: |
|
||||||
|
v1-data-osm-pbf
|
||||||
|
- name: Enable compiler cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.ccache
|
||||||
|
key: v1-ccache-benchmarks-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
v1-ccache-benchmarks-
|
||||||
|
- name: Enable Conan cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.conan
|
||||||
|
key: v1-conan-benchmarks-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
v1-conan-benchmarks-
|
||||||
- name: Checkout PR Branch
|
- name: Checkout PR Branch
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
path: pr
|
path: pr
|
||||||
- name: Activate virtualenv
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python3 -m venv .venv
|
python3 -m pip install "conan<2.0.0" "requests==2.31.0" "numpy==1.26.4" --break-system-packages
|
||||||
source .venv/bin/activate
|
sudo apt-get update -y && sudo apt-get install ccache
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
|
||||||
pip install "conan<2.0.0" "requests==2.31.0" "numpy==1.26.4"
|
|
||||||
- name: Prepare data
|
- name: Prepare data
|
||||||
run: |
|
run: |
|
||||||
if [ "$RUN_BIG_BENCHMARK" = "true" ]; then
|
if [ "$RUN_BIG_BENCHMARK" = "true" ]; then
|
||||||
@@ -702,67 +722,50 @@ jobs:
|
|||||||
path: base
|
path: base
|
||||||
- name: Build Base Branch
|
- name: Build Base Branch
|
||||||
run: |
|
run: |
|
||||||
cd base
|
|
||||||
npm ci --ignore-scripts
|
|
||||||
cd ..
|
|
||||||
mkdir base/build
|
mkdir base/build
|
||||||
cd base/build
|
cd base/build
|
||||||
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_NODE_BINDINGS=ON ..
|
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||||
make -j$(nproc)
|
make -j$(nproc)
|
||||||
make -j$(nproc) benchmarks
|
make -j$(nproc) benchmarks
|
||||||
cd ..
|
cd ..
|
||||||
make -C test/data
|
make -C test/data
|
||||||
- name: Build PR Branch
|
- name: Build PR Branch
|
||||||
run: |
|
run: |
|
||||||
cd pr
|
|
||||||
npm ci --ignore-scripts
|
|
||||||
cd ..
|
|
||||||
mkdir -p pr/build
|
mkdir -p pr/build
|
||||||
cd pr/build
|
cd pr/build
|
||||||
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_NODE_BINDINGS=ON ..
|
cmake -DENABLE_CONAN=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||||
make -j$(nproc)
|
make -j$(nproc)
|
||||||
make -j$(nproc) benchmarks
|
make -j$(nproc) benchmarks
|
||||||
cd ..
|
cd ..
|
||||||
make -C test/data
|
make -C test/data
|
||||||
# we run benchmarks in tmpfs to avoid impact of disk IO
|
# we run benchmarks in tmpfs to avoid impact of disk IO
|
||||||
- name: Create folder for tmpfs
|
- name: Create folder for tmpfs
|
||||||
run: |
|
run: mkdir -p /opt/benchmarks
|
||||||
# if by any chance it was mounted before(e.g. due to previous job failed), unmount it
|
|
||||||
sudo umount ~/benchmarks | true
|
|
||||||
rm -rf ~/benchmarks
|
|
||||||
mkdir -p ~/benchmarks
|
|
||||||
# see https://llvm.org/docs/Benchmarking.html
|
|
||||||
- name: Run PR Benchmarks
|
- name: Run PR Benchmarks
|
||||||
run: |
|
run: |
|
||||||
sudo cset shield -c 2-3 -k on
|
sudo mount -t tmpfs -o size=4g none /opt/benchmarks
|
||||||
sudo mount -t tmpfs -o size=4g none ~/benchmarks
|
cp -rf pr/build /opt/benchmarks/build
|
||||||
cp -rf pr/build ~/benchmarks/build
|
mkdir -p /opt/benchmarks/test
|
||||||
cp -rf pr/lib ~/benchmarks/lib
|
cp -rf pr/test/data /opt/benchmarks/test/data
|
||||||
mkdir -p ~/benchmarks/test
|
cp -rf pr/profiles /opt/benchmarks/profiles
|
||||||
cp -rf pr/test/data ~/benchmarks/test/data
|
|
||||||
cp -rf pr/profiles ~/benchmarks/profiles
|
|
||||||
|
|
||||||
sudo cset shield --exec -- ./pr/scripts/ci/run_benchmarks.sh -f ~/benchmarks -r $(pwd)/pr_results -s $(pwd)/pr -b ~/benchmarks/build -o ~/data.osm.pbf -g ~/gps_traces.csv
|
./pr/scripts/ci/run_benchmarks.sh -f /opt/benchmarks -r $(pwd)/pr_results -s $(pwd)/pr -b /opt/benchmarks/build -o ~/data.osm.pbf -g ~/gps_traces.csv
|
||||||
sudo umount ~/benchmarks
|
sudo umount /opt/benchmarks
|
||||||
sudo cset shield --reset
|
|
||||||
- name: Run Base Benchmarks
|
- name: Run Base Benchmarks
|
||||||
run: |
|
run: |
|
||||||
sudo cset shield -c 2-3 -k on
|
sudo mount -t tmpfs -o size=4g none /opt/benchmarks
|
||||||
sudo mount -t tmpfs -o size=4g none ~/benchmarks
|
cp -rf base/build /opt/benchmarks/build
|
||||||
cp -rf base/build ~/benchmarks/build
|
mkdir -p /opt/benchmarks/test
|
||||||
cp -rf base/lib ~/benchmarks/lib
|
cp -rf base/test/data /opt/benchmarks/test/data
|
||||||
mkdir -p ~/benchmarks/test
|
cp -rf base/profiles /opt/benchmarks/profiles
|
||||||
cp -rf base/test/data ~/benchmarks/test/data
|
|
||||||
cp -rf base/profiles ~/benchmarks/profiles
|
|
||||||
|
|
||||||
# TODO: remove it when base branch will have this file at needed location
|
# TODO: remove it when base branch will have this file at needed location
|
||||||
if [ ! -f ~/benchmarks/test/data/portugal_to_korea.json ]; then
|
if [ ! -f /opt/benchmarks/test/data/portugal_to_korea.json ]; then
|
||||||
cp base/src/benchmarks/portugal_to_korea.json ~/benchmarks/test/data/portugal_to_korea.json
|
cp base/src/benchmarks/portugal_to_korea.json /opt/benchmarks/test/data/portugal_to_korea.json
|
||||||
fi
|
fi
|
||||||
# we intentionally use scripts from PR branch to be able to update them and see results in the same PR
|
# we intentionally use scripts from PR branch to be able to update them and see results in the same PR
|
||||||
sudo cset shield --exec -- cset shield --exec -- ./pr/scripts/ci/run_benchmarks.sh -f ~/benchmarks -r $(pwd)/base_results -s $(pwd)/pr -b ~/benchmarks/build -o ~/data.osm.pbf -g ~/gps_traces.csv
|
./pr/scripts/ci/run_benchmarks.sh -f /opt/benchmarks -r $(pwd)/base_results -s $(pwd)/pr -b /opt/benchmarks/build -o ~/data.osm.pbf -g ~/gps_traces.csv
|
||||||
sudo umount ~/benchmarks
|
sudo umount /opt/benchmarks
|
||||||
sudo cset shield --reset
|
|
||||||
- name: Post Benchmark Results
|
- name: Post Benchmark Results
|
||||||
run: |
|
run: |
|
||||||
python3 pr/scripts/ci/post_benchmark_results.py base_results pr_results
|
python3 pr/scripts/ci/post_benchmark_results.py base_results pr_results
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Thumbs.db
|
|||||||
/test/data/ch
|
/test/data/ch
|
||||||
/test/data/mld
|
/test/data/mld
|
||||||
/cmake/postinst
|
/cmake/postinst
|
||||||
|
/target
|
||||||
|
|
||||||
# Eclipse related files #
|
# Eclipse related files #
|
||||||
#########################
|
#########################
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# Unreleased
|
# Unreleased
|
||||||
- Changes from 5.27.1
|
- Changes from 5.27.1
|
||||||
- Features
|
- Features
|
||||||
- ADDED: Route pedestrians over highway=platform [#6993](https://github.com/Project-OSRM/osrm-backend/pull/6993)
|
|
||||||
- REMOVED: Remove all core-CH left-overs [#6920](https://github.com/Project-OSRM/osrm-backend/pull/6920)
|
- REMOVED: Remove all core-CH left-overs [#6920](https://github.com/Project-OSRM/osrm-backend/pull/6920)
|
||||||
- ADDED: Add support for a keepalive_timeout flag. [#6674](https://github.com/Project-OSRM/osrm-backend/pull/6674)
|
- ADDED: Add support for a keepalive_timeout flag. [#6674](https://github.com/Project-OSRM/osrm-backend/pull/6674)
|
||||||
- ADDED: Add support for a default_radius flag. [#6575](https://github.com/Project-OSRM/osrm-backend/pull/6575)
|
- ADDED: Add support for a default_radius flag. [#6575](https://github.com/Project-OSRM/osrm-backend/pull/6575)
|
||||||
|
|||||||
@@ -353,9 +353,6 @@ if(ENABLE_CONAN)
|
|||||||
KEEP_RPATHS
|
KEEP_RPATHS
|
||||||
NO_OUTPUT_DIRS
|
NO_OUTPUT_DIRS
|
||||||
OPTIONS boost:without_stacktrace=True # Apple Silicon cross-compilation fails without it
|
OPTIONS boost:without_stacktrace=True # Apple Silicon cross-compilation fails without it
|
||||||
onetbb:tbbmalloc=False
|
|
||||||
onetbb:tbbproxy=False
|
|
||||||
onetbb:tbbbind=False
|
|
||||||
BUILD missing
|
BUILD missing
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Generated
+2582
File diff suppressed because it is too large
Load Diff
+37
@@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
name = "osrm-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cheap-ruler = "0.4.0"
|
||||||
|
chksum-md5 = "0.0.0"
|
||||||
|
clap = "4.5.7"
|
||||||
|
colored = "2.1.0"
|
||||||
|
cucumber = { version = "0.21.1", features = ["tracing"] }
|
||||||
|
flatbuffers = "24.3.25"
|
||||||
|
futures = "0.3.30"
|
||||||
|
geo-types = "0.7.13"
|
||||||
|
help = "0.0.0"
|
||||||
|
log = "0.4.21"
|
||||||
|
reqwest = {version = "0.12.5", features = ["blocking"] }
|
||||||
|
serde = { version = "1.0.203", features = ["serde_derive"] }
|
||||||
|
serde_json = "1.0.118"
|
||||||
|
xml-builder = "0.5.2"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "cucumber"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
flatc-rust = "0.2.0"
|
||||||
|
reqwest = {version = "0.12.5", features = ["blocking"] }
|
||||||
|
serde = { version = "1.0.203", features = ["serde_derive"] }
|
||||||
|
toml = "0.8.14"
|
||||||
|
zip-extract = "0.1.3"
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{collections::HashMap, path::Path};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
macro_rules! build_println {
|
||||||
|
($($tokens: tt)*) => {
|
||||||
|
println!("cargo:warning=\r\x1b[32;1m {}", format!($($tokens)*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum OS {
|
||||||
|
Mac,
|
||||||
|
MacIntel,
|
||||||
|
Linux,
|
||||||
|
Windows,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for OS {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum DependencyValue {
|
||||||
|
String(String),
|
||||||
|
Object {
|
||||||
|
version: String,
|
||||||
|
features: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct CargoToml {
|
||||||
|
dependencies: HashMap<String, DependencyValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let cargo_toml_raw = include_str!("Cargo.toml");
|
||||||
|
let cargo_toml: CargoToml = toml::from_str(cargo_toml_raw).unwrap();
|
||||||
|
|
||||||
|
let version = match cargo_toml
|
||||||
|
.dependencies
|
||||||
|
.get("flatbuffers")
|
||||||
|
.expect("Must have dependency flatbuffers")
|
||||||
|
{
|
||||||
|
DependencyValue::String(s) => s,
|
||||||
|
DependencyValue::Object {
|
||||||
|
version,
|
||||||
|
features: _,
|
||||||
|
} => version,
|
||||||
|
};
|
||||||
|
|
||||||
|
let executable_path = match env::consts::OS {
|
||||||
|
"windows" => "target/flatc.exe",
|
||||||
|
_ => "target/flatc",
|
||||||
|
};
|
||||||
|
|
||||||
|
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 == "aarch64" => Some((OS::Mac, "")),
|
||||||
|
"windows" if env::consts::ARCH == "x86_64" => Some((OS::Windows, "")),
|
||||||
|
_ => None,
|
||||||
|
} {
|
||||||
|
let url = format!("https://github.com/google/flatbuffers/releases/download/v{version}/{platform}.flatc.binary{compiler}.zip");
|
||||||
|
|
||||||
|
if !Path::new(executable_path).exists() {
|
||||||
|
build_println!("Downloading flatc executable from {url}");
|
||||||
|
let response = match reqwest::blocking::get(url) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => panic!("network error during build: {e}"),
|
||||||
|
};
|
||||||
|
let archive = match response.bytes() {
|
||||||
|
Ok(archive) => archive,
|
||||||
|
Err(e) => panic!("could not retrieve byte stream during build: {e}"),
|
||||||
|
};
|
||||||
|
let target_dir = PathBuf::from("target");
|
||||||
|
zip_extract::extract(Cursor::new(archive), &target_dir, true)
|
||||||
|
.expect("flatc cannot be unpacked")
|
||||||
|
} else {
|
||||||
|
build_println!("cached flatc executable found, not downloading");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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, location) = match Path::new(executable_path).exists() {
|
||||||
|
true => (flatc_rust::Flatc::from_path(executable_path), "downloaded"),
|
||||||
|
false => (flatc_rust::Flatc::from_env_path(), "locally installed"),
|
||||||
|
};
|
||||||
|
assert!(flatc.check().is_ok());
|
||||||
|
let version = &flatc.version().unwrap();
|
||||||
|
build_println!(
|
||||||
|
"Using {location} flatc v{} to compile schema files ({executable_path})",
|
||||||
|
version.version()
|
||||||
|
);
|
||||||
|
flatc
|
||||||
|
.run(flatc_rust::Args {
|
||||||
|
extra: &["--gen-all"],
|
||||||
|
inputs: &[
|
||||||
|
Path::new("generated/include/engine/api/flatbuffers/position.fbs"),
|
||||||
|
Path::new("generated/include/engine/api/flatbuffers/waypoint.fbs"),
|
||||||
|
Path::new("generated/include/engine/api/flatbuffers/route.fbs"),
|
||||||
|
Path::new("generated/include/engine/api/flatbuffers/table.fbs"),
|
||||||
|
Path::new("generated/include/engine/api/flatbuffers/fbresult.fbs"),
|
||||||
|
],
|
||||||
|
out_dir: Path::new("target/flatbuffers/"),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.expect("flatc failed generating files");
|
||||||
|
}
|
||||||
+1
-1
@@ -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.
|
Finds the fastest route between coordinates in the supplied order.
|
||||||
|
|
||||||
```endpoint
|
```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:
|
In addition to the [general options](#general-options) the following options are supported for this service:
|
||||||
|
|||||||
@@ -60,5 +60,5 @@ Feature: Car - Handle driving
|
|||||||
When I route I should get
|
When I route I should get
|
||||||
| from | to | route | modes | speed | turns |
|
| from | to | route | modes | speed | turns |
|
||||||
| a | g | abc,cde,efg,efg | driving,driving,driving,driving | 7 km/h | depart,new name right,new name left,arrive |
|
| a | g | abc,cde,efg,efg | driving,driving,driving,driving | 7 km/h | depart,new name right,new name left,arrive |
|
||||||
| c | e | cde,cde | driving,driving | 2 km/h | depart,arrive |
|
| c | e | cde,cde | driving,driving | 2.4 km/h | depart,arrive |
|
||||||
| e | c | cde,cde | driving,driving | 2 km/h | depart,arrive |
|
| e | c | cde,cde | driving,driving | 2.4 km/h | depart,arrive |
|
||||||
|
|||||||
@@ -113,12 +113,12 @@ Feature: Car - Destination only, no passing through
|
|||||||
Scenario: Car - Routing around a way that becomes destination only
|
Scenario: Car - Routing around a way that becomes destination only
|
||||||
Given the node map
|
Given the node map
|
||||||
"""
|
"""
|
||||||
a---c---b
|
a---c---b
|
||||||
+ \
|
+ \
|
||||||
+ |
|
+ |
|
||||||
d |
|
d |
|
||||||
1 |
|
1 |
|
||||||
\___e
|
\___e
|
||||||
"""
|
"""
|
||||||
|
|
||||||
And the ways
|
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
|
Scenario: Car - Routing through a parking lot tagged access=destination,service
|
||||||
Given the node map
|
Given the node map
|
||||||
"""
|
"""
|
||||||
a----c++++b+++g------h---i
|
a----c++++b+++g------h---i
|
||||||
| + + + /
|
| + + + /
|
||||||
| + + + /
|
| + + + /
|
||||||
| + + + /
|
| + + + /
|
||||||
| d++++e+f /
|
| d++++e+f /
|
||||||
z--------------y
|
z--------------y
|
||||||
"""
|
"""
|
||||||
|
|
||||||
And the ways
|
And the ways
|
||||||
@@ -165,12 +165,12 @@ Feature: Car - Destination only, no passing through
|
|||||||
Given a grid size of 20 meters
|
Given a grid size of 20 meters
|
||||||
Given the node map
|
Given the node map
|
||||||
"""
|
"""
|
||||||
a---c---b
|
a---c---b
|
||||||
:
|
:
|
||||||
x
|
x
|
||||||
:
|
:
|
||||||
d
|
d
|
||||||
\__e
|
\__e
|
||||||
"""
|
"""
|
||||||
|
|
||||||
And the ways
|
And the ways
|
||||||
|
|||||||
@@ -26,15 +26,7 @@ Feature: Foot - Access tags on ways
|
|||||||
| motorway | no | | |
|
| motorway | no | | |
|
||||||
| motorway | no | yes | x |
|
| motorway | no | yes | x |
|
||||||
| motorway | no | no | |
|
| motorway | no | no | |
|
||||||
| platform | | | x |
|
|
||||||
| platform | | yes | x |
|
|
||||||
| platform | | no | |
|
|
||||||
| platform | yes | | x |
|
|
||||||
| platform | yes | yes | x |
|
|
||||||
| platform | yes | no | |
|
|
||||||
| platform | no | | |
|
|
||||||
| platform | no | yes | x |
|
|
||||||
| platform | no | no | |
|
|
||||||
|
|
||||||
Scenario: Foot - Overwriting implied acccess on ways
|
Scenario: Foot - Overwriting implied acccess on ways
|
||||||
Then routability should be
|
Then routability should be
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#define MEMINFO_HPP
|
#define MEMINFO_HPP
|
||||||
|
|
||||||
#include "util/log.hpp"
|
#include "util/log.hpp"
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
@@ -11,27 +10,18 @@
|
|||||||
namespace osrm::util
|
namespace osrm::util
|
||||||
{
|
{
|
||||||
|
|
||||||
inline size_t PeakRAMUsedInBytes()
|
inline void DumpMemoryStats()
|
||||||
{
|
{
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
rusage usage;
|
rusage usage;
|
||||||
getrusage(RUSAGE_SELF, &usage);
|
getrusage(RUSAGE_SELF, &usage);
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
// Under linux, ru.maxrss is in kb
|
// Under linux, ru.maxrss is in kb
|
||||||
return usage.ru_maxrss * 1024;
|
util::Log() << "RAM: peak bytes used: " << usage.ru_maxrss * 1024;
|
||||||
#else // __linux__
|
#else // __linux__
|
||||||
// Under BSD systems (OSX), it's in bytes
|
// Under BSD systems (OSX), it's in bytes
|
||||||
return usage.ru_maxrss;
|
util::Log() << "RAM: peak bytes used: " << usage.ru_maxrss;
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
#else // _WIN32
|
|
||||||
return 0;
|
|
||||||
#endif // _WIN32
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void DumpMemoryStats()
|
|
||||||
{
|
|
||||||
#ifndef _WIN32
|
|
||||||
util::Log() << "RAM: peak bytes used: " << PeakRAMUsedInBytes();
|
|
||||||
#else // _WIN32
|
#else // _WIN32
|
||||||
util::Log() << "RAM: peak bytes used: <not implemented on Windows>";
|
util::Log() << "RAM: peak bytes used: <not implemented on Windows>";
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|||||||
Generated
+1
-12
@@ -10,8 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
"@mapbox/node-pre-gyp": "^1.0.11"
|
||||||
"seedrandom": "^3.0.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.18.10",
|
"@babel/cli": "^7.18.10",
|
||||||
@@ -14653,11 +14652,6 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"node_modules/seedrandom": {
|
|
||||||
"version": "3.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
|
||||||
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "5.7.2",
|
"version": "5.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
@@ -30302,11 +30296,6 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"seedrandom": {
|
|
||||||
"version": "3.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
|
||||||
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
|
|
||||||
},
|
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.7.2",
|
"version": "5.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
|
|||||||
+4
-7
@@ -4,8 +4,7 @@
|
|||||||
"private": false,
|
"private": false,
|
||||||
"description": "The Open Source Routing Machine is a high performance routing engine written in C++ designed to run on OpenStreetMap data.",
|
"description": "The Open Source Routing Machine is a high performance routing engine written in C++ designed to run on OpenStreetMap data.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
"@mapbox/node-pre-gyp": "^1.0.11"
|
||||||
"seedrandom": "^3.0.5"
|
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
@@ -58,7 +57,6 @@
|
|||||||
"jsonpath": "^1.1.1",
|
"jsonpath": "^1.1.1",
|
||||||
"mkdirp": "^0.5.6",
|
"mkdirp": "^0.5.6",
|
||||||
"node-addon-api": "^5.0.0",
|
"node-addon-api": "^5.0.0",
|
||||||
"node-cmake": "^2.5.1",
|
|
||||||
"node-timeout": "0.0.4",
|
"node-timeout": "0.0.4",
|
||||||
"polyline": "^0.2.0",
|
"polyline": "^0.2.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
@@ -66,13 +64,12 @@
|
|||||||
"tape": "^4.16.0",
|
"tape": "^4.16.0",
|
||||||
"turf": "^3.0.14",
|
"turf": "^3.0.14",
|
||||||
"uglify-js": "^3.17.0",
|
"uglify-js": "^3.17.0",
|
||||||
"xmlbuilder": "^4.2.1"
|
"xmlbuilder": "^4.2.1",
|
||||||
|
"node-cmake": "^2.5.1"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"binary": {
|
"binary": {
|
||||||
"napi_versions": [
|
"napi_versions": [8],
|
||||||
8
|
|
||||||
],
|
|
||||||
"module_name": "node_osrm",
|
"module_name": "node_osrm",
|
||||||
"module_path": "./lib/binding_napi_v{napi_build_version}/",
|
"module_path": "./lib/binding_napi_v{napi_build_version}/",
|
||||||
"host": "https://github.com",
|
"host": "https://github.com",
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ function setup()
|
|||||||
path = walking_speed,
|
path = walking_speed,
|
||||||
steps = walking_speed,
|
steps = walking_speed,
|
||||||
pedestrian = walking_speed,
|
pedestrian = walking_speed,
|
||||||
platform = walking_speed,
|
|
||||||
footway = walking_speed,
|
footway = walking_speed,
|
||||||
pier = walking_speed,
|
pier = walking_speed,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,211 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const readline = require('readline');
|
|
||||||
const seedrandom = require('seedrandom');
|
|
||||||
|
|
||||||
|
|
||||||
let RNG;
|
|
||||||
|
|
||||||
class GPSData {
|
|
||||||
constructor(gpsTracesFilePath) {
|
|
||||||
this.tracks = {};
|
|
||||||
this.coordinates = [];
|
|
||||||
this.trackIds = [];
|
|
||||||
this._loadGPSTraces(gpsTracesFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadGPSTraces(gpsTracesFilePath) {
|
|
||||||
const expandedPath = path.resolve(gpsTracesFilePath);
|
|
||||||
const data = fs.readFileSync(expandedPath, 'utf-8');
|
|
||||||
const lines = data.split('\n');
|
|
||||||
const headers = lines[0].split(',');
|
|
||||||
|
|
||||||
const latitudeIndex = headers.indexOf('Latitude');
|
|
||||||
const longitudeIndex = headers.indexOf('Longitude');
|
|
||||||
const trackIdIndex = headers.indexOf('TrackID');
|
|
||||||
|
|
||||||
for (let i = 1; i < lines.length; i++) {
|
|
||||||
if (lines[i].trim() === '') continue;
|
|
||||||
const row = lines[i].split(',');
|
|
||||||
|
|
||||||
const latitude = parseFloat(row[latitudeIndex]);
|
|
||||||
const longitude = parseFloat(row[longitudeIndex]);
|
|
||||||
const trackId = row[trackIdIndex];
|
|
||||||
|
|
||||||
const coord = [longitude, latitude];
|
|
||||||
this.coordinates.push(coord);
|
|
||||||
|
|
||||||
if (!this.tracks[trackId]) {
|
|
||||||
this.tracks[trackId] = [];
|
|
||||||
}
|
|
||||||
this.tracks[trackId].push(coord);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.trackIds = Object.keys(this.tracks);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRandomCoordinate() {
|
|
||||||
const randomIndex = Math.floor(RNG() * this.coordinates.length);
|
|
||||||
return this.coordinates[randomIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
getRandomTrack() {
|
|
||||||
const randomIndex = Math.floor(RNG() * this.trackIds.length);
|
|
||||||
const trackId = this.trackIds[randomIndex];
|
|
||||||
return this.tracks[trackId];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function runOSRMMethod(osrm, method, coordinates) {
|
|
||||||
const time = await new Promise((resolve, reject) => {
|
|
||||||
const startTime = process.hrtime();
|
|
||||||
osrm[method]({coordinates}, (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
if (['NoSegment', 'NoMatch', 'NoRoute', 'NoTrips'].includes(err.message)) {
|
|
||||||
resolve(null);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const endTime = process.hrtime(startTime);
|
|
||||||
resolve(endTime[0] + endTime[1] / 1e9);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function nearest(osrm, gpsData) {
|
|
||||||
const times = [];
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
const coord = gpsData.getRandomCoordinate();
|
|
||||||
times.push(await runOSRMMethod(osrm, 'nearest', [coord]));
|
|
||||||
}
|
|
||||||
return times;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function route(osrm, gpsData) {
|
|
||||||
const times = [];
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
const from = gpsData.getRandomCoordinate();
|
|
||||||
const to = gpsData.getRandomCoordinate();
|
|
||||||
|
|
||||||
|
|
||||||
times.push(await runOSRMMethod(osrm, 'route', [from, to]));
|
|
||||||
}
|
|
||||||
return times;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function table(osrm, gpsData) {
|
|
||||||
const times = [];
|
|
||||||
for (let i = 0; i < 250; i++) {
|
|
||||||
const numPoints = Math.floor(RNG() * 3) + 15;
|
|
||||||
const coordinates = [];
|
|
||||||
for (let i = 0; i < numPoints; i++) {
|
|
||||||
coordinates.push(gpsData.getRandomCoordinate());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
times.push(await runOSRMMethod(osrm, 'table', coordinates));
|
|
||||||
}
|
|
||||||
return times;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function match(osrm, gpsData) {
|
|
||||||
const times = [];
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
const numPoints = Math.floor(RNG() * 50) + 50;
|
|
||||||
const coordinates = gpsData.getRandomTrack().slice(0, numPoints);
|
|
||||||
|
|
||||||
|
|
||||||
times.push(await runOSRMMethod(osrm, 'match', coordinates));
|
|
||||||
}
|
|
||||||
return times;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function trip(osrm, gpsData) {
|
|
||||||
const times = [];
|
|
||||||
for (let i = 0; i < 250; i++) {
|
|
||||||
const numPoints = Math.floor(RNG() * 2) + 5;
|
|
||||||
const coordinates = [];
|
|
||||||
for (let i = 0; i < numPoints; i++) {
|
|
||||||
coordinates.push(gpsData.getRandomCoordinate());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
times.push(await runOSRMMethod(osrm, 'trip', coordinates));
|
|
||||||
}
|
|
||||||
return times;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bootstrapConfidenceInterval(data, numSamples = 1000, confidenceLevel = 0.95) {
|
|
||||||
let means = [];
|
|
||||||
let dataLength = data.length;
|
|
||||||
|
|
||||||
for (let i = 0; i < numSamples; i++) {
|
|
||||||
let sample = [];
|
|
||||||
for (let j = 0; j < dataLength; j++) {
|
|
||||||
let randomIndex = Math.floor(RNG() * dataLength);
|
|
||||||
sample.push(data[randomIndex]);
|
|
||||||
}
|
|
||||||
let sampleMean = sample.reduce((a, b) => a + b, 0) / sample.length;
|
|
||||||
means.push(sampleMean);
|
|
||||||
}
|
|
||||||
|
|
||||||
means.sort((a, b) => a - b);
|
|
||||||
let lowerBoundIndex = Math.floor((1 - confidenceLevel) / 2 * numSamples);
|
|
||||||
let upperBoundIndex = Math.floor((1 + confidenceLevel) / 2 * numSamples);
|
|
||||||
let mean = means.reduce((a, b) => a + b, 0) / means.length;
|
|
||||||
let lowerBound = means[lowerBoundIndex];
|
|
||||||
let upperBound = means[upperBoundIndex];
|
|
||||||
|
|
||||||
return { mean: mean, lowerBound: lowerBound, upperBound: upperBound };
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateConfidenceInterval(data) {
|
|
||||||
let { mean, lowerBound, upperBound } = bootstrapConfidenceInterval(data);
|
|
||||||
let bestValue = Math.max(...data);
|
|
||||||
let errorMargin = (upperBound - lowerBound) / 2;
|
|
||||||
|
|
||||||
return { mean, errorMargin, bestValue };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
|
|
||||||
const {OSRM} = require(args[0]);
|
|
||||||
const path = args[1];
|
|
||||||
const algorithm = args[2].toUpperCase();
|
|
||||||
const method = args[3];
|
|
||||||
const gpsTracesFilePath = args[4];
|
|
||||||
const iterations = parseInt(args[5]);
|
|
||||||
|
|
||||||
const gpsData = new GPSData(gpsTracesFilePath);
|
|
||||||
const osrm = new OSRM({path, algorithm});
|
|
||||||
|
|
||||||
|
|
||||||
const functions = {
|
|
||||||
route: route,
|
|
||||||
table: table,
|
|
||||||
nearest: nearest,
|
|
||||||
match: match,
|
|
||||||
trip: trip
|
|
||||||
};
|
|
||||||
const func = functions[method];
|
|
||||||
if (!func) {
|
|
||||||
throw new Error('Unknown method');
|
|
||||||
}
|
|
||||||
const allTimes = [];
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
RNG = seedrandom(42);
|
|
||||||
allTimes.push((await func(osrm, gpsData)).filter(t => t !== null));
|
|
||||||
}
|
|
||||||
|
|
||||||
const opsPerSec = allTimes.map(times => times.length / times.reduce((a, b) => a + b, 0));
|
|
||||||
const { mean, errorMargin, bestValue } = calculateConfidenceInterval(opsPerSec);
|
|
||||||
console.log(`Ops: ${mean.toFixed(1)} ± ${errorMargin.toFixed(1)} ops/s. Best: ${bestValue.toFixed(1)} ops/s`);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -53,7 +53,6 @@ function run_benchmarks_for_folder {
|
|||||||
mkdir -p $RESULTS_FOLDER
|
mkdir -p $RESULTS_FOLDER
|
||||||
|
|
||||||
BENCHMARKS_FOLDER="$BINARIES_FOLDER/src/benchmarks"
|
BENCHMARKS_FOLDER="$BINARIES_FOLDER/src/benchmarks"
|
||||||
|
|
||||||
echo "Running match-bench MLD"
|
echo "Running match-bench MLD"
|
||||||
$BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/match_mld.bench"
|
$BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/match_mld.bench"
|
||||||
echo "Running match-bench CH"
|
echo "Running match-bench CH"
|
||||||
@@ -82,18 +81,6 @@ function run_benchmarks_for_folder {
|
|||||||
echo "Running osrm-contract"
|
echo "Running osrm-contract"
|
||||||
measure_peak_ram_and_time "$BINARIES_FOLDER/osrm-contract $FOLDER/data.osrm" "$RESULTS_FOLDER/osrm_contract.bench"
|
measure_peak_ram_and_time "$BINARIES_FOLDER/osrm-contract $FOLDER/data.osrm" "$RESULTS_FOLDER/osrm_contract.bench"
|
||||||
|
|
||||||
|
|
||||||
for ALGORITHM in ch mld; do
|
|
||||||
for BENCH in nearest table trip route match; do
|
|
||||||
echo "Running node $BENCH $ALGORITHM"
|
|
||||||
START=$(date +%s.%N)
|
|
||||||
node $SCRIPTS_FOLDER/scripts/ci/bench.js $FOLDER/lib/binding/node_osrm.node $FOLDER/data.osrm $ALGORITHM $BENCH $GPS_TRACES > "$RESULTS_FOLDER/node_${BENCH}_${ALGORITHM}.bench" 5
|
|
||||||
END=$(date +%s.%N)
|
|
||||||
DIFF=$(echo "$END - $START" | bc)
|
|
||||||
echo "Took: ${DIFF}s"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
for ALGORITHM in ch mld; do
|
for ALGORITHM in ch mld; do
|
||||||
for BENCH in nearest table trip route match; do
|
for BENCH in nearest table trip route match; do
|
||||||
echo "Running random $BENCH $ALGORITHM"
|
echo "Running random $BENCH $ALGORITHM"
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
#include "osrm/osrm.hpp"
|
#include "osrm/osrm.hpp"
|
||||||
#include "osrm/status.hpp"
|
#include "osrm/status.hpp"
|
||||||
|
|
||||||
#include "util/meminfo.hpp"
|
|
||||||
#include <boost/assert.hpp>
|
#include <boost/assert.hpp>
|
||||||
|
|
||||||
#include <boost/optional/optional.hpp>
|
#include <boost/optional/optional.hpp>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
@@ -655,12 +655,6 @@ try
|
|||||||
std::cerr << "Unknown benchmark: " << benchmarkToRun << std::endl;
|
std::cerr << "Unknown benchmark: " << benchmarkToRun << std::endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Peak RAM: " << std::setprecision(3)
|
|
||||||
<< static_cast<double>(osrm::util::PeakRAMUsedInBytes()) /
|
|
||||||
static_cast<double>((1024 * 1024))
|
|
||||||
<< "MB" << std::endl;
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "util/json_container.hpp"
|
#include "util/json_container.hpp"
|
||||||
|
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace osrm::server::service
|
namespace osrm::server::service
|
||||||
{
|
{
|
||||||
@@ -30,11 +31,11 @@ std::string getWrongOptionHelp(const engine::api::NearestParameters ¶meters)
|
|||||||
return help;
|
return help;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
engine::Status NearestService::RunQuery(std::size_t prefix_length,
|
engine::Status NearestService::RunQuery(std::size_t prefix_length,
|
||||||
std::string &query,
|
std::string &query,
|
||||||
osrm::engine::api::ResultT &result)
|
osrm::engine::api::ResultT &result)
|
||||||
{
|
{
|
||||||
|
std::cout << "running query: " << query << "\n";
|
||||||
result = util::json::Object();
|
result = util::json::Object();
|
||||||
auto &json_result = std::get<util::json::Object>(result);
|
auto &json_result = std::get<util::json::Object>(result);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(clap::ValueEnum, Clone, Default, Debug)]
|
||||||
|
pub enum LoadMethod {
|
||||||
|
Mmap,
|
||||||
|
#[default]
|
||||||
|
Datastore,
|
||||||
|
Directly,
|
||||||
|
}
|
||||||
|
impl Display for LoadMethod {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let result = match self {
|
||||||
|
LoadMethod::Mmap => "mmap",
|
||||||
|
LoadMethod::Datastore => "datastore",
|
||||||
|
LoadMethod::Directly => "directly",
|
||||||
|
};
|
||||||
|
write!(f, "{result}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::ValueEnum, Clone, Default, Debug)]
|
||||||
|
pub enum RoutingAlgorithm {
|
||||||
|
#[default]
|
||||||
|
Ch,
|
||||||
|
Mld,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RoutingAlgorithm {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let result = match self {
|
||||||
|
RoutingAlgorithm::Ch => "ch",
|
||||||
|
RoutingAlgorithm::Mld => "mld",
|
||||||
|
};
|
||||||
|
write!(f, "{result}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move to external file
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Args {
|
||||||
|
// underlying memory storage
|
||||||
|
#[arg(short, default_value_t = LoadMethod::Datastore)]
|
||||||
|
memory: LoadMethod,
|
||||||
|
|
||||||
|
// Number of times to greet
|
||||||
|
#[arg(short, default_value_t = RoutingAlgorithm::Ch)]
|
||||||
|
p: RoutingAlgorithm,
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
use colored::Colorize;
|
||||||
|
use cucumber::{cli, event, parser, Event};
|
||||||
|
use std::{
|
||||||
|
io::{self, Write},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct DotWriter {
|
||||||
|
scenarios_started: usize,
|
||||||
|
scenarios_failed: usize,
|
||||||
|
scenarios_finished: usize,
|
||||||
|
features_started: usize,
|
||||||
|
features_finished: usize,
|
||||||
|
step_started: usize,
|
||||||
|
step_failed: usize,
|
||||||
|
step_passed: usize,
|
||||||
|
step_skipped: usize,
|
||||||
|
start_time: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: 'static> cucumber::Writer<W> for DotWriter {
|
||||||
|
type Cli = cli::Empty; // we provide no CLI options
|
||||||
|
|
||||||
|
async fn handle_event(&mut self, ev: parser::Result<Event<event::Cucumber<W>>>, _: &Self::Cli) {
|
||||||
|
let green_dot = ".".green();
|
||||||
|
let cyan_dash = "-".cyan();
|
||||||
|
let red_cross = "X".red();
|
||||||
|
match ev {
|
||||||
|
Ok(Event { value, .. }) => match value {
|
||||||
|
event::Cucumber::Feature(_feature, ev) => match ev {
|
||||||
|
event::Feature::Started => {
|
||||||
|
self.features_started += 1;
|
||||||
|
print!("{green_dot}")
|
||||||
|
}
|
||||||
|
event::Feature::Scenario(_scenario, ev) => match ev.event {
|
||||||
|
event::Scenario::Started => {
|
||||||
|
self.scenarios_started += 1;
|
||||||
|
print!("{green_dot}")
|
||||||
|
}
|
||||||
|
event::Scenario::Step(_step, ev) => match ev {
|
||||||
|
event::Step::Started => {
|
||||||
|
self.step_started += 1;
|
||||||
|
print!("{green_dot}")
|
||||||
|
}
|
||||||
|
event::Step::Passed(..) => {
|
||||||
|
self.step_passed += 1;
|
||||||
|
print!("{green_dot}")
|
||||||
|
}
|
||||||
|
event::Step::Skipped => {
|
||||||
|
self.step_skipped += 1;
|
||||||
|
print!("{cyan_dash}")
|
||||||
|
}
|
||||||
|
event::Step::Failed(_, _, _, _err) => {
|
||||||
|
self.step_failed += 1;
|
||||||
|
print!("{red_cross}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event::Scenario::Hook(_, _) => {}
|
||||||
|
event::Scenario::Background(_, _) => {}
|
||||||
|
event::Scenario::Log(_) => {}
|
||||||
|
event::Scenario::Finished => {
|
||||||
|
self.scenarios_finished += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event::Feature::Rule(_, _) => {}
|
||||||
|
event::Feature::Finished => {
|
||||||
|
self.features_finished += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event::Cucumber::Finished => {
|
||||||
|
println!();
|
||||||
|
let f = format!("{} failed", self.scenarios_failed).red();
|
||||||
|
let p = format!("{} passed", self.scenarios_finished).green();
|
||||||
|
println!("{} scenarios ({f}, {p})", self.scenarios_started);
|
||||||
|
let f = format!("{} failed", self.step_failed).red();
|
||||||
|
let s = format!("{} skipped", self.step_skipped).cyan();
|
||||||
|
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 seconds = (elapsed.as_millis() % 60_000) as f64 / 1000.;
|
||||||
|
println!("{}m{}s", minutes, seconds);
|
||||||
|
}
|
||||||
|
event::Cucumber::ParsingFinished {
|
||||||
|
features: _,
|
||||||
|
rules: _,
|
||||||
|
scenarios: _,
|
||||||
|
steps: _,
|
||||||
|
parser_errors: _,
|
||||||
|
} => {}
|
||||||
|
event::Cucumber::Started => {
|
||||||
|
self.start_time = Some(Instant::now());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => println!("Error: {e}"),
|
||||||
|
}
|
||||||
|
let _ = io::stdout().flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::Read,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
pub fn get_file_as_byte_vec(path: &PathBuf) -> Vec<u8> {
|
||||||
|
debug!("opening {path:?}");
|
||||||
|
let mut f = File::open(path).expect("no file found");
|
||||||
|
let metadata = fs::metadata(path).expect("unable to read metadata");
|
||||||
|
let mut buffer = vec![0; metadata.len() as usize];
|
||||||
|
match f.read(&mut buffer) {
|
||||||
|
Ok(l) => assert_eq!(metadata.len() as usize, l, "data was not completely read"),
|
||||||
|
Err(e) => panic!("Error: {e}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
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
|
||||||
|
let exe_path = env::current_exe().expect("failed to get current exe path");
|
||||||
|
let path = exe_path
|
||||||
|
.ancestors()
|
||||||
|
.find(|p| p.ends_with("target"))
|
||||||
|
.expect("compiled cucumber test executable resides in a directory tree with the root name 'target'")
|
||||||
|
.parent().unwrap();
|
||||||
|
// TODO: Remove after migration to Rust build dir
|
||||||
|
let build_path = path.join("build");
|
||||||
|
// TODO: Remove after migration to Rust build dir
|
||||||
|
let mut dependencies = Vec::new();
|
||||||
|
|
||||||
|
// FIXME: the following iterator gymnastics port the exact order and behavior of the JavaScript implementation
|
||||||
|
let names = [
|
||||||
|
"osrm-extract",
|
||||||
|
"osrm-contract",
|
||||||
|
"osrm-customize",
|
||||||
|
"osrm-partition",
|
||||||
|
"osrm_extract",
|
||||||
|
"osrm_contract",
|
||||||
|
"osrm_customize",
|
||||||
|
"osrm_partition",
|
||||||
|
];
|
||||||
|
|
||||||
|
let files: Vec<PathBuf> = fs::read_dir(build_path)
|
||||||
|
.unwrap()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.map(|dir_entry| dir_entry.path())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let iter = names.iter().map(|name| {
|
||||||
|
files
|
||||||
|
.iter()
|
||||||
|
.find(|path_buf| {
|
||||||
|
path_buf
|
||||||
|
.file_stem()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.contains(name)
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.expect("file exists and is usable")
|
||||||
|
});
|
||||||
|
|
||||||
|
dependencies.extend(iter);
|
||||||
|
|
||||||
|
let profiles_path = path.join("profiles");
|
||||||
|
debug!("{profiles_path:?}");
|
||||||
|
|
||||||
|
dependencies.extend(
|
||||||
|
LexicographicFileWalker::new(&profiles_path)
|
||||||
|
.filter(|pb| !pb.to_str().unwrap().contains("examples"))
|
||||||
|
.filter(|pathbuf| match pathbuf.extension() {
|
||||||
|
Some(ext) => ext.to_str().unwrap() == "lua",
|
||||||
|
None => false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let mut md5 = chksum_md5::new();
|
||||||
|
debug!("md5: {}", md5.digest().to_hex_lowercase());
|
||||||
|
|
||||||
|
for path_buf in dependencies {
|
||||||
|
let data = get_file_as_byte_vec(&path_buf);
|
||||||
|
if data.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
md5.update(data);
|
||||||
|
// debug!("md5: {}", md5.digest().to_hex_lowercase());
|
||||||
|
}
|
||||||
|
md5
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// struct to keep state agent, profile, host, etc
|
||||||
|
// functions to make nearest, route, etc calls
|
||||||
|
// fn nearest(arg1, ... argn) -> NearestResponse
|
||||||
|
|
||||||
|
// use std::{path::Path, time::Duration};
|
||||||
|
|
||||||
|
// use ureq::{Agent, AgentBuilder};
|
||||||
|
|
||||||
|
// pub struct HttpRequest {
|
||||||
|
// agent: Agent,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// Self { agent }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: port into toolbox-rs
|
||||||
|
pub struct LexicographicFileWalker {
|
||||||
|
dirs: VecDeque<PathBuf>,
|
||||||
|
files: VecDeque<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LexicographicFileWalker {
|
||||||
|
pub fn new(path: &Path) -> Self {
|
||||||
|
let mut dirs = VecDeque::new();
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
dirs.push_back(path.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
dirs,
|
||||||
|
files: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for LexicographicFileWalker {
|
||||||
|
type Item = PathBuf;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.dirs.is_empty() && self.files.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
while self.files.is_empty() && !self.dirs.is_empty() {
|
||||||
|
assert!(!self.dirs.is_empty());
|
||||||
|
let current_dir = self.dirs.pop_front().unwrap();
|
||||||
|
let mut temp_dirs = Vec::new();
|
||||||
|
|
||||||
|
for entry in fs::read_dir(current_dir).unwrap() {
|
||||||
|
let entry = entry.unwrap();
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
temp_dirs.push(path.clone());
|
||||||
|
} else {
|
||||||
|
self.files.push_back(path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.files.make_contiguous().sort();
|
||||||
|
temp_dirs.sort();
|
||||||
|
self.dirs.extend(temp_dirs.into_iter());
|
||||||
|
}
|
||||||
|
self.files.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use std::{
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
process::{Child, Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct LocalTask {
|
||||||
|
ready: bool,
|
||||||
|
command: String,
|
||||||
|
arguments: Vec<String>,
|
||||||
|
child: Option<Child>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalTask {
|
||||||
|
pub fn new(command: String) -> Self {
|
||||||
|
Self {
|
||||||
|
ready: false,
|
||||||
|
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) -> Self {
|
||||||
|
self.arguments.push(argument.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_wait_till_ready(&mut self, ready_token: &str) {
|
||||||
|
let mut command = &mut Command::new(&self.command);
|
||||||
|
for argument in &self.arguments {
|
||||||
|
command = command.arg(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
match command.stdout(Stdio::piped()).spawn() {
|
||||||
|
Ok(o) => self.child = Some(o),
|
||||||
|
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);
|
||||||
|
let mut line = String::new();
|
||||||
|
while let Ok(_count) = reader.read_line(&mut line) {
|
||||||
|
// println!("count: {count} ->{line}");
|
||||||
|
if line.contains(ready_token) {
|
||||||
|
self.ready = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Deserialize)]
|
||||||
|
pub struct Location {
|
||||||
|
// Note: The order is important since we derive Deserialize
|
||||||
|
pub longitude: f64,
|
||||||
|
pub latitude: f64,
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#![allow(clippy::derivable_impls, clippy::all)]
|
||||||
|
extern crate flatbuffers;
|
||||||
|
|
||||||
|
pub mod cli_arguments;
|
||||||
|
pub mod comparison;
|
||||||
|
pub mod dot_writer;
|
||||||
|
pub mod f64_utils;
|
||||||
|
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_error;
|
||||||
|
pub mod osrm_world;
|
||||||
|
pub mod route_response;
|
||||||
|
pub mod scenario_id;
|
||||||
|
|
||||||
|
// 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/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/waypoint_generated.rs"]
|
||||||
|
pub mod waypoint_flatbuffers;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
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: Option<Vec<u64>>,
|
||||||
|
pub distance: f32,
|
||||||
|
pub name: String,
|
||||||
|
location: Location,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Waypoint {
|
||||||
|
pub fn location(&self) -> &Location {
|
||||||
|
&self.location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct NearestResponse {
|
||||||
|
pub code: String,
|
||||||
|
pub waypoints: Vec<Waypoint>,
|
||||||
|
pub data_version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NearestResponse {
|
||||||
|
// TODO: the from_* functions should be a) into_. or b) implement From<_> trait
|
||||||
|
pub fn from_json_reader(reader: impl std::io::Read) -> Self {
|
||||||
|
let response = match serde_json::from_reader::<_, NearestResponse>(reader) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => panic!("parsing error {e}"),
|
||||||
|
};
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_flatbuffer(mut reader: impl std::io::Read) -> Self {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
if let Err(e) = reader.read_to_end(&mut buffer) {
|
||||||
|
panic!("cannot read from strem: {e}");
|
||||||
|
};
|
||||||
|
let decoded: Result<FBResult, flatbuffers::InvalidFlatbuffer> =
|
||||||
|
flatbuffers::root::<FBResult>(&buffer);
|
||||||
|
let decoded: FBResult = match decoded {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => panic!("Error during parsing: {e} {:?}", buffer),
|
||||||
|
};
|
||||||
|
let code = match decoded.code() {
|
||||||
|
Some(e) => e.message().expect("code exists but is not unwrappable"),
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
let data_version = match decoded.data_version() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
|
||||||
|
let waypoints = decoded
|
||||||
|
.waypoints()
|
||||||
|
.expect("waypoints should be at least an empty list")
|
||||||
|
.iter()
|
||||||
|
.map(|wp| {
|
||||||
|
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() as f64,
|
||||||
|
longitude: location.longitude() as f64,
|
||||||
|
};
|
||||||
|
let nodes = wp.nodes().expect("waypoint mus have nodes");
|
||||||
|
let nodes = Some(vec![nodes.first(), nodes.second()]);
|
||||||
|
let distance = wp.distance();
|
||||||
|
|
||||||
|
Waypoint {
|
||||||
|
hint,
|
||||||
|
nodes,
|
||||||
|
distance,
|
||||||
|
name: "".into(),
|
||||||
|
location,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
code: code.into(),
|
||||||
|
waypoints,
|
||||||
|
data_version: Some(data_version.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use xml_builder::XMLElement;
|
||||||
|
|
||||||
|
use super::location::Location;
|
||||||
|
|
||||||
|
static OSM_USER: &str = "osrm";
|
||||||
|
static OSM_TIMESTAMP: &str = "2000-01-01T00:00:00Z";
|
||||||
|
static OSM_UID: &str = "1";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct OSMNode {
|
||||||
|
pub id: u64,
|
||||||
|
pub location: Location,
|
||||||
|
pub tags: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OSMNode {
|
||||||
|
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;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn set_tags(&mut self, tags: HashMap<String, String>) {
|
||||||
|
// self.tags = tags
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn to_xml(&self) -> XMLElement {
|
||||||
|
let mut node = XMLElement::new("node");
|
||||||
|
node.add_attribute("id", &self.id.to_string());
|
||||||
|
node.add_attribute("version", "1");
|
||||||
|
node.add_attribute("uid", OSM_UID);
|
||||||
|
node.add_attribute("user", OSM_USER);
|
||||||
|
node.add_attribute("timestamp", OSM_TIMESTAMP);
|
||||||
|
node.add_attribute("lon", &format!("{:?}", self.location.longitude));
|
||||||
|
node.add_attribute("lat", &format!("{:?}", self.location.latitude));
|
||||||
|
|
||||||
|
if !self.tags.is_empty() {
|
||||||
|
for (key, value) in &self.tags {
|
||||||
|
let mut tags = XMLElement::new("tag");
|
||||||
|
tags.add_attribute("k", key);
|
||||||
|
tags.add_attribute("v", value);
|
||||||
|
node.add_child(tags).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct OSMWay {
|
||||||
|
pub id: u64,
|
||||||
|
pub tags: HashMap<String, String>,
|
||||||
|
pub nodes: Vec<OSMNode>,
|
||||||
|
pub add_locations: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OSMWay {
|
||||||
|
pub fn add_node(&mut self, node: OSMNode) {
|
||||||
|
self.nodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn set_tags(&mut self, tags: HashMap<String, String>) {
|
||||||
|
// 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());
|
||||||
|
way.add_attribute("version", "1");
|
||||||
|
way.add_attribute("uid", OSM_UID);
|
||||||
|
way.add_attribute("user", OSM_USER);
|
||||||
|
way.add_attribute("timestamp", OSM_TIMESTAMP);
|
||||||
|
|
||||||
|
assert!(self.nodes.len() >= 2);
|
||||||
|
|
||||||
|
for node in &self.nodes {
|
||||||
|
let mut nd = XMLElement::new("nd");
|
||||||
|
nd.add_attribute("ref", &node.id.to_string());
|
||||||
|
if self.add_locations {
|
||||||
|
nd.add_attribute("lon", &format!("{:?}", node.location.longitude));
|
||||||
|
nd.add_attribute("lat", &format!("{:?}", node.location.latitude));
|
||||||
|
}
|
||||||
|
way.add_child(nd).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.tags.is_empty() {
|
||||||
|
for (key, value) in &self.tags {
|
||||||
|
let mut tags = XMLElement::new("tag");
|
||||||
|
tags.add_attribute("k", key);
|
||||||
|
tags.add_attribute("v", value);
|
||||||
|
way.add_child(tags).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
way
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[derive(Clone, Debug)]
|
||||||
|
// struct Member {
|
||||||
|
// id: u64,
|
||||||
|
// member_type: String,
|
||||||
|
// member_role: String,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Clone, Debug)]
|
||||||
|
// struct OSMRelation {
|
||||||
|
// id: u64,
|
||||||
|
// osm_user: String,
|
||||||
|
// osm_time_stamp: String,
|
||||||
|
// osm_uid: String,
|
||||||
|
// members: Vec<Member>,
|
||||||
|
// tags: HashMap<String, String>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl OSMRelation {
|
||||||
|
// fn add_member(&mut self, member_type: String, id: u64, member_role: String) {
|
||||||
|
// self.members.push(Member {
|
||||||
|
// id,
|
||||||
|
// member_type,
|
||||||
|
// member_role,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn add_tag(&mut self, key: &str, value: &str) {
|
||||||
|
// self.tags.insert(key.into(), value.into());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
use super::osm::{OSMNode, OSMWay};
|
||||||
|
use xml_builder::{XMLBuilder, XMLElement, XMLVersion};
|
||||||
|
|
||||||
|
// TODO: better error handling in XML creation
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct OSMDb {
|
||||||
|
nodes: Vec<(String, OSMNode)>,
|
||||||
|
ways: Vec<OSMWay>,
|
||||||
|
// relations: Vec<OSMRelation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.clone(), node));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_way(&mut self, way: OSMWay) {
|
||||||
|
self.ways.push(way);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn add_relation(&mut self, relation: OSMRelation) {
|
||||||
|
// self.relations.push(relation);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn clear(&mut self) {
|
||||||
|
// self.nodes.clear();
|
||||||
|
// self.ways.clear();
|
||||||
|
// // self.relations.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn to_xml(&self) -> String {
|
||||||
|
let mut xml = XMLBuilder::new()
|
||||||
|
.version(XMLVersion::XML1_0)
|
||||||
|
.encoding("UTF-8".into())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let mut osm = XMLElement::new("osm");
|
||||||
|
osm.add_attribute("generator", "osrm-test");
|
||||||
|
osm.add_attribute("version", "0.6");
|
||||||
|
|
||||||
|
for (_, node) in &self.nodes {
|
||||||
|
osm.add_child(node.to_xml()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for way in &self.ways {
|
||||||
|
osm.add_child(way.to_xml()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.set_root_element(osm);
|
||||||
|
|
||||||
|
let mut writer: Vec<u8> = Vec::new();
|
||||||
|
xml.generate(&mut writer).unwrap();
|
||||||
|
String::from_utf8(writer).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn node_len(&self) -> usize {
|
||||||
|
// self.nodes.len()
|
||||||
|
// }
|
||||||
|
// pub fn way_len(&self) -> usize {
|
||||||
|
// self.ways.len()
|
||||||
|
// }
|
||||||
|
// pub fn relation_len(&self) -> usize {
|
||||||
|
// self.relations.len()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_db_by_default() {
|
||||||
|
use super::*;
|
||||||
|
let osm_db = OSMDb::default();
|
||||||
|
assert_eq!(0, osm_db.node_len());
|
||||||
|
assert_eq!(0, osm_db.way_len());
|
||||||
|
// assert_eq!(0, osm_db.relation_len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn osm_db_with_single_node() {
|
||||||
|
use super::*;
|
||||||
|
let mut osm_db = OSMDb::default();
|
||||||
|
|
||||||
|
let mut node1 = OSMNode {
|
||||||
|
id: 123,
|
||||||
|
location: Location {
|
||||||
|
longitude: 8.9876,
|
||||||
|
latitude: 50.1234,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut node2 = OSMNode {
|
||||||
|
id: 321,
|
||||||
|
location: Location {
|
||||||
|
longitude: 8.9876,
|
||||||
|
latitude: 50.1234,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
node1.add_tag("name", "a");
|
||||||
|
node2.add_tag("name", "b");
|
||||||
|
osm_db.add_node(node1.clone());
|
||||||
|
osm_db.add_node(node2.clone());
|
||||||
|
|
||||||
|
let mut way = OSMWay {
|
||||||
|
id: 890,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
way.nodes.push(node1);
|
||||||
|
way.nodes.push(node2);
|
||||||
|
|
||||||
|
osm_db.add_way(way);
|
||||||
|
|
||||||
|
let actual = osm_db.to_xml();
|
||||||
|
let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<osm generator=\"osrm-test\" version=\"0.6\">\n\t<node id=\"123\" version=\"1.0\" user=\"osrm\" timestamp=\"2000-01-01T00:00:00Z\" lon=\"8.9876\" lat=\"50.1234\">\n\t\t<tag name=\"a\" />\n\t</node>\n\t<node id=\"321\" version=\"1.0\" user=\"osrm\" timestamp=\"2000-01-01T00:00:00Z\" lon=\"8.9876\" lat=\"50.1234\">\n\t\t<tag name=\"b\" />\n\t</node>\n\t<way id=\"890\" version=\"1\" uid=\"1\" user=\"osrm\" timestamp=\"2000-01-01T00:00:00Z\">\n\t\t<nd ref=\"123\" />\n\t\t<nd ref=\"321\" />\n\t</way>\n</osm>\n";
|
||||||
|
|
||||||
|
// println!("{actual}");
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
use super::{
|
||||||
|
nearest_response::NearestResponse,
|
||||||
|
osm::{OSMNode, OSMWay},
|
||||||
|
osm_db::OSMDb,
|
||||||
|
osrm_error::OSRMError,
|
||||||
|
route_response::RouteResponse,
|
||||||
|
};
|
||||||
|
use crate::{common::local_task::LocalTask, Location};
|
||||||
|
use core::panic;
|
||||||
|
use cucumber::World;
|
||||||
|
use log::debug;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs::{create_dir_all, File},
|
||||||
|
io::Write,
|
||||||
|
path::PathBuf,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_ORIGIN: Location = Location {
|
||||||
|
longitude: 1.0f64,
|
||||||
|
latitude: 1.0f64,
|
||||||
|
};
|
||||||
|
const DEFAULT_GRID_SIZE: f64 = 100.;
|
||||||
|
const WAY_SPACING: f64 = 100.;
|
||||||
|
const DEFAULT_PROFILE: &str = "bicycle";
|
||||||
|
|
||||||
|
#[derive(Debug, World)]
|
||||||
|
pub struct OSRMWorld {
|
||||||
|
pub feature_path: Option<PathBuf>,
|
||||||
|
pub scenario_id: String,
|
||||||
|
pub feature_digest: String,
|
||||||
|
pub osrm_digest: String,
|
||||||
|
pub osm_id: u64,
|
||||||
|
pub profile: String,
|
||||||
|
|
||||||
|
pub known_osm_nodes: HashMap<char, Location>,
|
||||||
|
pub known_locations: HashMap<char, Location>,
|
||||||
|
|
||||||
|
pub osm_db: OSMDb,
|
||||||
|
pub extraction_parameters: Vec<String>,
|
||||||
|
|
||||||
|
pub request_with_flatbuffers: bool,
|
||||||
|
pub query_options: HashMap<String, String>,
|
||||||
|
pub request_string: Option<String>,
|
||||||
|
|
||||||
|
pub grid_size: f64,
|
||||||
|
pub origin: Location,
|
||||||
|
pub way_spacing: f64,
|
||||||
|
|
||||||
|
task: LocalTask,
|
||||||
|
client: reqwest::blocking::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
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_PROFILE.into(),
|
||||||
|
known_osm_nodes: Default::default(),
|
||||||
|
known_locations: Default::default(),
|
||||||
|
osm_db: Default::default(),
|
||||||
|
extraction_parameters: Default::default(),
|
||||||
|
request_with_flatbuffers: Default::default(),
|
||||||
|
query_options: HashMap::from([
|
||||||
|
// default parameters // TODO: check if necessary
|
||||||
|
("steps".into(), "true".into()),
|
||||||
|
("alternatives".into(), "false".into()),
|
||||||
|
// ("annotations".into(), "true".into()),
|
||||||
|
]),
|
||||||
|
request_string: Default::default(),
|
||||||
|
|
||||||
|
grid_size: DEFAULT_GRID_SIZE,
|
||||||
|
origin: DEFAULT_ORIGIN,
|
||||||
|
way_spacing: WAY_SPACING,
|
||||||
|
task: LocalTask::default(),
|
||||||
|
client: reqwest::blocking::Client::builder()
|
||||||
|
.connect_timeout(Duration::from_secs(5))
|
||||||
|
.no_proxy()
|
||||||
|
.http1_only()
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OSRMWorld {
|
||||||
|
pub fn feature_cache_path(&self) -> PathBuf {
|
||||||
|
let full_path = self.feature_path.clone().unwrap();
|
||||||
|
let path = full_path
|
||||||
|
.ancestors()
|
||||||
|
.find(|p| p.ends_with("features"))
|
||||||
|
.expect(".feature files reside in a directory tree with the root name 'features'");
|
||||||
|
|
||||||
|
let suffix = full_path.strip_prefix(path).unwrap();
|
||||||
|
let path = path.parent().unwrap();
|
||||||
|
debug!("suffix: {suffix:?}");
|
||||||
|
let cache_path = path
|
||||||
|
.join("test")
|
||||||
|
.join("cache")
|
||||||
|
.join(suffix)
|
||||||
|
.join(&self.feature_digest);
|
||||||
|
|
||||||
|
debug!("{cache_path:?}");
|
||||||
|
if !cache_path.exists() {
|
||||||
|
create_dir_all(&cache_path).expect("cache path could not be created");
|
||||||
|
} else {
|
||||||
|
debug!("not creating cache dir");
|
||||||
|
}
|
||||||
|
cache_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routed_path(&self) -> PathBuf {
|
||||||
|
let full_path = self.feature_path.clone().unwrap();
|
||||||
|
let path = full_path
|
||||||
|
.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");
|
||||||
|
assert!(routed_path.exists(), "osrm-routed binary not found");
|
||||||
|
routed_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scenario_specific_paths_and_digests(&mut self, path: Option<PathBuf>) {
|
||||||
|
self.feature_path.clone_from(&path);
|
||||||
|
|
||||||
|
let file = File::open(path.clone().unwrap())
|
||||||
|
.unwrap_or_else(|_| panic!("filesystem broken? can't open file {:?}", path));
|
||||||
|
self.feature_digest = chksum_md5::chksum(file)
|
||||||
|
.expect("md5 could not be computed")
|
||||||
|
.to_hex_lowercase();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_osm_id(&mut self) -> u64 {
|
||||||
|
// number implicitly starts a 1. This is in line with previous implementations
|
||||||
|
self.osm_id += 1;
|
||||||
|
self.osm_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_osm_node(&mut self, name: char, location: Location, id: Option<u64>) {
|
||||||
|
if self.known_osm_nodes.contains_key(&name) {
|
||||||
|
panic!("duplicate node: {name}");
|
||||||
|
}
|
||||||
|
let id = match id {
|
||||||
|
Some(id) => id,
|
||||||
|
None => self.make_osm_id(),
|
||||||
|
};
|
||||||
|
let node = OSMNode {
|
||||||
|
id,
|
||||||
|
location,
|
||||||
|
tags: HashMap::from([("name".to_string(), name.to_string())]),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.known_osm_nodes.insert(name, location);
|
||||||
|
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
|
||||||
|
'0'..='9' => self
|
||||||
|
.known_locations
|
||||||
|
.get(&name)
|
||||||
|
.expect("test case specifies unknown location: {name}"),
|
||||||
|
'a'..='z' => self
|
||||||
|
.known_osm_nodes
|
||||||
|
.get(&name)
|
||||||
|
.expect("test case specifies unknown osm node: {name}"),
|
||||||
|
_ => unreachable!("nodes have to be name in [0-9][a-z]"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_location(&mut self, name: char, location: Location) {
|
||||||
|
if self.known_locations.contains_key(&name) {
|
||||||
|
panic!("duplicate location: {name}")
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
|
||||||
|
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");
|
||||||
|
assert!(self.task.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nearest(
|
||||||
|
&mut self,
|
||||||
|
query_location: &Location,
|
||||||
|
) -> Result<(u16, NearestResponse), (u16, OSRMError)> {
|
||||||
|
self.start_routed();
|
||||||
|
|
||||||
|
let mut url = format!(
|
||||||
|
"http://localhost:5000/nearest/v1/{}/{:?},{:?}",
|
||||||
|
self.profile, query_location.longitude, query_location.latitude
|
||||||
|
);
|
||||||
|
if self.request_with_flatbuffers {
|
||||||
|
url += ".flatbuffers";
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = match self.client.get(url).send() {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => panic!("http error: {e}"),
|
||||||
|
};
|
||||||
|
let status = response.status();
|
||||||
|
let bytes = &response.bytes().unwrap()[..];
|
||||||
|
|
||||||
|
match status {
|
||||||
|
StatusCode::OK => {
|
||||||
|
let response = match self.request_with_flatbuffers {
|
||||||
|
true => NearestResponse::from_flatbuffer(bytes),
|
||||||
|
false => NearestResponse::from_json_reader(bytes),
|
||||||
|
};
|
||||||
|
return Ok((status.as_u16(), response));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err((status.as_u16(), OSRMError::from_json_reader(bytes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn route(
|
||||||
|
&mut self,
|
||||||
|
waypoints: &[Location],
|
||||||
|
) -> Result<(u16, RouteResponse), (u16, OSRMError)> {
|
||||||
|
self.start_routed();
|
||||||
|
|
||||||
|
let waypoint_string = waypoints
|
||||||
|
.iter()
|
||||||
|
.map(|location| format!("{:?},{:?}", location.longitude, location.latitude))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(";");
|
||||||
|
|
||||||
|
let url = match &self.request_string {
|
||||||
|
None => {
|
||||||
|
let mut url = format!(
|
||||||
|
"http://127.0.0.1:5000/route/v1/{}/{waypoint_string}",
|
||||||
|
self.profile,
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
url
|
||||||
|
}
|
||||||
|
Some(request_string) => {
|
||||||
|
let temp = format!("http://127.0.0.1:5000/{}", request_string);
|
||||||
|
temp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// println!("url: {url}");
|
||||||
|
let response = match self.client.get(url).send() {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => panic!("http error: {e}"),
|
||||||
|
};
|
||||||
|
let status = &response.status();
|
||||||
|
|
||||||
|
match *status {
|
||||||
|
StatusCode::OK => {
|
||||||
|
let text = response.text().unwrap();
|
||||||
|
let response = match self.request_with_flatbuffers {
|
||||||
|
true => unimplemented!("RouteResponse::from_flatbuffer(body)"),
|
||||||
|
false => RouteResponse::from_string(&text),
|
||||||
|
};
|
||||||
|
Ok((status.as_u16(), response))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let bytes = &response.bytes().unwrap()[..];
|
||||||
|
return Err((status.as_u16(), OSRMError::from_json_reader(bytes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::{location::Location, nearest_response::Waypoint};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug)]
|
||||||
|
pub struct Maneuver {
|
||||||
|
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)]
|
||||||
|
#[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(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,
|
||||||
|
pub classes: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug)]
|
||||||
|
pub struct Step {
|
||||||
|
pub geometry: Geometry,
|
||||||
|
pub mode: String,
|
||||||
|
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)]
|
||||||
|
// 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, Default)]
|
||||||
|
pub struct Route {
|
||||||
|
pub geometry: Geometry,
|
||||||
|
pub weight: f64,
|
||||||
|
pub duration: f64,
|
||||||
|
pub legs: Vec<Leg>,
|
||||||
|
pub weight_name: String,
|
||||||
|
pub distance: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize)]
|
||||||
|
pub struct RouteResponse {
|
||||||
|
pub code: String,
|
||||||
|
pub routes: Vec<Route>,
|
||||||
|
pub waypoints: Option<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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
pub fn scenario_id(scenario: &cucumber::gherkin::Scenario) -> String {
|
||||||
|
// ports the following logic:
|
||||||
|
// let name = scenario.getName().toLowerCase().replace(/[/\-'=,():*#]/g, '')
|
||||||
|
// .replace(/\s/g, '_').replace(/__/g, '_').replace(/\.\./g, '.')
|
||||||
|
// .substring(0, 64);
|
||||||
|
let mut s = scenario
|
||||||
|
.name
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.replace(
|
||||||
|
&['/', '\\', '-', '\'', '=', ',', '(', ')', ':', '*', '#'][..],
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
.chars()
|
||||||
|
.map(|x| match x {
|
||||||
|
' ' => '_',
|
||||||
|
_ => x,
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
.replace('\\', "_")
|
||||||
|
.replace("__", "_")
|
||||||
|
.replace("..", ".");
|
||||||
|
s.truncate(64);
|
||||||
|
format!("{}_{}", scenario.position.line, s)
|
||||||
|
}
|
||||||
+1183
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user