Compare commits

..

44 Commits

Author SHA1 Message Date
Dennis 5d6c27762c Implement classes check 2024-07-12 19:13:16 +02:00
Dennis 2596a9354f Merge remote-tracking branch 'origin/master' into oxidize_cucumber 2024-07-12 17:41:30 +02:00
Dennis eb15e5bde4 Merge remote-tracking branch 'origin/master' into oxidize_cucumber 2024-07-10 11:46:28 +02:00
Dennis 51c7421ebc Apply clippy fixes 2024-07-09 20:04:25 +02:00
Dennis bc56a1f8e6 Implement routability more or less completely 2024-07-09 19:08:04 +02:00
Dennis 4f36d2dce1 Switch ureq with reqwest 2024-07-04 14:48:58 +02:00
Dennis 772f9ccc21 Implement geometry comparison 2024-06-28 13:04:04 +02:00
Dennis 85eb7a9383 Upgrade dependencies 2024-06-27 10:14:35 +02:00
Dennis 9457f36f49 Implement more features 2024-06-27 10:09:33 +02:00
Dennis 37f5780472 Implement support for car/names.feature 2024-06-13 17:02:28 +02:00
Dennis d985459696 Implement Windows download of flatc.exe 2024-06-13 12:50:49 +02:00
Dennis edffbae777 Add missing file 2024-06-13 12:22:57 +02:00
Dennis 77477a86dd Implement downloading of flatc on Linux 2024-06-13 11:51:10 +02:00
Dennis eba856af17 Implement parsing of flatbuffer response 2024-06-08 17:23:15 +02:00
Dennis c9b8462754 Write elapsed time after tests have finished 2024-06-07 10:46:48 +02:00
Dennis a7956f65ca Emit debug information when profiling 2024-06-06 14:49:36 +02:00
Dennis 38bd6b547d Instantiate cucumber writer with default instance 2024-06-06 13:57:19 +02:00
Dennis 926a6c1849 Add colors and summary to test report 2024-06-06 13:56:37 +02:00
Dennis aa1b47c596 Clippy fixes 2024-06-05 19:19:36 +02:00
Dennis 9003881a77 Implement parsing of data_version in nearest tests 2024-06-05 18:26:39 +02:00
Dennis f1ad997a4b Rename struct 2024-06-05 14:59:19 +02:00
Dennis 20708b0ff8 Add simple DotWriter 2024-06-05 14:58:41 +02:00
Dennis 995eaec555 Move approximate equality test for floats to util 2024-06-05 12:04:39 +02:00
Dennis beaaa597d4 Implement parsing of node tables with locations 2024-06-05 10:31:07 +02:00
Dennis 96e90ce321 Refactor common functionality into helpers 2024-06-05 10:29:53 +02:00
Dennis 7f7aa3dc2c Move starting of tasks into a helper struct 2024-06-02 16:34:05 +02:00
Dennis 9fff420a1a Refactor logic into their own source files 2024-06-02 15:17:12 +02:00
Dennis b04f3200b1 Update comments 2024-05-31 21:11:00 +02:00
Dennis 5cf37a0c7c Make the first 3 nearest/pick.feature scenarios pass 2024-05-31 21:07:10 +02:00
Dennis 59cbb08c0e Run one scenario concurrently at most 2024-05-31 09:43:46 +02:00
Dennis 9b88056062 Merge remote-tracking branch 'origin/master' into oxidize_cucumber 2024-05-31 09:29:45 +02:00
Dennis 3808617142 Execute tests against a running osrm-routed instance 2024-05-30 18:59:00 +02:00
Dennis 32dffeb54d Cleanup some code nearest step 2024-05-30 18:32:06 +02:00
Dennis 3f14453f5b Add test case parsing for nearest and osrm-routed startup 2024-05-30 18:23:03 +02:00
Dennis 3ba8001807 Untangle large source file 2024-05-30 17:10:23 +02:00
Dennis 6960bd42c5 Apply clippy fixes 2024-05-30 16:55:33 +02:00
Dennis 699ac31383 Rearrange code 2024-05-30 16:39:51 +02:00
Dennis 797db7a097 Move LexicographicFileWalker into its own source file 2024-05-30 16:19:03 +02:00
Dennis 63c9d599f9 Add dummy parameter parsing to cucumber 2024-05-30 16:14:58 +02:00
Dennis 6faa5f45e8 Remove Walkdir dependency 2024-05-30 15:32:32 +02:00
Dennis 86d4db97ca Rename Cucumber source file to cucumber.rs 2024-05-30 15:23:00 +02:00
Dennis 844685102d Fix generation of timestamps; Generate parseable OSM files 2024-05-30 15:21:09 +02:00
Dennis 97bd968b2a Check for existence of preprocessed files 2024-05-30 14:46:45 +02:00
Dennis 80eb71ded5 Oxidizing Cucumber tests 2024-05-30 14:35:52 +02:00
49 changed files with 5978 additions and 1535 deletions
File diff suppressed because it is too large Load Diff
+1
View File
@@ -48,6 +48,7 @@ Thumbs.db
/test/data/ch
/test/data/mld
/cmake/postinst
/target
# Eclipse related files #
#########################
-1
View File
@@ -1,7 +1,6 @@
# Unreleased
- Changes from 5.27.1
- 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)
- 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)
Generated
+2582
View File
File diff suppressed because it is too large Load Diff
+37
View File
@@ -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"
+117
View File
@@ -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
View File
@@ -200,7 +200,7 @@ curl 'http://router.project-osrm.org/nearest/v1/driving/13.388860,52.517037?numb
Finds the fastest route between coordinates in the supplied order.
```endpoint
GET /route/v1/{profile}/{coordinates}?alternatives={true|false|number}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false}
GET /route/v1/{profile}/{coordinates}?alternatives={true|false|number}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false}&continue_straight={default|true|false}
```
In addition to the [general options](#general-options) the following options are supported for this service:
+2 -2
View File
@@ -60,5 +60,5 @@ Feature: Car - Handle driving
When I route I should get
| 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 |
| c | e | cde,cde | driving,driving | 2 km/h | depart,arrive |
| e | c | 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.4 km/h | depart,arrive |
+18 -18
View File
@@ -113,12 +113,12 @@ Feature: Car - Destination only, no passing through
Scenario: Car - Routing around a way that becomes destination only
Given the node map
"""
a---c---b
+ \
+ |
d |
1 |
\___e
a---c---b
+ \
+ |
d |
1 |
\___e
"""
And the ways
@@ -136,12 +136,12 @@ Feature: Car - Destination only, no passing through
Scenario: Car - Routing through a parking lot tagged access=destination,service
Given the node map
"""
a----c++++b+++g------h---i
| + + + /
| + + + /
| + + + /
| d++++e+f /
z--------------y
a----c++++b+++g------h---i
| + + + /
| + + + /
| + + + /
| d++++e+f /
z--------------y
"""
And the ways
@@ -165,12 +165,12 @@ Feature: Car - Destination only, no passing through
Given a grid size of 20 meters
Given the node map
"""
a---c---b
:
x
:
d
\__e
a---c---b
:
x
:
d
\__e
"""
And the ways
+1 -9
View File
@@ -26,15 +26,7 @@ Feature: Foot - Access tags on ways
| motorway | no | | |
| motorway | no | yes | x |
| 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
Then routability should be
+1 -1
View File
@@ -46,7 +46,7 @@ class OSRMBaseLoader{
let retry = (err) => {
if (err) {
if (retryCount < this.scope.OSRM_CONNECTION_RETRIES) {
const timeoutMs = 10 * Math.pow(this.scope.OSRM_CONNECTION_EXP_BACKOFF_COEF, retryCount);
const timeoutMs = 10 * Math.pow(1.1, retryCount);
retryCount++;
setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, timeoutMs);
} else {
@@ -460,19 +460,6 @@ void search(SearchEngineData<Algorithm> &engine_working_data,
duration_upper_bound);
}
inline std::vector<double> getNetworkDistances(
SearchEngineData<Algorithm> &,
const DataFacade<ch::Algorithm> &,
SearchEngineData<Algorithm>::QueryHeap &,
const std::vector<std::unique_ptr<typename SearchEngineData<Algorithm>::QueryHeap>> &,
const PhantomNode &,
const std::vector<PhantomNode> &,
EdgeWeight /*duration_upper_bound*/ = INVALID_EDGE_WEIGHT)
{
std::vector<double> distances;
return distances;
}
// Requires the heaps for be empty
// If heaps should be adjusted to be initialized outside of this function,
// the addition of force_step parameters might be required
@@ -38,13 +38,10 @@ inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition,
return INVALID_LEVEL_ID;
};
auto res = std::min(std::min(level(source.forward_segment_id, target.forward_segment_id),
level(source.forward_segment_id, target.reverse_segment_id)),
std::min(level(source.reverse_segment_id, target.forward_segment_id),
level(source.reverse_segment_id, target.reverse_segment_id)));
// std::cerr << "OLD!!! " << (int)res << std::endl;
return res;
return std::min(std::min(level(source.forward_segment_id, target.forward_segment_id),
level(source.forward_segment_id, target.reverse_segment_id)),
std::min(level(source.reverse_segment_id, target.forward_segment_id),
level(source.reverse_segment_id, target.reverse_segment_id)));
}
template <typename MultiLevelPartition>
@@ -95,7 +92,6 @@ inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition,
getNodeQueryLevel(partition, node, source, target));
}));
});
// std::cerr << "NEW " << (int)min_level << std::endl;
return min_level;
}
@@ -144,8 +140,6 @@ inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition,
highest_different_level(phantom_node.reverse_segment_id));
return std::min(current_level, highest_level);
});
// std::cerr << "NEW!!! " << (int)node_level << std::endl;
return node_level;
}
@@ -306,6 +300,7 @@ void relaxOutgoingEdges(const DataFacade<Algorithm> &facade,
const auto &metric = facade.GetCellMetric();
const auto level = getNodeQueryLevel(partition, heapNode.node, args...);
static constexpr auto IS_MAP_MATCHING =
std::is_same_v<typename SearchEngineData<mld::Algorithm>::MapMatchingQueryHeap, Heap>;
@@ -462,15 +457,6 @@ void routingStep(const DataFacade<Algorithm> &facade,
BOOST_ASSERT(!facade.ExcludeNode(heapNode.node));
if (DIRECTION == FORWARD_DIRECTION)
{
// std::cerr << "FORWARDO " << heapNode.node << std::endl;
}
else
{
// std::cerr << "REVERSEO " << heapNode.node << std::endl;
}
// Upper bound for the path source -> target with
// weight(source -> node) = weight weight(to -> target) ≤ reverse_weight
// is weight + reverse_weight
@@ -658,7 +644,6 @@ searchDistance(SearchEngineData<Algorithm> &,
auto [middle, _] = *searchResult;
// std::cerr << "old " << middle << std::endl;
auto distance = forward_heap.GetData(middle).distance + reverse_heap.GetData(middle).distance;
return distance;
@@ -778,307 +763,6 @@ double getNetworkDistance(SearchEngineData<Algorithm> &engine_working_data,
return from_alias<double>(distance);
}
template <typename Algorithm, typename Heap>
std::vector<NodeID>
runSearch2(const DataFacade<Algorithm> &facade,
Heap &forward_heap,
const std::vector<std::unique_ptr<Heap>> &reverse_heap,
size_t candidatesCount,
const std::vector<NodeID> &force_step_nodes,
EdgeWeight weight_upper_bound,
const PhantomEndpointCandidates &candidates)
{
// if (forward_heap.Empty() || reverse_heap.Empty())
// {
// return {};
// }
// BOOST_ASSERT(!forward_heap.Empty() && forward_heap.MinKey() < INVALID_EDGE_WEIGHT);
// BOOST_ASSERT(!reverse_heap.Empty() && reverse_heap.MinKey() < INVALID_EDGE_WEIGHT);
std::vector<NodeID> middles;
std::vector<EdgeWeight> weights;
middles.resize(candidatesCount, SPECIAL_NODEID);
weights.resize(candidatesCount, weight_upper_bound);
// run two-Target Dijkstra routing step.
EdgeWeight forward_heap_min = forward_heap.MinKey();
std::vector<EdgeWeight> reverse_heap_mins;
for (size_t i = 0; i < candidatesCount; ++i)
{
reverse_heap_mins.push_back(reverse_heap[i]->MinKey());
}
auto shouldContinue = [&]()
{
bool cont = false;
for (size_t i = 0; i < candidatesCount; ++i)
{
if ((forward_heap.Size() + reverse_heap[i]->Size() > 0) &&
(forward_heap_min + reverse_heap_mins[i]) < weights[i])
{
cont = true;
break;
}
}
return cont;
};
bool cont = shouldContinue();
while (cont)
{
if (!forward_heap.Empty())
{
const auto heapNode = forward_heap.DeleteMinGetHeapNode();
// std::cerr << "FORWARDN " << heapNode.node << std::endl;
// auto heapNode = routingStep2<FORWARD_DIRECTION>(facade, forward_heap, args...);
for (size_t i = 0; i < candidatesCount; ++i)
{
auto &rh = reverse_heap[i];
const auto reverseHeapNode = rh->GetHeapNodeIfWasInserted(heapNode.node);
if (reverseHeapNode)
{
auto reverse_weight = reverseHeapNode->weight;
auto path_weight = heapNode.weight + reverse_weight;
if (!shouldForceStep(force_step_nodes, heapNode, *reverseHeapNode) &&
(path_weight >= EdgeWeight{0}) && (path_weight < weights[i]))
{
middles[i] = heapNode.node;
weights[i] = path_weight;
// auto distance =
// forward_heap.GetData(middles[i]).distance +
// reverse_heap[i]->GetData(middles[i]).distance;
// std::cerr << "RFOUNDN " << i <<" " << distance << std::endl;
}
}
}
relaxOutgoingEdges<FORWARD_DIRECTION>(facade, forward_heap, heapNode, candidates);
if (!forward_heap.Empty())
forward_heap_min = forward_heap.MinKey();
}
cont = false;
for (size_t i = 0; i < candidatesCount; ++i)
{
if ((forward_heap.Size() + reverse_heap[i]->Size() > 0) &&
(forward_heap_min + reverse_heap_mins[i]) < weights[i])
{
cont = true;
}
if (!reverse_heap[i]->Empty() && (forward_heap_min + reverse_heap_mins[i]) < weights[i])
{
const auto heapNode = reverse_heap[i]->DeleteMinGetHeapNode();
// std::cerr << "REVERSEN " << i << " " << heapNode.node << std::endl;
const auto reverseHeapNode = forward_heap.GetHeapNodeIfWasInserted(heapNode.node);
if (reverseHeapNode)
{
auto reverse_weight = reverseHeapNode->weight;
auto path_weight = heapNode.weight + reverse_weight;
if (!shouldForceStep(force_step_nodes, heapNode, *reverseHeapNode) &&
(path_weight >= EdgeWeight{0}) && (path_weight < weights[i]))
{
middles[i] = heapNode.node;
weights[i] = path_weight;
// auto distance =
// forward_heap.GetData(middles[i]).distance +
// reverse_heap[i]->GetData(middles[i]).distance;
// std::cerr << "FFOUNDN " << i << " " << distance << std::endl;
}
}
relaxOutgoingEdges<REVERSE_DIRECTION>(
facade, *reverse_heap[i], heapNode, candidates);
if (!reverse_heap[i]->Empty())
reverse_heap_mins[i] = reverse_heap[i]->MinKey();
}
}
};
return middles;
// std::vector<std::optional<std::pair<NodeID, EdgeWeight>>> results;
// results.reserve(candidatesCount);
// for (size_t i = 0; i < candidatesCount; ++i)
// {
// if (weights[i] >= weight_upper_bound || SPECIAL_NODEID == middles[i])
// {
// results.push_back({});
// }
// else
// {
// results.push_back({{middles[i], weights[i]}});
// }
// }
// return results;
// // run two-Target Dijkstra routing step.
// NodeID middle = SPECIAL_NODEID;
// EdgeWeight weight = weight_upper_bound;
// EdgeWeight forward_heap_min = forward_heap.MinKey();
// EdgeWeight reverse_heap_min = reverse_heap.MinKey();
// while (forward_heap.Size() + reverse_heap.Size() > 0 &&
// forward_heap_min + reverse_heap_min < weight)
// {
// if (!forward_heap.Empty())
// {
// routingStep<FORWARD_DIRECTION>(
// facade, forward_heap, reverse_heap, middle, weight, force_step_nodes, args...);
// if (!forward_heap.Empty())
// forward_heap_min = forward_heap.MinKey();
// }
// if (!reverse_heap.Empty())
// {
// routingStep<REVERSE_DIRECTION>(
// facade, reverse_heap, forward_heap, middle, weight, force_step_nodes, args...);
// if (!reverse_heap.Empty())
// reverse_heap_min = reverse_heap.MinKey();
// }
// };
// // No path found for both target nodes?
// if (weight >= weight_upper_bound || SPECIAL_NODEID == middle)
// {
// return {};
// }
// return {{middle, weight}};
}
template <typename Algorithm>
std::vector<double> searchDistance2(
SearchEngineData<Algorithm> &,
const DataFacade<Algorithm> &facade,
typename SearchEngineData<Algorithm>::MapMatchingQueryHeap &forward_heap,
const std::vector<std::unique_ptr<typename SearchEngineData<Algorithm>::MapMatchingQueryHeap>>
&reverse_heaps,
size_t candidatesCount,
const std::vector<NodeID> &force_step_nodes,
EdgeWeight weight_upper_bound,
const PhantomEndpointCandidates &candidates)
{
auto searchResults = runSearch2(facade,
forward_heap,
reverse_heaps,
candidatesCount,
force_step_nodes,
weight_upper_bound,
candidates);
std::vector<double> res;
res.reserve(candidatesCount);
for (size_t i = 0; i < searchResults.size(); ++i)
{
if (searchResults[i] == SPECIAL_NODEID)
{
res.push_back(std::numeric_limits<double>::max());
}
else
{
auto middle = searchResults[i];
// std::cerr << "new " << i << " " << middle << std::endl;
auto distance =
forward_heap.GetData(middle).distance + reverse_heaps[i]->GetData(middle).distance;
res.push_back(from_alias<double>(distance));
}
}
return res;
}
template <typename Algorithm>
std::vector<double> getNetworkDistances(
SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
typename SearchEngineData<Algorithm>::MapMatchingQueryHeap &forward_heap,
const std::vector<std::unique_ptr<typename SearchEngineData<Algorithm>::MapMatchingQueryHeap>>
&reverse_heaps,
const PhantomNode &source_phantom,
const std::vector<PhantomNode> &target_phantoms,
EdgeWeight weight_upper_bound = INVALID_EDGE_WEIGHT)
{
forward_heap.Clear();
for (const auto &heap : reverse_heaps)
{
heap->Clear();
}
// std::vector<std::unique_ptr<Heap>> reverse_heaps;
// const auto nodes_number = facade.GetNumberOfNodes();
// const auto border_nodes_number = facade.GetMaxBorderNodeID() + 1;
// for (const auto &target_phantom : target_phantoms)
// {
// (void)target_phantom;
// reverse_heaps.emplace_back(std::make_unique<Heap>(nodes_number, border_nodes_number));
// }
if (source_phantom.IsValidForwardSource())
{
forward_heap.Insert(source_phantom.forward_segment_id.id,
EdgeWeight{0} - source_phantom.GetForwardWeightPlusOffset(),
{source_phantom.forward_segment_id.id,
false,
EdgeDistance{0} - source_phantom.GetForwardDistance()});
}
if (source_phantom.IsValidReverseSource())
{
forward_heap.Insert(source_phantom.reverse_segment_id.id,
EdgeWeight{0} - source_phantom.GetReverseWeightPlusOffset(),
{source_phantom.reverse_segment_id.id,
false,
EdgeDistance{0} - source_phantom.GetReverseDistance()});
}
for (size_t i = 0; i < target_phantoms.size(); ++i)
{
auto &reverse_heap = *reverse_heaps[i];
const auto &target_phantom = target_phantoms[i];
if (target_phantom.IsValidForwardTarget())
{
reverse_heap.Insert(
target_phantom.forward_segment_id.id,
target_phantom.GetForwardWeightPlusOffset(),
{target_phantom.forward_segment_id.id, false, target_phantom.GetForwardDistance()});
}
if (target_phantom.IsValidReverseTarget())
{
reverse_heap.Insert(
target_phantom.reverse_segment_id.id,
target_phantom.GetReverseWeightPlusOffset(),
{target_phantom.reverse_segment_id.id, false, target_phantom.GetReverseDistance()});
}
}
// PhantomEndpoints endpoints{};
// endpoints.push_back(source_phantom);
// for (const auto &target_phantom : target_phantoms)
// {
// endpoints.push_back(target_phantom);
// }
std::vector<PhantomNode> source_phantomes;
source_phantomes.push_back(source_phantom);
PhantomEndpointCandidates phantom_candidates{source_phantomes, target_phantoms};
auto distances = searchDistance2(engine_working_data,
facade,
forward_heap,
reverse_heaps,
target_phantoms.size(),
{},
weight_upper_bound,
phantom_candidates);
return distances;
}
} // namespace osrm::engine::routing_algorithms::mld
#endif // OSRM_ENGINE_ROUTING_BASE_MLD_HPP
+1 -4
View File
@@ -56,7 +56,6 @@ template <> struct SearchEngineData<routing_algorithms::ch::Algorithm>
static thread_local ManyToManyHeapPtr many_to_many_heap;
static thread_local SearchEngineHeapPtr map_matching_forward_heap_1;
static thread_local SearchEngineHeapPtr map_matching_reverse_heap_1;
static thread_local std::vector<SearchEngineHeapPtr> map_matching_reverse_heaps;
void InitializeOrClearMapMatchingThreadLocalStorage(unsigned number_of_nodes);
@@ -134,15 +133,13 @@ template <> struct SearchEngineData<routing_algorithms::mld::Algorithm>
static thread_local SearchEngineHeapPtr reverse_heap_1;
static thread_local MapMatchingHeapPtr map_matching_forward_heap_1;
static thread_local MapMatchingHeapPtr map_matching_reverse_heap_1;
static thread_local std::vector<MapMatchingHeapPtr> map_matching_reverse_heaps;
static thread_local ManyToManyHeapPtr many_to_many_heap;
void InitializeOrClearFirstThreadLocalStorage(unsigned number_of_nodes,
unsigned number_of_boundary_nodes);
void InitializeOrClearMapMatchingThreadLocalStorage(unsigned number_of_nodes,
unsigned number_of_boundary_nodes,
size_t max_candidates);
unsigned number_of_boundary_nodes);
void InitializeOrClearManyToManyThreadLocalStorage(unsigned number_of_nodes,
unsigned number_of_boundary_nodes);
+4 -14
View File
@@ -2,7 +2,6 @@
#define MEMINFO_HPP
#include "util/log.hpp"
#include <cstddef>
#ifndef _WIN32
#include <sys/resource.h>
@@ -11,31 +10,22 @@
namespace osrm::util
{
inline size_t PeakRAMUsedInBytes()
inline void DumpMemoryStats()
{
#ifndef _WIN32
rusage usage;
getrusage(RUSAGE_SELF, &usage);
#ifdef __linux__
// Under linux, ru.maxrss is in kb
return usage.ru_maxrss * 1024;
util::Log() << "RAM: peak bytes used: " << usage.ru_maxrss * 1024;
#else // __linux__
// Under BSD systems (OSX), it's in bytes
return usage.ru_maxrss;
util::Log() << "RAM: peak bytes used: " << usage.ru_maxrss;
#endif // __linux__
#else // _WIN32
return 0;
#endif // _WIN32
}
inline void DumpMemoryStats()
{
#ifndef _WIN32
util::Log() << "RAM: peak bytes used: " << PeakRAMUsedInBytes();
#else // _WIN32
util::Log() << "RAM: peak bytes used: <not implemented on Windows>";
#endif // _WIN32
}
} // namespace osrm::util
#endif
#endif
+1 -12
View File
@@ -10,8 +10,7 @@
"hasInstallScript": true,
"license": "BSD-2-Clause",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"seedrandom": "^3.0.5"
"@mapbox/node-pre-gyp": "^1.0.11"
},
"devDependencies": {
"@babel/cli": "^7.18.10",
@@ -14653,11 +14652,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"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": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
@@ -30302,11 +30296,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"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": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+4 -7
View File
@@ -4,8 +4,7 @@
"private": false,
"description": "The Open Source Routing Machine is a high performance routing engine written in C++ designed to run on OpenStreetMap data.",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"seedrandom": "^3.0.5"
"@mapbox/node-pre-gyp": "^1.0.11"
},
"browserify": {
"transform": [
@@ -58,7 +57,6 @@
"jsonpath": "^1.1.1",
"mkdirp": "^0.5.6",
"node-addon-api": "^5.0.0",
"node-cmake": "^2.5.1",
"node-timeout": "0.0.4",
"polyline": "^0.2.0",
"request": "^2.88.2",
@@ -66,13 +64,12 @@
"tape": "^4.16.0",
"turf": "^3.0.14",
"uglify-js": "^3.17.0",
"xmlbuilder": "^4.2.1"
"xmlbuilder": "^4.2.1",
"node-cmake": "^2.5.1"
},
"main": "lib/index.js",
"binary": {
"napi_versions": [
8
],
"napi_versions": [8],
"module_name": "node_osrm",
"module_path": "./lib/binding_napi_v{napi_build_version}/",
"host": "https://github.com",
-1
View File
@@ -90,7 +90,6 @@ function setup()
path = walking_speed,
steps = walking_speed,
pedestrian = walking_speed,
platform = walking_speed,
footway = walking_speed,
pier = walking_speed,
},
-211
View File
@@ -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();
+37 -51
View File
@@ -50,27 +50,25 @@ function measure_peak_ram_and_time {
}
function run_benchmarks_for_folder {
rm -rf $RESULTS_FOLDER
mkdir -p $RESULTS_FOLDER
BENCHMARKS_FOLDER="$BINARIES_FOLDER/src/benchmarks"
# echo "Running match-bench MLD"
# $BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/match_mld.bench"
# echo "Running match-bench CH"
# $BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/ch/monaco.osrm" ch > "$RESULTS_FOLDER/match_ch.bench"
# echo "Running route-bench MLD"
# $BENCHMARKS_FOLDER/route-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/route_mld.bench"
# echo "Running route-bench CH"
# $BENCHMARKS_FOLDER/route-bench "$FOLDER/test/data/ch/monaco.osrm" ch > "$RESULTS_FOLDER/route_ch.bench"
# echo "Running alias"
# $BENCHMARKS_FOLDER/alias-bench > "$RESULTS_FOLDER/alias.bench"
# echo "Running json-render-bench"
# $BENCHMARKS_FOLDER/json-render-bench "$FOLDER/test/data/portugal_to_korea.json" > "$RESULTS_FOLDER/json-render.bench"
# echo "Running packedvector-bench"
# $BENCHMARKS_FOLDER/packedvector-bench > "$RESULTS_FOLDER/packedvector.bench"
# echo "Running rtree-bench"
# $BENCHMARKS_FOLDER/rtree-bench "$FOLDER/test/data/monaco.osrm.ramIndex" "$FOLDER/test/data/monaco.osrm.fileIndex" "$FOLDER/test/data/monaco.osrm.nbg_nodes" > "$RESULTS_FOLDER/rtree.bench"
echo "Running match-bench MLD"
$BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/match_mld.bench"
echo "Running match-bench CH"
$BENCHMARKS_FOLDER/match-bench "$FOLDER/test/data/ch/monaco.osrm" ch > "$RESULTS_FOLDER/match_ch.bench"
echo "Running route-bench MLD"
$BENCHMARKS_FOLDER/route-bench "$FOLDER/test/data/mld/monaco.osrm" mld > "$RESULTS_FOLDER/route_mld.bench"
echo "Running route-bench CH"
$BENCHMARKS_FOLDER/route-bench "$FOLDER/test/data/ch/monaco.osrm" ch > "$RESULTS_FOLDER/route_ch.bench"
echo "Running alias"
$BENCHMARKS_FOLDER/alias-bench > "$RESULTS_FOLDER/alias.bench"
echo "Running json-render-bench"
$BENCHMARKS_FOLDER/json-render-bench "$FOLDER/test/data/portugal_to_korea.json" > "$RESULTS_FOLDER/json-render.bench"
echo "Running packedvector-bench"
$BENCHMARKS_FOLDER/packedvector-bench > "$RESULTS_FOLDER/packedvector.bench"
echo "Running rtree-bench"
$BENCHMARKS_FOLDER/rtree-bench "$FOLDER/test/data/monaco.osrm.ramIndex" "$FOLDER/test/data/monaco.osrm.fileIndex" "$FOLDER/test/data/monaco.osrm.nbg_nodes" > "$RESULTS_FOLDER/rtree.bench"
cp -rf $OSM_PBF $FOLDER/data.osm.pbf
@@ -83,20 +81,8 @@ function run_benchmarks_for_folder {
echo "Running osrm-contract"
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 mld; do
for BENCH in match; do
for ALGORITHM in ch mld; do
for BENCH in nearest table trip route match; do
echo "Running random $BENCH $ALGORITHM"
START=$(date +%s.%N)
$BENCHMARKS_FOLDER/bench "$FOLDER/data.osrm" $ALGORITHM $GPS_TRACES ${BENCH} > "$RESULTS_FOLDER/random_${BENCH}_${ALGORITHM}.bench" 5 || true
@@ -107,28 +93,28 @@ function run_benchmarks_for_folder {
done
# for ALGORITHM in ch mld; do
# $BINARIES_FOLDER/osrm-routed --algorithm $ALGORITHM $FOLDER/data.osrm > /dev/null 2>&1 &
# OSRM_ROUTED_PID=$!
for ALGORITHM in ch mld; do
$BINARIES_FOLDER/osrm-routed --algorithm $ALGORITHM $FOLDER/data.osrm > /dev/null 2>&1 &
OSRM_ROUTED_PID=$!
# # wait for osrm-routed to start
# if ! curl --retry-delay 3 --retry 10 --retry-all-errors "http://127.0.0.1:5000/route/v1/driving/13.388860,52.517037;13.385983,52.496891?steps=true" > /dev/null 2>&1; then
# echo "osrm-routed failed to start for algorithm $ALGORITHM"
# kill -9 $OSRM_ROUTED_PID
# continue
# fi
# wait for osrm-routed to start
if ! curl --retry-delay 3 --retry 10 --retry-all-errors "http://127.0.0.1:5000/route/v1/driving/13.388860,52.517037;13.385983,52.496891?steps=true" > /dev/null 2>&1; then
echo "osrm-routed failed to start for algorithm $ALGORITHM"
kill -9 $OSRM_ROUTED_PID
continue
fi
# for METHOD in route nearest trip table match; do
# echo "Running e2e benchmark for $METHOD $ALGORITHM"
# START=$(date +%s.%N)
# python3 $SCRIPTS_FOLDER/scripts/ci/e2e_benchmark.py --host http://localhost:5000 --method $METHOD --iterations 5 --num_requests 1000 --gps_traces_file_path $GPS_TRACES > $RESULTS_FOLDER/e2e_${METHOD}_${ALGORITHM}.bench
# END=$(date +%s.%N)
# DIFF=$(echo "$END - $START" | bc)
# echo "Took: ${DIFF}s"
# done
for METHOD in route nearest trip table match; do
echo "Running e2e benchmark for $METHOD $ALGORITHM"
START=$(date +%s.%N)
python3 $SCRIPTS_FOLDER/scripts/ci/e2e_benchmark.py --host http://localhost:5000 --method $METHOD --iterations 5 --num_requests 1000 --gps_traces_file_path $GPS_TRACES > $RESULTS_FOLDER/e2e_${METHOD}_${ALGORITHM}.bench
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
echo "Took: ${DIFF}s"
done
# kill -9 $OSRM_ROUTED_PID
# done
kill -9 $OSRM_ROUTED_PID
done
}
run_benchmarks_for_folder
+1 -7
View File
@@ -16,8 +16,8 @@
#include "osrm/osrm.hpp"
#include "osrm/status.hpp"
#include "util/meminfo.hpp"
#include <boost/assert.hpp>
#include <boost/optional/optional.hpp>
#include <cstdlib>
#include <exception>
@@ -655,12 +655,6 @@ try
std::cerr << "Unknown benchmark: " << benchmarkToRun << std::endl;
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;
}
catch (const std::exception &e)
+13 -203
View File
@@ -45,8 +45,7 @@ unsigned getMedianSampleTime(const std::vector<unsigned> &timestamps)
template <typename Algorithm>
inline void initializeHeap(SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
size_t)
const DataFacade<Algorithm> &facade)
{
const auto nodes_number = facade.GetNumberOfNodes();
@@ -55,92 +54,14 @@ inline void initializeHeap(SearchEngineData<Algorithm> &engine_working_data,
template <>
inline void initializeHeap<mld::Algorithm>(SearchEngineData<mld::Algorithm> &engine_working_data,
const DataFacade<mld::Algorithm> &facade,
size_t max_candidates)
const DataFacade<mld::Algorithm> &facade)
{
const auto nodes_number = facade.GetNumberOfNodes();
const auto border_nodes_number = facade.GetMaxBorderNodeID() + 1;
engine_working_data.InitializeOrClearMapMatchingThreadLocalStorage(
nodes_number, border_nodes_number, max_candidates);
engine_working_data.InitializeOrClearMapMatchingThreadLocalStorage(nodes_number,
border_nodes_number);
}
#include <fstream>
#include <iostream>
template <typename T> void saveVectorToFile(const std::vector<T> &data, const std::string &filename)
{
std::ofstream outFile(filename, std::ios::binary);
if (!outFile)
{
std::cerr << "Error opening file for writing: " << filename << std::endl;
return;
}
size_t size = data.size();
outFile.write(reinterpret_cast<const char *>(&size), sizeof(size));
outFile.write(reinterpret_cast<const char *>(data.data()), size * sizeof(T));
outFile.close();
if (!outFile.good())
{
std::cerr << "Error occurred at writing time!" << std::endl;
}
}
template <typename T> bool loadVectorFromFile(std::vector<T> &data, const std::string &filename)
{
std::ifstream inFile(filename, std::ios::binary);
if (!inFile)
{
std::cerr << "Error opening file for reading: " << filename << std::endl;
return false;
}
size_t size;
inFile.read(reinterpret_cast<char *>(&size), sizeof(size));
data.resize(size);
inFile.read(reinterpret_cast<char *>(data.data()), size * sizeof(T));
inFile.close();
if (!inFile.good())
{
std::cerr << "Error occurred at reading time!" << std::endl;
return false;
}
return true;
}
template <typename T> void saveStructToFile(const T &data, const std::string &filename)
{
std::ofstream outFile(filename, std::ios::binary);
if (!outFile)
{
std::cerr << "Error opening file for writing: " << filename << std::endl;
return;
}
outFile.write(reinterpret_cast<const char *>(&data), sizeof(T));
outFile.close();
if (!outFile.good())
{
std::cerr << "Error occurred at writing time!" << std::endl;
}
}
template <typename T> bool loadStructFromFile(T &data, const std::string &filename)
{
std::ifstream inFile(filename, std::ios::binary);
if (!inFile)
{
std::cerr << "Error opening file for reading: " << filename << std::endl;
return false;
}
inFile.read(reinterpret_cast<char *>(&data), sizeof(T));
inFile.close();
if (!inFile.good())
{
std::cerr << "Error occurred at reading time!" << std::endl;
return false;
}
return true;
}
} // namespace
template <typename Algorithm>
@@ -223,16 +144,9 @@ SubMatchingList mapMatching(SearchEngineData<Algorithm> &engine_working_data,
return sub_matchings;
}
size_t max_candidates = 0;
for (const auto &candidates : candidates_list)
{
max_candidates = std::max(max_candidates, candidates.size());
}
initializeHeap(engine_working_data, facade, max_candidates);
initializeHeap(engine_working_data, facade);
auto &forward_heap = *engine_working_data.map_matching_forward_heap_1;
auto &reverse_heap = *engine_working_data.map_matching_reverse_heap_1;
const auto &reverse_heaps = engine_working_data.map_matching_reverse_heaps;
std::size_t breakage_begin = map_matching::INVALID_STATE;
std::vector<std::size_t> split_points;
@@ -311,108 +225,6 @@ SubMatchingList mapMatching(SearchEngineData<Algorithm> &engine_working_data,
continue;
}
// PhantomNode source;
// loadStructFromFile<PhantomNode>(source, "source.bin");
std::vector<PhantomNode> target_phantom_nodes;
// loadVectorFromFile(target_phantom_nodes, "target.bin");
// target_phantom_nodes.erase(target_phantom_nodes.begin());
// target_phantom_nodes.erase(target_phantom_nodes.begin());
// target_phantom_nodes.erase(target_phantom_nodes.begin());
// target_phantom_nodes.erase(target_phantom_nodes.begin());
// target_phantom_nodes.pop_back();
// target_phantom_nodes.pop_back();
// target_phantom_nodes.erase(target_phantom_nodes.begin() + 1);
// target_phantom_nodes.push_back(target);
for (const auto s_prime : util::irange<std::size_t>(0UL, current_viterbi.size()))
{
const double emission_pr = emission_log_probabilities[t][s_prime];
double new_value = prev_viterbi[s] + emission_pr;
if (current_viterbi[s_prime] > new_value)
{
continue;
}
target_phantom_nodes.push_back(current_timestamps_list[s_prime].phantom_node);
}
// TIMER_START(NEW_DIST);
#define MODE 1
#if MODE == 0
auto new_distances =
getNetworkDistances(engine_working_data,
facade,
forward_heap,
reverse_heaps,
prev_unbroken_timestamps_list[s].phantom_node,
target_phantom_nodes,
weight_upper_bound);
std::vector<double> old_distances;
for (const auto &pn : target_phantom_nodes)
{
double network_distance =
getNetworkDistance(engine_working_data,
facade,
forward_heap,
reverse_heap,
prev_unbroken_timestamps_list[s].phantom_node,
pn,
weight_upper_bound);
old_distances.push_back(network_distance);
}
for (size_t i = 0; i < old_distances.size(); ++i)
{
if (std::abs(old_distances[i] - new_distances[i]) > 0.01)
{
// saveStructToFile(prev_unbroken_timestamps_list[s].phantom_node,
// "source.bin");
// saveVectorToFile(target_phantom_nodes, "target.bin");
std::cerr << "OOPS " << old_distances[i] << " " << new_distances[i]
<< std::endl;
// std::exit(1);
}
}
auto distances = old_distances;
#elif MODE == 1
(void)reverse_heap;
auto distances =
getNetworkDistances(engine_working_data,
facade,
forward_heap,
reverse_heaps,
prev_unbroken_timestamps_list[s].phantom_node,
target_phantom_nodes,
weight_upper_bound);
// TIMER_STOP(NEW_DIST);
#else
// TIMER_START(OLD_DIST);
(void)reverse_heaps;
std::vector<double> distances;
for (const auto &pn : target_phantom_nodes)
{
double network_distance =
getNetworkDistance(engine_working_data,
facade,
forward_heap,
reverse_heap,
prev_unbroken_timestamps_list[s].phantom_node,
pn,
weight_upper_bound);
distances.push_back(network_distance);
}
#endif
// TIMER_STOP(OLD_DIST);
// std::cerr << "Old: " << TIMER_MSEC(OLD_DIST) << " New: " << TIMER_MSEC(NEW_DIST)
// << std::endl;
size_t distance_index = 0;
for (const auto s_prime : util::irange<std::size_t>(0UL, current_viterbi.size()))
{
const double emission_pr = emission_log_probabilities[t][s_prime];
@@ -422,16 +234,14 @@ SubMatchingList mapMatching(SearchEngineData<Algorithm> &engine_working_data,
continue;
}
double network_distance = distances[distance_index];
++distance_index;
// double network_distance =
// getNetworkDistance(engine_working_data,
// facade,
// forward_heap,
// reverse_heap,
// prev_unbroken_timestamps_list[s].phantom_node,
// current_timestamps_list[s_prime].phantom_node,
// weight_upper_bound);
double network_distance =
getNetworkDistance(engine_working_data,
facade,
forward_heap,
reverse_heap,
prev_unbroken_timestamps_list[s].phantom_node,
current_timestamps_list[s_prime].phantom_node,
weight_upper_bound);
// get distance diff between loc1/2 and locs/s_prime
const auto d_t = std::abs(network_distance - haversine_distance);
+1 -15
View File
@@ -15,8 +15,6 @@ thread_local SearchEngineData<CH>::SearchEngineHeapPtr
SearchEngineData<CH>::map_matching_forward_heap_1;
thread_local SearchEngineData<CH>::SearchEngineHeapPtr
SearchEngineData<CH>::map_matching_reverse_heap_1;
thread_local std::vector<typename SearchEngineData<CH>::SearchEngineHeapPtr>
SearchEngineData<CH>::map_matching_reverse_heaps;
thread_local SearchEngineData<CH>::ManyToManyHeapPtr SearchEngineData<CH>::many_to_many_heap;
@@ -125,11 +123,9 @@ thread_local SearchEngineData<MLD>::MapMatchingHeapPtr
thread_local SearchEngineData<MLD>::MapMatchingHeapPtr
SearchEngineData<MLD>::map_matching_reverse_heap_1;
thread_local SearchEngineData<MLD>::ManyToManyHeapPtr SearchEngineData<MLD>::many_to_many_heap;
thread_local std::vector<typename SearchEngineData<MLD>::MapMatchingHeapPtr>
SearchEngineData<MLD>::map_matching_reverse_heaps;
void SearchEngineData<MLD>::InitializeOrClearMapMatchingThreadLocalStorage(
unsigned number_of_nodes, unsigned number_of_boundary_nodes, size_t max_candidates)
unsigned number_of_nodes, unsigned number_of_boundary_nodes)
{
if (map_matching_forward_heap_1.get())
{
@@ -150,16 +146,6 @@ void SearchEngineData<MLD>::InitializeOrClearMapMatchingThreadLocalStorage(
map_matching_reverse_heap_1.reset(
new MapMatchingQueryHeap(number_of_nodes, number_of_boundary_nodes));
}
if (max_candidates > map_matching_reverse_heaps.size())
{
size_t to_add = max_candidates - map_matching_reverse_heaps.size();
for (unsigned i = 0; i < to_add; ++i)
{
map_matching_reverse_heaps.emplace_back(
new MapMatchingQueryHeap(number_of_nodes, number_of_boundary_nodes));
}
}
}
void SearchEngineData<MLD>::InitializeOrClearFirstThreadLocalStorage(
+3
View File
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}
+2 -1
View File
@@ -7,6 +7,7 @@
#include "util/json_container.hpp"
#include <boost/format.hpp>
#include <iostream>
namespace osrm::server::service
{
@@ -30,11 +31,11 @@ std::string getWrongOptionHelp(const engine::api::NearestParameters &parameters)
return help;
}
} // namespace
engine::Status NearestService::RunQuery(std::size_t prefix_length,
std::string &query,
osrm::engine::api::ResultT &result)
{
std::cout << "running query: " << query << "\n";
result = util::json::Object();
auto &json_result = std::get<util::json::Object>(result);
+51
View File
@@ -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,
}
+17
View File
@@ -0,0 +1,17 @@
#[derive(Debug)]
pub enum Offset {
Absolute(f64),
Percentage(f64),
}
// #[cfg(test)]
// mod tests {
// use crate::extract_number_and_offset;
// #[test]
// fn extract_number_and_offset() {
// let (value, result) = extract_number_and_offset("m", "300 +- 1m");
// assert_eq!(value, 300.);
// assert_eq!(offset, 1);
// }
// }
+101
View File
@@ -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();
}
}
+24
View File
@@ -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),
}
}
+20
View File
@@ -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
}
+80
View File
@@ -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
}
+24
View File
@@ -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 }
// }
// }
+55
View File
@@ -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()
}
}
+68
View File
@@ -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}");
// }
// }
// }
+8
View File
@@ -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,
}
+37
View File
@@ -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;
+88
View File
@@ -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()),
}
}
}
+143
View File
@@ -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());
// }
// }
+131
View File
@@ -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);
}
}
+17
View File
@@ -0,0 +1,17 @@
use serde::Deserialize;
#[derive(Debug, Default, Deserialize)]
pub struct OSRMError {
pub code: String,
pub message: String,
}
impl OSRMError {
pub fn from_json_reader(reader: impl std::io::Read) -> Self {
let response = match serde_json::from_reader::<_, Self>(reader) {
Ok(response) => response,
Err(e) => panic!("parsing error {e}"),
};
response
}
}
+335
View File
@@ -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)));
}
}
}
}
+105
View File
@@ -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
}
}
+24
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff