diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a7c9e36..8841c887 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,8 @@ target_link_libraries(osr boost-iostreams ) +set(ABS_TEST_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test") + # --- MAIN --- add_executable(osr-extract exe/extract.cc) target_link_libraries(osr-extract osr) @@ -139,6 +141,9 @@ file(GLOB_RECURSE osr-backend-src exe/backend/*.cc) add_executable(osr-backend ${osr-backend-src}) target_link_libraries(osr-backend osr web-server conf boost-json) target_include_directories(osr-backend PRIVATE exe/backend/include) +target_compile_definitions(osr-backend PRIVATE + TEST_DATA_DIR="${ABS_TEST_DATA_DIR}" +) # --- TEST --- configure_file( @@ -147,8 +152,11 @@ configure_file( ) file(GLOB_RECURSE osr-test-files test/*cc) add_executable(osr-test ${osr-test-files}) -target_include_directories(osr-test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated) +target_include_directories(osr-test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated test/include) target_link_libraries(osr-test gmock osr boost-json) +target_compile_definitions(osr-test PRIVATE + TEST_DATA_DIR="${ABS_TEST_DATA_DIR}" +) # --- MIMALLOC --- if (OSR_MIMALLOC) diff --git a/exe/backend/include/osr/backend/http_server.h b/exe/backend/include/osr/backend/http_server.h index 6011b20a..6a96729f 100644 --- a/exe/backend/include/osr/backend/http_server.h +++ b/exe/backend/include/osr/backend/http_server.h @@ -19,7 +19,8 @@ struct http_server { lookup const&, platforms const*, elevation_storage const*, - std::string const& static_file_path); + std::string const& static_file_path, + bool provide_test_features); ~http_server(); http_server(http_server const&) = delete; http_server& operator=(http_server const&) = delete; diff --git a/exe/backend/src/http_server.cc b/exe/backend/src/http_server.cc index fcf18eb2..bd2e80ed 100644 --- a/exe/backend/src/http_server.cc +++ b/exe/backend/src/http_server.cc @@ -7,6 +7,7 @@ #include "boost/beast/core/string.hpp" #include "boost/beast/version.hpp" #include "boost/json.hpp" +#include "boost/url/parse.hpp" #include "fmt/core.h" @@ -21,6 +22,7 @@ #include "osr/geojson.h" #include "osr/lookup.h" #include "osr/routing/algorithms.h" +#include "osr/routing/instructions/instruction_annotator.h" #include "osr/routing/parameters.h" #include "osr/routing/profiles/bike.h" #include "osr/routing/profiles/bike_sharing.h" @@ -40,6 +42,9 @@ namespace json = boost::json; namespace osr::backend { +const std::filesystem::path test_data_dir = TEST_DATA_DIR; +const std::filesystem::path test_cases_path = test_data_dir / "instruction-test-cases.json"; + template void set_cors_headers(http::response& res) { using namespace boost::beast::http; @@ -82,14 +87,16 @@ struct http_server::impl { lookup const& l, platforms const* pl, elevation_storage const* elevations, - std::string const& static_file_path) + std::string const& static_file_path, + bool const provide_test_features) : ioc_{ios}, thread_pool_{thread_pool}, w_{g}, l_{l}, pl_{pl}, elevations_{elevations}, - server_{ioc_} { + server_{ioc_}, + provide_test_features_(provide_test_features) { try { if (!static_file_path.empty() && fs::is_directory(static_file_path)) { static_file_path_ = fs::canonical(static_file_path).string(); @@ -140,8 +147,8 @@ struct http_server::impl { foot_speed_result.value()} : get_parameters(profile); - auto const p = route(params, w_, l_, profile, from, to, max, dir, 100, - nullptr, nullptr, elevations_, routing_algo); + auto p = route(params, w_, l_, profile, from, to, max, dir, 100, + nullptr, nullptr, elevations_, routing_algo); auto const p1 = route(params, w_, l_, profile, from, std::vector{to}, max, dir, 100, nullptr, nullptr, elevations_); @@ -161,7 +168,233 @@ struct http_server::impl { http::status::not_found)); return; } - cb(json_response(req, to_featurecollection(w_, p))); + + auto annotator = instruction_annotator{w_}; + annotator.annotate(*p); + + cb(json_response(req, to_featurecollection(w_, p, true, true, true))); + } + + static void handle_post_test_case(web_server::http_req_t const& req, + web_server::http_res_cb_t const& cb) { + try { + auto const test_case = json::parse(req.body()).as_object(); + json::array all_tests; + auto const new_name = test_case.at("name").as_string(); + + std::ifstream ifs(test_cases_path); + if (ifs.is_open()) { + std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + if (!content.empty()) { + all_tests = json::parse(content).as_array(); + } + ifs.close(); + } + + bool found = false; + for (auto& existing_test : all_tests) { + if (existing_test.as_object().at("name").as_string() == new_name) { + existing_test = test_case; // Replace existing entry + found = true; + break; + } + } + if (!found) { + all_tests.push_back(test_case); + } + + std::ofstream ofs(test_cases_path); + ofs << boost::json::serialize(all_tests); + ofs.close(); + return cb(json_response(req, "Saved", http::status::ok)); + } catch (std::exception const& e) { + return cb( + json_response(req, e.what(), http::status::internal_server_error)); + } + } + + static void handle_get_test_cases(web_server::http_req_t const& req, + web_server::http_res_cb_t const& cb) { + try { + std::ifstream ifs(test_cases_path); + if (ifs.is_open()) { + std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + if (!content.empty()) { + return cb(json_response(req, content, http::status::ok)); + } + } + } catch (std::exception const& e) { + return cb( + json_response(req, e.what(), http::status::internal_server_error)); + } + } + + static void handle_get_available_feeds(web_server::http_req_t const& req, + web_server::http_res_cb_t const& cb) { + try { + json::array feeds; + + if (fs::exists(test_data_dir) && fs::is_directory(test_data_dir)) { + for (auto const& entry : fs::directory_iterator(test_data_dir)) { + if (entry.is_regular_file() && entry.path().extension() == ".pbf") { + feeds.push_back(entry.path().filename().c_str()); + } + } + } + + return cb( + json_response(req, boost::json::serialize(feeds), http::status::ok)); + } catch (std::exception const& e) { + return cb( + json_response(req, e.what(), http::status::internal_server_error)); + } + } + + void handle_get_way_instruction_properties( + web_server::http_req_t const& req, web_server::http_res_cb_t const& cb) { + + constexpr auto instruction_properties_to_json = + [](way_instruction_properties const& props) { + return boost::json::object{ + {"highway", get_highway_name(props.get_highway())}, + {"junction", get_junction_name(props.get_junction())}, + {"is_link", props.is_link()}, + {"is_footway_crossing", props.is_footway_crossing()}, + {"lanes_forward", props.lanes_forward()}, + {"lanes_backward", props.lanes_backward()} + }; + }; + + try { + auto target = req.target(); + auto view = boost::urls::parse_origin_form(target); + if (view.has_value()) { + auto const params = view->params(); + + auto const way_param_iter = params.find("way"); + if (way_param_iter == params.end() || !(*way_param_iter).has_value) { + return cb(json_response(req, "Parameter mode is missing", + http::status::bad_request)); + } + + const auto osm_way_idx = + osm_way_idx_t{std::stoll((*way_param_iter).value)}; + const auto way_idx = w_.find_way(osm_way_idx); + if (!way_idx.has_value()) { + return cb(json_response(req, "no mapping for given way", + http::status::bad_request)); + } + return cb( + json_response(req, + boost::json::serialize(instruction_properties_to_json( + w_.way_instruction_properties_[*way_idx])), + http::status::ok)); + } + + return cb( + json_response(req, "Parameters missing", http::status::bad_request)); + + } catch (std::exception const& e) { + return cb( + json_response(req, e.what(), http::status::internal_server_error)); + } + } + + void handle_get_traversed_node_hub(web_server::http_req_t const& req, + web_server::http_res_cb_t const& cb) { + + try { + auto target = req.target(); + auto view = boost::urls::parse_origin_form(target); + + std::array nodes_indices; + nodes_indices.fill(node_idx_t::invalid()); + std::array way_indices; + way_indices.fill(way_idx_t::invalid()); + std::array way_directions_reverse; + way_directions_reverse.fill(false); + + if (view.has_value()) { + auto const params = view->params(); + auto const mode_param_iter = params.find("mode"); + if (mode_param_iter == params.end() || !(*mode_param_iter).has_value) { + return cb(json_response(req, "Parameter mode is missing", + http::status::bad_request)); + } + + const auto m = static_cast(std::stol((*mode_param_iter).value)); + + for (const auto [i, node_param] : + utl::enumerate>( + {"osm_prev_node", "osm_hub_node", "osm_next_node"})) { + const auto node_param_iter = params.find(node_param); + if (node_param_iter != params.end() && (*node_param_iter).has_value) { + osm_node_idx_t const osm_node_idx( + std::stoll((*node_param_iter).value)); + if (osm_node_idx != 0U) { + nodes_indices[i] = w_.get_node_idx(osm_node_idx); + } + } + } + + for (const auto [i, way_param] : + utl::enumerate>( + {"osm_arrive_way", "osm_exit_way"})) { + const auto way_param_iter = params.find(way_param); + if (way_param_iter != params.end() && (*way_param_iter).has_value) { + osm_way_idx_t const osm_way_idx( + std::stoll((*way_param_iter).value)); + if (osm_way_idx != 0U) { + if (const auto opt_way_idx = w_.find_way(osm_way_idx); + opt_way_idx.has_value()) { + way_indices[i] = *opt_way_idx; + } + } + } + } + + for (const auto [i, way_direction] : + utl::enumerate>( + {"arrive_way_direction", "exit_way_direction"})) { + const auto way_param_iter = params.find(way_direction); + if (way_param_iter != params.end() && (*way_param_iter).has_value) { + way_directions_reverse[i] = (*way_param_iter).value == "backward"; + } + } + + if (std::ranges::any_of(nodes_indices, [](node_idx_t const idx) { + return idx == node_idx_t::invalid(); + })) { + return cb(json_response(req, "At least one node idx is invalid", + http::status::bad_request)); + } + + if (std::ranges::any_of(way_indices, [](way_idx_t const idx) { + return idx == way_idx_t::invalid(); + })) { + return cb(json_response(req, "At least one way idx is invalid", + http::status::bad_request)); + } + + const auto hub = traversed_node_hub::from( + w_, nodes_indices[0], way_indices[0], nodes_indices[1], + way_indices[1], nodes_indices[2], !way_directions_reverse[0], + !way_directions_reverse[1], m); + + return cb(json_response(req, + hub.to_json(w_), + http::status::ok)); + } + + return cb( + json_response(req, "Parameters missing", http::status::bad_request)); + } catch (std::exception const& e) { + return cb( + json_response(req, e.what(), http::status::internal_server_error)); + } } void handle_levels(web_server::http_req_t const& req, @@ -256,6 +489,10 @@ struct http_server::impl { case http::verb::options: return cb(json_response(req, {})); case http::verb::post: { auto const& target = req.target(); + if (provide_test_features_ && + target.starts_with("/api/test-framework/test-case")) { + return handle_post_test_case(req, cb); + } if (target.starts_with("/api/route")) { return run_parallel( [this](web_server::http_req_t const& req1, @@ -289,7 +526,36 @@ struct http_server::impl { http::status::not_found)); } } - case http::verb::get: + case http::verb::get: { + auto const& target = req.target(); + if (provide_test_features_) { + if (target.starts_with("/api/test-framework/test-cases")) { + return run_parallel( + [this](web_server::http_req_t const& req1, + web_server::http_res_cb_t const& cb1) { + handle_get_test_cases(req1, cb1); + }, + req, cb); + } else if (target.starts_with("/api/test-framework/feeds")) { + return handle_get_available_feeds(req, cb); + } else if (target.starts_with("/api/test-framework/hub")) { + return run_parallel( + [this](web_server::http_req_t const& req1, + web_server::http_res_cb_t const& cb1) { + handle_get_traversed_node_hub(req1, cb1); + }, + req, cb); + } else if (target.starts_with( + "/api/test-framework/instruction-properties")) { + return run_parallel( + [this](web_server::http_req_t const& req1, + web_server::http_res_cb_t const& cb1) { + handle_get_way_instruction_properties(req1, cb1); + }, + req, cb); + } + } + } case http::verb::head: return handle_static(req, cb); default: return cb(json_response(req, @@ -358,6 +624,7 @@ struct http_server::impl { web_server server_; bool serve_static_files_{false}; std::string static_file_path_; + bool provide_test_features_{false}; }; http_server::http_server(boost::asio::io_context& ioc, @@ -366,8 +633,9 @@ http_server::http_server(boost::asio::io_context& ioc, lookup const& l, platforms const* pl, elevation_storage const* elevation, - std::string const& static_file_path) - : impl_{new impl(ioc, thread_pool, w, l, pl, elevation, static_file_path)} { + std::string const& static_file_path, + bool const provide_test_features) + : impl_{new impl(ioc, thread_pool, w, l, pl, elevation, static_file_path, provide_test_features)} { } http_server::~http_server() = default; diff --git a/exe/backend/src/main.cc b/exe/backend/src/main.cc index 98cf3647..d8802f8c 100644 --- a/exe/backend/src/main.cc +++ b/exe/backend/src/main.cc @@ -31,6 +31,8 @@ class settings : public conf::configuration { param(static_file_path_, "static,s", "Path to static files (ui/web)"); param(threads_, "threads,t", "Number of routing threads"); param(lock_, "lock,l", "Lock to memory"); + param(provide_test_features_, "test_features", + "Adds additional endpoints used in the test framework"); } fs::path data_dir_{"osr"}; @@ -39,6 +41,7 @@ class settings : public conf::configuration { std::string static_file_path_; bool lock_{true}; unsigned threads_{std::thread::hardware_concurrency()}; + bool provide_test_features_{false}; }; auto run(boost::asio::io_context& ioc) { @@ -96,8 +99,14 @@ int main(int argc, char const* argv[]) { auto ioc = boost::asio::io_context{}; auto pool = boost::asio::io_context{}; - auto server = http_server{ - ioc, pool, w, l, pl.get(), elevations.get(), opt.static_file_path_}; + auto server = http_server{ioc, + pool, + w, + l, + pl.get(), + elevations.get(), + opt.static_file_path_, + opt.provide_test_features_}; auto work_guard = boost::asio::make_work_guard(pool); auto threads = std::vector(std::max(1U, opt.threads_)); diff --git a/include/osr/debug.h b/include/osr/debug.h new file mode 100644 index 00000000..ed7e6967 --- /dev/null +++ b/include/osr/debug.h @@ -0,0 +1,16 @@ +#pragma once + +#include "fmt/core.h" + +namespace osr { + +constexpr bool osr_tracing_enabled = true; + +template +void trace(fmt::format_string fmt, Args&&... args) { + if constexpr (osr_tracing_enabled) { + fmt::print(fmt, std::forward(args)...); + } +} + +} // namespace osr \ No newline at end of file diff --git a/include/osr/extract/tags.h b/include/osr/extract/tags.h index 38516960..8c16ca03 100644 --- a/include/osr/extract/tags.h +++ b/include/osr/extract/tags.h @@ -56,9 +56,13 @@ struct tags { oneway_ |= t.value() == "yes"sv; break; case cista::hash("junction"): + junction_ = t.value(); oneway_ |= t.value() == "roundabout"sv; circular |= t.value() == "circular"sv; break; + case cista::hash("footway"): + footway_ = t.value(); + break; case cista::hash("oneway:bicycle"): not_oneway_bike_ = t.value() == "no"sv; break; @@ -178,6 +182,13 @@ struct tags { } } break; case cista::hash("maxspeed"): max_speed_ = t.value(); break; + case cista::hash("lanes"): lanes_ = t.value(); break; + case cista::hash("lanes:forward"): lanes_forward_ = t.value(); break; + case cista::hash("lanes:backward"): lanes_backward_ = t.value(); break; + case cista::hash("turn:lanes"): turn_lanes_ = t.value(); break; + case cista::hash("turn:lanes:forward"): turn_lanes_forward_ = t.value(); break; + case cista::hash("turn:lanes:backward"): turn_lanes_backward_ = t.value(); break; + case cista::hash("turn:lanes:both_ways"): turn_lanes_both_ways_ = t.value(); break; case cista::hash("toll"): toll_ = t.value() == "yes"sv; break; case cista::hash("incline"): { auto const value = std::string_view{t.value()}; @@ -263,12 +274,33 @@ struct tags { // https://wiki.openstreetmap.org/wiki/Key:maxspeed std::string_view max_speed_; + // https://wiki.openstreetmap.org/wiki/Key:lanes + std::string_view lanes_; + + // https://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right + std::string_view lanes_forward_; + + // https://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right + std::string_view lanes_backward_; + + // https://wiki.openstreetmap.org/wiki/Key:turn#Indicated_turns_by_lane + std::string_view turn_lanes_; + std::string_view turn_lanes_forward_; + std::string_view turn_lanes_backward_; + std::string_view turn_lanes_both_ways_; + // https://wiki.openstreetmap.org/wiki/Key:name std::string_view name_; // https://wiki.openstreetmap.org/wiki/Key:ref std::string_view ref_; + // https://wiki.openstreetmap.org/wiki/Key:junction + std::string_view junction_; + + // https://wiki.openstreetmap.org/wiki/Key:footway + std::string_view footway_; + // https://wiki.openstreetmap.org/wiki/Key:vehicle bool is_destination_{false}; override vehicle_{override::kNone}; diff --git a/include/osr/geojson.h b/include/osr/geojson.h index adace77b..fbdc211e 100644 --- a/include/osr/geojson.h +++ b/include/osr/geojson.h @@ -1,7 +1,6 @@ -#include "boost/json.hpp" +#pragma once -#include "fmt/ranges.h" -#include "fmt/std.h" +#include "boost/json.hpp" #include "utl/pairwise.h" #include "utl/pipes.h" @@ -35,7 +34,9 @@ boost::json::value to_line_string(std::initializer_list&& line) { inline std::string to_featurecollection(ways const& w, std::optional const& p, - bool const with_properties = true) { + bool const with_properties = true, + bool const with_instructions = false, + bool const with_osm_nodes = false) { return boost::json::serialize(boost::json::object{ {"type", "FeatureCollection"}, {"metadata", with_properties ? boost::json::value{{"duration", p->cost_}, @@ -43,18 +44,33 @@ inline std::string to_featurecollection(ways const& w, : boost::json::value{{}}}, {"features", utl::all(p->segments_) | utl::transform([&](const path::segment& s) { + + boost::json::object properties = { + {"level", s.from_level_.to_float()}, + {"osm_way_id", s.way_ == way_idx_t::invalid() + ? 0U : to_idx(w.way_osm_idx_[s.way_])}, + {"cost", s.cost_}, + {"distance", s.dist_}, + {"mode", static_cast(s.mode_)}, + {"is_reverse", s.is_reverse_}}; + + if (with_instructions) { + properties["instruction"] = to_json(s.instruction_annotation_, w); + } + + if (with_osm_nodes) { + properties["segment_from"] = s.from_ == node_idx_t::invalid() + ? 0U + : to_idx(w.node_to_osm_[s.from_]); + properties["segment_to"] = s.to_ == node_idx_t::invalid() + ? 0U + : to_idx(w.node_to_osm_[s.to_]); + } + return boost::json::object{ - {"type", "Feature"}, - { - "properties", - {{"level", s.from_level_.to_float()}, - {"osm_way_id", s.way_ == way_idx_t::invalid() - ? 0U - : to_idx(w.way_osm_idx_[s.way_])}, - {"cost", s.cost_}, - {"distance", s.dist_}}, - }, - {"geometry", to_line_string(s.polyline_)}}; + {"type", "Feature"}, + {"properties", std::move(properties)}, + {"geometry", to_line_string(s.polyline_)}}; }) | utl::emplace_back_to()}}); } diff --git a/include/osr/routing/instructions/directions.h b/include/osr/routing/instructions/directions.h new file mode 100644 index 00000000..12d8d23b --- /dev/null +++ b/include/osr/routing/instructions/directions.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "geo/latlng.h" + +namespace osr { + +enum class vertical_direction : std::uint8_t { + kUp, + kDown +}; + +enum class relative_direction: std::uint8_t { + kTurnAround, + kSharpRight, + kRight, + kSlightRight, + kSharpLeft, + kLeft, + kSlightLeft, + kStraight, + kInvalid +}; + +double normalize(double angle); + +relative_direction get_relative_direction(double angle); + +bool is_rightish(relative_direction direction); + +bool is_leftish(relative_direction direction); + +bool is_slightish(relative_direction dir); + +double get_angle(geo::latlng const& p1, + geo::latlng const& shared, + geo::latlng const& p2); + + + +} // namespace osr diff --git a/include/osr/routing/instructions/instruction_annotator.h b/include/osr/routing/instructions/instruction_annotator.h new file mode 100644 index 00000000..c634b22a --- /dev/null +++ b/include/osr/routing/instructions/instruction_annotator.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "osr/routing/path.h" +#include "osr/routing/instructions/module.h" +#include "osr/routing/instructions/meta_data.h" + +namespace osr { + +struct ways; + +class instruction_annotator { +public: + explicit instruction_annotator(ways const& w); + void annotate(path& path); + +private: + void preprocess(path& path, std::vector& meta); + void init_chain(); + void process_chain(); + + ways const& ways_; + std::vector> modules_; + std::shared_ptr head_module_; +}; + +} \ No newline at end of file diff --git a/include/osr/routing/instructions/instruction_util.h b/include/osr/routing/instructions/instruction_util.h new file mode 100644 index 00000000..87498feb --- /dev/null +++ b/include/osr/routing/instructions/instruction_util.h @@ -0,0 +1,58 @@ +#pragma once + +#include "osr/routing/instructions/module.h" +#include "osr/types.h" +#include "osr/ways.h" + +namespace osr { + +bool is_from_motorway(ways const& w, traversed_node_hub const& hub); + +bool is_from_ramp(ways const& w, traversed_node_hub const& hub); + +bool is_onto_motorway(ways const& w, traversed_node_hub const& hub); + +bool is_onto_ramp(ways const& w, traversed_node_hub const& hub); + +bool is_last_segment(segment_contexts_window const& window); + +bool ways_have_same_name(way_idx_t w1, way_idx_t w2, ways const& w); + +bool ways_have_same_highway(way_idx_t w1, way_idx_t w2, ways const& w); + +bool is_roundabout_way(ways const& w, way_idx_t way); + +bool is_link(ways const& w, way_idx_t way); + +bool is_motorway_or_trunk(ways const& w, way_idx_t way); + +bool turn_onto_through_street(ways const& w, traversed_node_hub const& hub); + +bool is_more_important_by(highway h1, + highway h2, + unsigned diff); + +bool is_segment_mode(path::segment const& segment, mode m); + +way_idx_t get_way_from(segment_context const& context); + +std::pair get_instruction_for_fork( + ways const& w, traversed_node_hub const& hub, size_t left, size_t right); + +std::tuple +get_instruction_for_fork(ways const& w, traversed_node_hub const& hub, + size_t left, size_t center, size_t right); + +std::optional get_traversed_node_hub( + path::segment const& segment, path::segment const& next_segment, + ways const& w); + +std::optional get_crossing_street(ways const& w, + traversed_node_hub const& h); + +bool is_merge_on_through_street(ways const& w, + traversed_node_hub const& h); + +bool is_pedestrian(mode const m); + +} // namespace osr diff --git a/include/osr/routing/instructions/meta_data.h b/include/osr/routing/instructions/meta_data.h new file mode 100644 index 00000000..a274785a --- /dev/null +++ b/include/osr/routing/instructions/meta_data.h @@ -0,0 +1,110 @@ +#pragma once + +#include + +#include "osr/routing/instructions/directions.h" +#include "osr/routing/path.h" +#include "osr/types.h" +#include "osr/ways.h" + +namespace osr { + +struct traversed_node_hub { + + struct way_osm_nodes_idx_range { + + way_osm_nodes_idx_range flip() const; + + std::uint16_t from_; + std::uint16_t to_; // inclusive + }; + + struct way_segment { + + bool is_way_aligned() const; + + static way_segment from(const ways& w, way_idx_t way, osm_node_idx_t from, + osm_node_idx_t to, bool is_way_aligned); + + way_segment flip_directions() const; + + osm_node_idx_t get_from_idx(ways const& w) const; + osm_node_idx_t get_to_idx(ways const& w) const; + + point get_from_position(ways const& w) const; + point get_to_position(ways const& w) const; + + + way_idx_t way_idx_; + way_osm_nodes_idx_range osm_node_range_; + }; + + struct relative_way_segment { + + bool can_enter_hub() const; + + bool can_exit_hub() const; + + bool is_opposite_of_arrive() const; + + double angle_with_exit_; + double angle_with_arrive_; + + bool can_enter_hub_; + bool can_exit_hub_; + bool is_opposite_of_arrive_; + + way_segment segment_; + }; + + static traversed_node_hub from(ways const& w, node_idx_t prev_node, + way_idx_t arrive_way, node_idx_t hub_node, + way_idx_t depart_way, node_idx_t next_node, + bool is_arrive_way_aligned, + bool is_exit_way_aligned, + mode m); + + unsigned long number_of_possible_turns(bool consider_uturn) const; + + unsigned long number_of_visible_turns(bool consider_uturn) const; + + double angle_with_exit_from(ways const& w, std::size_t alt_idx) const; + + void print(std::ostream& out, ways const& w) const; + + relative_way_segment get_exit() const; + + auto get_exit_neighbors(bool left) const { + if (left) { + // From index 1 (omit u-turn) to exit_from_hub_idx_ (exclusive) + return std::ranges::subrange( + alternatives_.begin() + 1, + alternatives_.begin() + static_cast(exit_from_hub_idx_)); + } else { + // From index exit_from_hub_idx_ + 1 to the end + return std::ranges::subrange( + alternatives_.begin() + static_cast(exit_from_hub_idx_) + 1, + alternatives_.end()); + } + } + + bool is_exit_natural_choice(ways const& w) const; + + bool is_end_of_turn_lane(ways const& w, bool left, bool dedicated) const; + + bool does_way_names_change(ways const& w) const; + + std::string to_json(ways const& w) const; + + way_segment arrive_hub_on_; + node_idx_t hub_node_; + std::vector alternatives_{}; + size_t exit_from_hub_idx_; +}; + +struct segment_context { + path::segment* src_ptr_; + std::optional traversed_node_hub_; +}; + +} // namespace osr diff --git a/include/osr/routing/instructions/module.h b/include/osr/routing/instructions/module.h new file mode 100644 index 00000000..8bea3b1c --- /dev/null +++ b/include/osr/routing/instructions/module.h @@ -0,0 +1,36 @@ +#pragma once + +#include "osr/routing/instructions/meta_data.h" +#include "osr/routing/path.h" +#include "osr/util/sliding_window.h" +#include "osr/ways.h" + +namespace osr { + +using segment_contexts = std::vector; +using segment_contexts_window = window<1, segment_contexts::iterator>; + +class instruction_module { +public: + virtual ~instruction_module() = default; + + virtual void process(ways const& w, path& p, + segment_contexts_window const& window) { + if (this->next_module_) { + this->next_module_->process(w, p, window); + } + } + + virtual std::shared_ptr set_next( + std::shared_ptr next_module) { + this->next_module_ = next_module; + return next_module; + } + + virtual void post_process(ways const&, path&, segment_contexts const&) {} + +private: + std::shared_ptr next_module_; +}; + +} // namespace osr diff --git a/include/osr/routing/instructions/modules/destination_module.h b/include/osr/routing/instructions/modules/destination_module.h new file mode 100644 index 00000000..b9e4da80 --- /dev/null +++ b/include/osr/routing/instructions/modules/destination_module.h @@ -0,0 +1,11 @@ +#pragma once + +#include "osr/routing/instructions/module.h" + +namespace osr { + +struct destination_module : instruction_module { + void process(ways const& w, path& p, segment_contexts_window const& window) override; +}; + +} // namespace osr diff --git a/include/osr/routing/instructions/modules/intersection_module.h b/include/osr/routing/instructions/modules/intersection_module.h new file mode 100644 index 00000000..87430728 --- /dev/null +++ b/include/osr/routing/instructions/modules/intersection_module.h @@ -0,0 +1,12 @@ +#pragma once + +#include "osr/routing/instructions/module.h" + +namespace osr { + +struct intersection_module : instruction_module { + void process(ways const& w, path& p, segment_contexts_window const& meta) override; + void post_process(ways const&, path&, segment_contexts const&) override; +}; + +} // namespace osr diff --git a/include/osr/routing/instructions/modules/motorway_module.h b/include/osr/routing/instructions/modules/motorway_module.h new file mode 100644 index 00000000..f3e297ef --- /dev/null +++ b/include/osr/routing/instructions/modules/motorway_module.h @@ -0,0 +1,11 @@ +#pragma once + +#include "osr/routing/instructions/module.h" + +namespace osr { + +struct motorway_module : instruction_module { + void process(ways const& w, path& p, segment_contexts_window const& window) override; +}; + +} // namespace osr diff --git a/include/osr/routing/instructions/modules/pedestrian_module.h b/include/osr/routing/instructions/modules/pedestrian_module.h new file mode 100644 index 00000000..29801ec4 --- /dev/null +++ b/include/osr/routing/instructions/modules/pedestrian_module.h @@ -0,0 +1,12 @@ +#pragma once + +#include "osr/routing/instructions/module.h" + +namespace osr { + +struct pedestrian_module : instruction_module { + void process(ways const& w, path& p, segment_contexts_window const& window) override; + void post_process(ways const&, path&, segment_contexts const&) override; +}; + +} // namespace osr diff --git a/include/osr/routing/instructions/modules/roundabout_module.h b/include/osr/routing/instructions/modules/roundabout_module.h new file mode 100644 index 00000000..bb571aed --- /dev/null +++ b/include/osr/routing/instructions/modules/roundabout_module.h @@ -0,0 +1,12 @@ +#pragma once + +#include "osr/routing/instructions/module.h" + +namespace osr { + +struct roundabout_module : instruction_module { + void process(ways const& w, path& p, segment_contexts_window const& window) override; + void post_process(ways const& w, path& p, segment_contexts const& contexts) override; +}; + +} // namespace osr diff --git a/include/osr/routing/instructions/routing_instruction.h b/include/osr/routing/instructions/routing_instruction.h new file mode 100644 index 00000000..208568ac --- /dev/null +++ b/include/osr/routing/instructions/routing_instruction.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include "boost/json.hpp" +#include "osr/routing/instructions/directions.h" +#include "osr/ways.h" + +namespace osr { + +struct none_instruction { + auto operator<=>(none_instruction const&) const = default; +}; + +struct destination_instruction { + relative_direction direction_{relative_direction::kInvalid}; + auto operator<=>(destination_instruction const&) const = default; +}; + +struct becomes_instruction { + auto operator<=>(becomes_instruction const&) const = default; +}; + +struct continue_instruction { + auto operator<=>(continue_instruction const&) const = default; +}; + +struct turn_instruction { + relative_direction direction_{relative_direction::kInvalid}; + auto operator<=>(turn_instruction const&) const = default; +}; + +struct ramp_on_instruction { + relative_direction direction_{relative_direction::kInvalid}; + auto operator<=>(ramp_on_instruction const&) const = default; +}; + +struct ramp_off_instruction { + relative_direction direction_{relative_direction::kInvalid}; + auto operator<=>(ramp_off_instruction const&) const = default; +}; + +struct stay_instruction { + relative_direction direction_{relative_direction::kInvalid}; + auto operator<=>(stay_instruction const&) const = default; +}; + +struct enter_roundabout_instruction { + auto operator<=>(enter_roundabout_instruction const&) const = default; +}; + +struct exit_roundabout_instruction { + std::uint16_t exit_number_{0}; + auto operator<=>(exit_roundabout_instruction const&) const = default; +}; + +struct elevator_instruction { + vertical_direction direction_{vertical_direction::kUp}; + relative_direction relative_direction_{relative_direction::kInvalid}; + auto operator<=>(elevator_instruction const&) const = default; +}; + +struct stairs_instruction { + vertical_direction direction_{vertical_direction::kUp}; + relative_direction relative_direction_{relative_direction::kInvalid}; + auto operator<=>(stairs_instruction const&) const = default; +}; + +struct crossing_instruction { + string_idx_t street_{string_idx_t::invalid()}; + + bool operator==(crossing_instruction const&) const = default; + auto operator<=>(crossing_instruction const& o) const { + return cista::to_idx(street_) <=> cista::to_idx(o.street_); + } +}; + +using routing_instruction = std::variant< + none_instruction, + destination_instruction, + becomes_instruction, + continue_instruction, + turn_instruction, + ramp_on_instruction, + ramp_off_instruction, + stay_instruction, + enter_roundabout_instruction, + exit_roundabout_instruction, + elevator_instruction, + stairs_instruction, + crossing_instruction +>; + +boost::json::value to_json(routing_instruction const& instruction, ways const& w); +routing_instruction from_json(boost::json::value const& json, ways const& w); +std::string to_string(routing_instruction const& instruction); + +} \ No newline at end of file diff --git a/include/osr/routing/path.h b/include/osr/routing/path.h index 1971af62..f6370362 100644 --- a/include/osr/routing/path.h +++ b/include/osr/routing/path.h @@ -3,8 +3,10 @@ #include #include "geo/polyline.h" +#include "instructions/directions.h" #include "osr/elevation_storage.h" +#include "osr/routing/instructions/routing_instruction.h" #include "osr/routing/mode.h" #include "osr/types.h" @@ -12,16 +14,19 @@ namespace osr { struct path { struct segment { + geo::polyline polyline_; level_t from_level_{kNoLevel}; level_t to_level_{kNoLevel}; node_idx_t from_{node_idx_t::invalid()}; node_idx_t to_{node_idx_t::invalid()}; + bool is_reverse_{false}; way_idx_t way_{way_idx_t::invalid()}; cost_t cost_{kInfeasible}; distance_t dist_{0}; elevation_storage::elevation elevation_{}; mode mode_{mode::kFoot}; + routing_instruction instruction_annotation_{}; }; cost_t cost_{kInfeasible}; diff --git a/include/osr/routing/path_reconstruction.h b/include/osr/routing/path_reconstruction.h index 184b54c9..524a3d69 100644 --- a/include/osr/routing/path_reconstruction.h +++ b/include/osr/routing/path_reconstruction.h @@ -137,6 +137,7 @@ inline double add_path(typename P::parameters const& params, } segment.from_ = r.way_nodes_[way][start_idx]; segment.to_ = r.way_nodes_[way][end_idx]; + segment.is_reverse_ = is_reverse; for (auto const [osm_idx, coord] : infinite( reverse(utl::zip(w.way_osm_nodes_[way], w.way_polylines_[way]), diff --git a/include/osr/types.h b/include/osr/types.h index e9fc1437..eacade23 100644 --- a/include/osr/types.h +++ b/include/osr/types.h @@ -262,4 +262,44 @@ constexpr float to_seconds_per_meter(speed_limit const l) { std::unreachable(); } +enum highway : std::uint8_t { + motorway, + trunk, + primary, + secondary, + tertiary, + residential, + footish, + highway_other +}; + +constexpr std::string get_highway_name(highway const h) { + switch (h) { + case motorway: return "motorway"; + case trunk: return "trunk"; + case primary: return "primary"; + case secondary: return "secondary"; + case tertiary: return "tertiary"; + case residential: return "residential"; + case footish: return "footish"; + default: return "highway_other"; + } +} + +enum junction : std::uint8_t { + roundabout, + circular, + jughandle, + junction_none +}; + +constexpr std::string get_junction_name(junction const j) { + switch (j) { + case roundabout: return "roundabout"; + case circular: return "circular"; + case jughandle: return "jughandle"; + default: return "junction_none"; + } +} + } // namespace osr diff --git a/include/osr/util/sliding_window.h b/include/osr/util/sliding_window.h new file mode 100644 index 00000000..cb01a281 --- /dev/null +++ b/include/osr/util/sliding_window.h @@ -0,0 +1,136 @@ +#pragma once + +#include + +#include +#include + +#include "utl/verify.h" + +namespace osr { + +template +struct window { + window(It begin, It end, It focus, std::ptrdiff_t const index, std::ptrdiff_t const size) + : begin_{begin}, end_{end}, focus_{focus}, index_{index}, size_{size} {} + + It focus() const { return focus_; } + + bool has(int const offset) const { + if (std::abs(offset) > static_cast(Radius)) { + return false; + } + auto const target_idx = index_ + offset; + if (target_idx < 0 || target_idx >= size_) { + return false; + } + + return true; + } + + It at(int const offset) const { + if (std::abs(offset) > static_cast(Radius)) { + return end_; + } + auto const target_idx = index_ + offset; + if (target_idx < 0 || target_idx >= size_) { + return end_; + } + + // Optimization for Random Access Iterators + if constexpr (std::is_base_of_v< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category>) { + return begin_ + target_idx; + } else { + // Fallback for other iterators + if (offset == 0) { + return focus_; + } + if (offset > 0) { + return std::next(focus_, offset); + } + return std::prev(focus_, -offset); + } + } + + It operator[](int const offset) const { return at(offset); } + + It begin_; + It end_; + It focus_; + std::ptrdiff_t index_; + std::ptrdiff_t size_; +}; + +template +struct sliding_window_iterator { + using iterator_category = std::forward_iterator_tag; + using value_type = window; + using difference_type = std::ptrdiff_t; + using pointer = window; + using reference = window; + + sliding_window_iterator(It begin, It end, It focus, std::ptrdiff_t index, + std::ptrdiff_t size) + : begin_{begin}, end_{end}, focus_{focus}, index_{index}, size_{size} {} + + reference operator*() const { return {begin_, end_, focus_, index_, size_}; } + + sliding_window_iterator& operator++() { + ++focus_; + ++index_; + return *this; + } + + sliding_window_iterator operator++(int) { + auto tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(sliding_window_iterator const& a, + sliding_window_iterator const& b) { + return a.focus_ == b.focus_; + } + + friend bool operator!=(sliding_window_iterator const& a, + sliding_window_iterator const& b) { + return a.focus_ != b.focus_; + } + + It begin_; + It end_; + It focus_; + std::ptrdiff_t index_; + std::ptrdiff_t size_; +}; + +template +struct sliding_window_range { + using iterator = sliding_window_iterator; + + sliding_window_range(It begin, It end, std::ptrdiff_t size) + : begin_{begin}, end_{end}, size_{size} {} + + iterator begin() const { return iterator{begin_, end_, begin_, 0, size_}; } + iterator end() const { return iterator{begin_, end_, end_, size_, size_}; } + + friend iterator begin(sliding_window_range const& r) { return r.begin(); } + friend iterator end(sliding_window_range const& r) { return r.end(); } + + It begin_; + It end_; + std::ptrdiff_t size_; +}; + +template +auto sliding_window(Container&& c) { + using std::begin; + using std::end; + using std::size; + return sliding_window_range{ + begin(c), end(c), static_cast(size(c))}; +} + +} // namespace osr diff --git a/include/osr/ways.h b/include/osr/ways.h index 963071b1..be7260db 100644 --- a/include/osr/ways.h +++ b/include/osr/ways.h @@ -61,6 +61,84 @@ struct restriction { way_pos_t applies_to_bus_ : 1; }; +struct way_instruction_properties { + + template + friend constexpr auto static_type_hash( + way_instruction_properties const*, + cista::hash_data h) noexcept { + return h.combine(cista::hash("way_instruction_properties v1")); + } + + template + friend void serialize(Ctx&, way_instruction_properties const*, + cista::offset_t) {} + + template + friend void deserialize(Ctx const&, way_instruction_properties*) {} + + constexpr highway get_highway() const { return static_cast(highway_); } + + constexpr junction get_junction() const { return static_cast(junction_); } + + constexpr bool is_link() const { return is_link_ == 1U; } + + constexpr bool is_footway_crossing() const { return is_footway_crossing_ == 1U; } + + constexpr std::uint8_t lanes_forward() const { return lanes_forward_; } + + constexpr std::uint8_t lanes_backward() const { return lanes_backward_; } + + constexpr bool has_lanes() const { return lanes_forward_ != 0 || lanes_backward_ != 0; } + + constexpr bool is_stairs() const { return is_stairs_up_ == 1U || is_stairs_down_ == 1U; } + + constexpr bool is_stairs_up() const { return is_stairs_up_ == 1U; } + + constexpr bool is_stairs_down() const { return is_stairs_down_ == 1U; } + + constexpr bool has_left_turn_lane(direction const dir) const { + return (static_cast(dir == direction::kForward ? turn_lanes_forward_ : turn_lanes_backward_) & 1U) != 0; + } + + constexpr bool has_through_lane(direction const dir) const { + return (static_cast(dir == direction::kForward ? turn_lanes_forward_ : turn_lanes_backward_) & 2U) != 0; + } + + constexpr bool has_right_turn_lane(direction const dir) const { + return (static_cast(dir == direction::kForward ? turn_lanes_forward_ : turn_lanes_backward_) & 4U) != 0; + } + + std::uint8_t highway_ : 3; + std::uint8_t is_link_ : 1; + std::uint8_t junction_ : 2; + std::uint8_t is_footway_crossing_ : 1; + std::uint8_t is_stairs_down_ : 1; + std::uint8_t is_stairs_up_ : 1; + + /* + * Bitmask representations for turn lane configurations mapping from OSM keys: + * (turn:lanes, turn:lanes:forward, turn:lanes:backward, turn:lanes:both_ways) + * + * Each 3-bit field uses the following layout: + * [ Bit 2 ] [ Bit 1 ] [ Bit 0 ] + * | | | + * | | +-- Leftish turn lane exists (e.g., left, sharp_left, slight_left) + * | +------------ Through lane exists (e.g., through) + * +---------------------- Rightish turn lane exists (e.g., right, sharp_right, slight_right) + * + * Example: 0b101 (5) means there are left and right turn lanes, but no through lane. + */ + std::uint8_t turn_lanes_forward_ : 3; + std::uint8_t turn_lanes_backward_ : 3; + + std::uint8_t reserved_ : 1; + std::uint8_t lanes_forward_ : 4; + std::uint8_t lanes_backward_ : 4; +}; + +static_assert(sizeof(way_instruction_properties) == 3); + struct way_properties { constexpr bool is_accessible() const { return is_car_accessible() || is_bike_accessible() || @@ -203,7 +281,18 @@ struct ways { void compute_turn_bearings(); void build_components(); - std::optional find_way(osm_way_idx_t const i) { + std::optional find_string(std::string const& s) const { + const auto it = + std::find_if(begin(strings_), end(strings_), + [&s](const auto& str) { return str.view() == s; }); + if (it != end(strings_)) { + return std::optional{string_idx_t{std::distance(begin(strings_), it)}}; + } + + return std::nullopt; + } + + std::optional find_way(osm_way_idx_t const i) const { auto const it = std::lower_bound(begin(way_osm_idx_), end(way_osm_idx_), i); return it != end(way_osm_idx_) && *it == i ? std::optional{way_idx_t{ @@ -398,6 +487,7 @@ struct ways { mm_vecvec way_osm_nodes_; mm_vecvec strings_; mm_vec_map way_names_; + mm_vec_map way_instruction_properties_; mm_bitvec way_has_conditional_access_no_; mm_vec> way_conditional_access_no_; diff --git a/src/extract.cc b/src/extract.cc index 962ae091..897961f5 100644 --- a/src/extract.cc +++ b/src/extract.cc @@ -7,6 +7,7 @@ #include "osr/extract/extract.h" #include "boost/thread/tss.hpp" +#include "boost/algorithm/string.hpp" #include "fmt/core.h" #include "fmt/std.h" @@ -117,6 +118,120 @@ speed_limit get_speed_limit(tags const& t) { } } +std::pair get_highway(tags const& t) { + switch (cista::hash(t.highway_)) { + case cista::hash("motorway"): return { motorway, false }; + case cista::hash("motorway_link"): return { motorway, true }; + case cista::hash("trunk"): return { trunk, false }; + case cista::hash("trunk_link"): return { trunk, true }; + case cista::hash("primary"): return { primary, false }; + case cista::hash("primary_link"): return { primary, true }; + case cista::hash("secondary"): return { secondary, false }; + case cista::hash("secondary_link"): return { secondary, true }; + case cista::hash("tertiary"): return { tertiary, false }; + case cista::hash("tertiary_link"): return { tertiary, true }; + case cista::hash("residential"): return { residential, false }; + + case cista::hash("footway"): + case cista::hash("bridleway"): + case cista::hash("steps"): + case cista::hash("corridor"): + case cista::hash("path"): + case cista::hash("via_ferrata"): return { footish, false }; + + default: return { highway_other, false }; + } +} + +std::pair get_lanes(tags const& t) { + if (is_number(t.lanes_forward_) && is_number(t.lanes_backward_)) { + return {static_cast(std::min( + 15U, utl::parse(t.lanes_forward_))), + static_cast(std::min( + 15U, utl::parse(t.lanes_backward_)))}; + } + + if (t.oneway_) { + const auto total_lanes = is_number(t.lanes_forward_) + ? t.lanes_forward_ + : (is_number(t.lanes_) ? t.lanes_ : "0"); + return {static_cast( + std::min(15U, utl::parse(total_lanes))), + 0U}; + } + + // Bidirectional way + + if (is_number(t.lanes_) && (is_number(t.lanes_forward_) || is_number(t.lanes_backward_))) { + const bool is_forward_lanes_defined = is_number(t.lanes_forward_); + const auto total_lanes = utl::parse(t.lanes_); + + const auto lanes_forward = + is_forward_lanes_defined + ? utl::parse(t.lanes_forward_) + : std::max(0U, + total_lanes - utl::parse(t.lanes_backward_)); + + const auto lanes_backward = + !is_forward_lanes_defined + ? utl::parse(t.lanes_backward_) + : std::max(0U, + total_lanes - utl::parse(t.lanes_forward_)); + + return {static_cast(std::min(15U, lanes_forward)), + static_cast(std::min(15U, lanes_backward))}; + } + + // if not specified further, when way is bidirectional it is assumed that + // the total number of lanes split evenly in both directions + if (is_number(t.lanes_)) { + if (const auto total_lanes = utl::parse(t.lanes_); + total_lanes % 2 != 0) { + // this is a weird case and mostly due + // to wrong tagging, so we return 0 in + // both directions + return {0U, 0U}; + } + const auto lanes_both = static_cast( + std::min(15U, utl::parse(t.lanes_) >> 1)); + + return {lanes_both, lanes_both}; + } + + return {0U, 0U}; +} + +std::pair get_stairs(tags const& t) { + if (t.highway_ == "steps") { + if (t.is_incline_down_) { + return {false, true}; + } else { + return {true, false}; + } + } + + return {false, false}; +} + +junction get_junction(tags const& t) { + switch (cista::hash(t.junction_)) { + case cista::hash("roundabout"): return roundabout; + case cista::hash("circular"): return circular; + case cista::hash("jughandle"): return jughandle; + default: return junction_none; + } +} + +bool is_foot_crossing(tags const& t) { + switch (cista::hash(t.footway_)) { + case cista::hash("crossing"): + case cista::hash("traffic_island"): return true; + default: return false; + } +} + + + struct rel_way { way_properties p_; platform_idx_t pl_{platform_idx_t::invalid()}; @@ -128,6 +243,73 @@ std::tuple get_levels(tags const& t) { return get_levels(t.has_level_, t.level_bits_); } +std::uint8_t parse_turn_lanes(std::string_view s) { + if (s.empty()) { + return 0; + } + std::uint8_t mask = 0; + while (!s.empty()) { + auto const pos = s.find('|'); + auto const part = (pos == std::string_view::npos) ? s : s.substr(0, pos); + + auto part_s = part; + while (!part_s.empty()) { + auto const semi_pos = part_s.find(';'); + auto const val = (semi_pos == std::string_view::npos) ? part_s : part_s.substr(0, semi_pos); + + auto const start = val.find_first_not_of(" \t\r\n"); + if (start != std::string_view::npos) { + auto const end = val.find_last_not_of(" \t\r\n"); + auto const trimmed = val.substr(start, end - start + 1); + if (boost::iequals(trimmed, "left") || boost::iequals(trimmed, "sharp_left") || + boost::iequals(trimmed, "slight_left")) { + mask |= 1U; + } else if (boost::iequals(trimmed, "through")) { + mask |= 2U; + } else if (boost::iequals(trimmed, "right") || boost::iequals(trimmed, "sharp_right") || + boost::iequals(trimmed, "slight_right")) { + mask |= 4U; + } + } + + if (semi_pos == std::string_view::npos) { + break; + } + part_s.remove_prefix(semi_pos + 1); + } + + if (pos == std::string_view::npos) { + break; + } + s.remove_prefix(pos + 1); + } + return mask; +} + +way_instruction_properties get_way_instruction_properties(tags const& t) { + auto ip = way_instruction_properties{}; + const auto [h, l] = get_highway(t); + ip.highway_ = h; + ip.is_link_ = l; + ip.junction_ = get_junction(t); + ip.is_footway_crossing_ = ip.highway_ == footish ? is_foot_crossing(t) : false; + + const auto [f, b] = get_lanes(t); + ip.lanes_forward_ = f; + ip.lanes_backward_ = b; + + const auto [is_u, is_d] = get_stairs(t); + ip.is_stairs_down_ = is_d ? 1U : 0U; + ip.is_stairs_up_ = is_u ? 1U : 0U; + + ip.turn_lanes_forward_ = parse_turn_lanes(t.turn_lanes_forward_) | + parse_turn_lanes(t.turn_lanes_) | + parse_turn_lanes(t.turn_lanes_both_ways_); + ip.turn_lanes_backward_ = parse_turn_lanes(t.turn_lanes_backward_) | + parse_turn_lanes(t.turn_lanes_both_ways_); + return ip; +} + way_properties get_way_properties( tags const& t, osm_obj_type const obj_type = osm_obj_type::kWay) { auto const [from, to, _] = get_levels(t); @@ -261,6 +443,8 @@ struct way_handler : public osm::handler::Handler { return; } + auto ip = get_way_instruction_properties(t); + if (!t.has_level_ && it != end(rel_ways_)) { p.from_level_ = it->second.p_.from_level_; p.to_level_ = it->second.p_.to_level_; @@ -307,6 +491,7 @@ struct way_handler : public osm::handler::Handler { w_.way_osm_idx_.push_back(osm_way_idx_t{w.positive_id()}); w_.r_->way_properties_.emplace_back(p); + w_.way_instruction_properties_.emplace_back(ip); w_.way_polylines_.emplace_back(w.nodes() | std::views::transform(get_point)); diff --git a/src/routing/instructions/directions.cc b/src/routing/instructions/directions.cc new file mode 100644 index 00000000..476b953a --- /dev/null +++ b/src/routing/instructions/directions.cc @@ -0,0 +1,78 @@ +#include "osr/routing/instructions/directions.h" + +#include "boost/beast/zlib/zlib.hpp" + +#include "geo/latlng.h" +#include "geo/webmercator.h" + +namespace osr { + +double normalize(double const angle) { + double x = std::fmod(angle + 180.0, 360.0); + if (x < 0) { + x += 360.0; + } + return x - 180.0; +} + +relative_direction get_relative_direction(const double angle) { + bool is_left = angle > 0; + double abs_angle = std::abs(angle); + if (abs_angle <= 10) { + return relative_direction::kStraight; + } + if (abs_angle <= 45) { + return is_left ? relative_direction::kSlightLeft : relative_direction::kSlightRight; + } + if (abs_angle <= 135) { + return is_left ? relative_direction::kLeft : relative_direction::kRight; + } + if (abs_angle < 180) { + return is_left ? relative_direction::kSharpLeft : relative_direction::kSharpRight; + } + + return relative_direction::kTurnAround; +} + +bool is_rightish(relative_direction const direction) { + switch (direction) { + case relative_direction::kSlightRight: + case relative_direction::kRight: + case relative_direction::kSharpRight: + return true; + default: + return false; + } +} + +bool is_leftish(relative_direction const direction) { + switch (direction) { + case relative_direction::kSlightLeft: + case relative_direction::kLeft: + case relative_direction::kSharpLeft: + return true; + default: + return false; + } +} + +bool is_slightish(relative_direction const dir) { + switch (dir) { + case relative_direction::kStraight: + case relative_direction::kSlightRight: + case relative_direction::kSlightLeft: + return true; + default: + return false; + } +} + +double get_angle(geo::latlng const& p1, + geo::latlng const& shared, + geo::latlng const& p2) { + const double b1 = geo::bearing(p1, shared); + const double b2 = geo::bearing(shared, p2); + return normalize(b2 - b1); // [-180,180] +} + +} // namespace osr \ No newline at end of file diff --git a/src/routing/instructions/instruction_annotator.cc b/src/routing/instructions/instruction_annotator.cc new file mode 100644 index 00000000..12405c55 --- /dev/null +++ b/src/routing/instructions/instruction_annotator.cc @@ -0,0 +1,107 @@ +#include "osr/routing/instructions/instruction_annotator.h" + +#include "osr/debug.h" +#include "osr/routing/path.h" +#include "utl/verify.h" + +#include "osr/routing/instructions/directions.h" +#include "osr/routing/instructions/instruction_util.h" +#include "osr/routing/instructions/modules/destination_module.h" +#include "osr/routing/instructions/modules/intersection_module.h" +#include "osr/routing/instructions/modules/motorway_module.h" +#include "osr/routing/instructions/modules/pedestrian_module.h" +#include "osr/routing/instructions/modules/roundabout_module.h" +#include "osr/util/sliding_window.h" + +namespace osr { + +instruction_annotator::instruction_annotator(ways const& w) + : ways_(w) + { + init_chain(); + } + +void instruction_annotator::init_chain() { + auto destination_module_ptr = std::make_shared(); + auto roundabout_module_ptr = std::make_shared(); + auto motorway_module_ptr = std::make_shared(); + auto pedestrian_module_ptr = std::make_shared(); + auto intersection_module_ptr = std::make_shared(); + + destination_module_ptr + ->set_next(roundabout_module_ptr) + ->set_next(motorway_module_ptr) + ->set_next(pedestrian_module_ptr) + // Fallback + ->set_next(intersection_module_ptr); + head_module_ = destination_module_ptr; + modules_.emplace_back(std::move(destination_module_ptr)); + modules_.emplace_back(std::move(roundabout_module_ptr)); + modules_.emplace_back(std::move(motorway_module_ptr)); + modules_.emplace_back(std::move(pedestrian_module_ptr)); + modules_.emplace_back(std::move(intersection_module_ptr)); +} + +bool can_be_annotated(segment_contexts_window const& window) { + if (is_last_segment(window)) { + return true; + } + + const auto& segment_context = *window.focus(); + const auto& next_segment_context = *window[1]; + const auto& segment = *segment_context.src_ptr_; + const auto& next_segment = *next_segment_context.src_ptr_; + + if (segment.to_ == node_idx_t::invalid() || + next_segment.from_ == node_idx_t::invalid() || + segment.way_ == way_idx_t::invalid() || + next_segment.way_ == way_idx_t::invalid() || + !segment_context.traversed_node_hub_.has_value()) { + return false; + } + + if (segment.to_ != next_segment.from_) { + return false; + } + + return true; +} + +void instruction_annotator::annotate(path& path) { + std::vector segment_contexts; + preprocess(path, segment_contexts); + + const auto context_windows = sliding_window<1>(segment_contexts); + for (const auto cw : context_windows) { + if (!can_be_annotated(cw)) { + continue; + } + trace("Matching context window against modules:\n"); + head_module_->process(ways_, path, cw); + } + + for (const auto& m : modules_) { + m->post_process(ways_, path, segment_contexts); + } +} + +void instruction_annotator::preprocess(path& path, + std::vector& meta) { + + auto& segments = path.segments_; + for (std::size_t i = 0; i + 1 < segments.size(); ++i) { + auto const& segment = segments[i]; + auto const& next_segment = segments[i + 1]; + + meta.emplace_back(&segments[i], + get_traversed_node_hub(segment, next_segment, ways_)); + } + + if (segments.size() > 1) { + // Sentinel for the last segment + meta.emplace_back(&segments.back(), + std::nullopt); + } +} + +} // namespace osr diff --git a/src/routing/instructions/instruction_util.cc b/src/routing/instructions/instruction_util.cc new file mode 100644 index 00000000..7fd59dfe --- /dev/null +++ b/src/routing/instructions/instruction_util.cc @@ -0,0 +1,404 @@ +#include "osr/routing/instructions/instruction_util.h" + +#include "osr/routing/instructions/module.h" + +namespace osr { + +bool is_from_motorway(ways const& w, traversed_node_hub const& hub) { + const auto& segment_instruction_properties = + w.way_instruction_properties_[hub.arrive_hub_on_.way_idx_]; + + return is_motorway_or_trunk(w, hub.arrive_hub_on_.way_idx_) && + !segment_instruction_properties.is_link(); +} + +bool is_from_ramp(ways const& w, traversed_node_hub const& hub) { + const auto& segment_instruction_properties = + w.way_instruction_properties_[hub.arrive_hub_on_.way_idx_]; + + return is_motorway_or_trunk(w, hub.arrive_hub_on_.way_idx_) && + segment_instruction_properties.is_link(); +} + +bool is_onto_motorway(ways const& w, traversed_node_hub const& hub) { + const auto& exit_from_hub = hub.get_exit(); + + const auto& segment_instruction_properties = + w.way_instruction_properties_[exit_from_hub.segment_.way_idx_]; + + return is_motorway_or_trunk(w, exit_from_hub.segment_.way_idx_) && + !segment_instruction_properties.is_link(); +} + +bool is_onto_ramp(ways const& w, traversed_node_hub const& hub) { + const auto& exit_from_hub = hub.get_exit(); + + const auto& segment_instruction_properties = + w.way_instruction_properties_[exit_from_hub.segment_.way_idx_]; + + return is_motorway_or_trunk(w, exit_from_hub.segment_.way_idx_) && + segment_instruction_properties.is_link(); +} + + +bool is_last_segment(segment_contexts_window const& window) { + return !window.has(1); +} + +bool ways_have_same_name(way_idx_t const w1, way_idx_t const w2, ways const& w) { + const string_idx_t s1 = w.way_names_[w1]; + if (s1 == string_idx_t::invalid()) { + return false; + } + + const string_idx_t s2 = w.way_names_[w2]; + if (s2 == string_idx_t::invalid()) { + return false; + } + + return s1 == s2; +} + +bool ways_have_same_highway(way_idx_t const w1, + way_idx_t const w2, + ways const& w) { + return w.way_instruction_properties_[w1].get_highway() == + w.way_instruction_properties_[w2].get_highway(); +} + +bool is_roundabout_way(ways const& w, way_idx_t const way) { + if (way == way_idx_t::invalid()) { + return false; + } + const auto junction = w.way_instruction_properties_[way].get_junction(); + return junction == junction::roundabout || + junction == junction::circular; +} + +bool is_link(ways const& w, way_idx_t const way) { + return w.way_instruction_properties_[way].is_link(); +} + +bool is_motorway_or_trunk(ways const& w, way_idx_t const way) { + const auto h = w.way_instruction_properties_[way].get_highway(); + return h == highway::motorway || h == highway::trunk; +} + +bool turn_onto_through_street(ways const& w, traversed_node_hub const& hub) { + + const auto& exit = hub.get_exit(); + for (std::size_t alt_idx = 1U; alt_idx < hub.alternatives_.size(); ++alt_idx) { + if (alt_idx == hub.exit_from_hub_idx_) { + continue; + } + + const auto& alt = hub.alternatives_[alt_idx]; + + const bool is_nearly_straight = std::abs(alt.angle_with_exit_) > 160.0; + const bool same_name = + ways_have_same_name(alt.segment_.way_idx_, + exit.segment_.way_idx_, w); + const bool same_highway = + ways_have_same_highway(alt.segment_.way_idx_, + exit.segment_.way_idx_, w); + + if (is_nearly_straight && same_name && same_highway) { + return true; + } + } + + return false; +} + +bool is_more_important_by(highway const h1, + highway const h2, + unsigned const diff) { + return h1 + diff <= h2; +} + +bool is_segment_mode(path::segment const& segment, mode const m) { + return segment.mode_ == m; +} + +way_idx_t get_way_from(segment_context const& context) { + return context.src_ptr_->way_; +} + +std::pair get_instruction_for_fork( + ways const& w, traversed_node_hub const& hub, size_t const left, size_t const right) { + utl::verify(left < right, "Alternatives are ordered in circular order"); + + const auto& alt_left = hub.alternatives_[left]; + const auto& alt_right = hub.alternatives_[right]; + + const auto alt_left_direction = + get_relative_direction(alt_left.angle_with_arrive_); + const auto alt_right_direction = + get_relative_direction(alt_right.angle_with_arrive_); + + const auto& left_props = + w.way_instruction_properties_[alt_left.segment_.way_idx_]; + const auto& right_props = + w.way_instruction_properties_[alt_right.segment_.way_idx_]; + + const auto left_highway = left_props.get_highway(); + const auto right_highway = right_props.get_highway(); + + if (alt_left_direction == relative_direction::kStraight && + alt_right_direction != relative_direction::kStraight) { + + if (is_more_important_by(right_highway, left_highway, 1)) { + return {stay_instruction{relative_direction::kLeft}, stay_instruction{relative_direction::kRight}}; + } + + return {none_instruction{}, stay_instruction{relative_direction::kRight}}; + } else if (alt_right_direction == relative_direction::kStraight && + alt_left_direction != relative_direction::kStraight) { + + if (is_more_important_by(left_highway, right_highway, 1)) { + return {stay_instruction{relative_direction::kLeft}, stay_instruction{relative_direction::kRight}}; + } + + return {stay_instruction{relative_direction::kLeft}, none_instruction{}}; + } + + return {stay_instruction{relative_direction::kLeft}, stay_instruction{relative_direction::kRight}}; +} + +std::tuple +get_instruction_for_fork(ways const& w, + traversed_node_hub const& hub, + size_t const left, + size_t const center, + size_t const right) { + utl::verify(left < center && center < right, + "Alternatives are ordered in circular order"); + + const auto& alt_left = hub.alternatives_[left]; + const auto& alt_center = hub.alternatives_[center]; + const auto& alt_right = hub.alternatives_[right]; + + if (alt_left.can_exit_hub() && + alt_center.can_exit_hub() && + alt_right.can_exit_hub()) { + + const auto alt_left_direction = + get_relative_direction(alt_left.angle_with_arrive_); + if (alt_left_direction == relative_direction::kStraight) { + return { + stay_instruction{relative_direction::kStraight}, + stay_instruction{relative_direction::kSlightRight}, + stay_instruction{relative_direction::kRight} + }; + } + + const auto alt_right_direction = + get_relative_direction(alt_right.angle_with_arrive_); + if (alt_right_direction == relative_direction::kStraight) { + return { + stay_instruction{relative_direction::kLeft}, + stay_instruction{relative_direction::kSlightLeft}, + stay_instruction{relative_direction::kStraight} + }; + } + + // center is somewhat straight + return { + stay_instruction{relative_direction::kLeft}, + stay_instruction{relative_direction::kStraight}, + stay_instruction{relative_direction::kRight} + }; + } + + if (alt_left.can_exit_hub()) { + if (alt_right.can_exit_hub()) { + const auto fork_instructions = + get_instruction_for_fork(w, hub, left, right); + return { + fork_instructions.first, + none_instruction{}, + fork_instructions.second}; + } + if (alt_center.can_exit_hub()) { + const auto fork_instructions = + get_instruction_for_fork(w, hub, left, center); + return { + fork_instructions.first, + fork_instructions.second, + none_instruction{} + }; + } + + return { + stay_instruction{relative_direction::kLeft}, + none_instruction{}, + none_instruction{} + }; + } + + if (alt_right.can_exit_hub()) { + if (alt_center.can_exit_hub()) { + const auto fork_instructions = + get_instruction_for_fork(w, hub, center, right); + + return { + none_instruction{}, + fork_instructions.first, + fork_instructions.second + }; + } + + return { + none_instruction{}, + none_instruction{}, + stay_instruction{relative_direction::kRight} + }; + } + + + if (alt_center.can_exit_hub()) { + return { + none_instruction{}, + stay_instruction{relative_direction::kStraight}, + none_instruction{} + }; + } + + return { + none_instruction{}, + none_instruction{}, + none_instruction{} + }; +} + +std::optional get_traversed_node_hub( + path::segment const& segment, path::segment const& next_segment, + ways const& w) { + if (segment.way_ == way_idx_t::invalid() || + next_segment.way_ == way_idx_t::invalid()) { + return std::nullopt; + } + + if (segment.from_ == node_idx_t::invalid() || segment.to_ == node_idx_t::invalid() || + next_segment.from_ == node_idx_t::invalid() || next_segment.to_ == node_idx_t::invalid()) { + return std::nullopt; + } + + return traversed_node_hub::from(w, segment.from_, segment.way_, segment.to_, + next_segment.way_, next_segment.to_, + !segment.is_reverse_, !next_segment.is_reverse_, + segment.mode_); +} + +unsigned long number_of_possible_turns_at(traversed_node_hub const& hub, + bool const left) { + constexpr auto is_possible = + [](traversed_node_hub::relative_way_segment const& rel_seg) { + return rel_seg.can_exit_hub_; + }; + + const auto count = + std::ranges::count_if(hub.get_exit_neighbors(left), is_possible); + return static_cast(count); +} + +std::optional get_crossing_street(ways const& w, + traversed_node_hub const& h) { + const auto& exit_segment = h.get_exit(); + + const auto from_way_idx = h.arrive_hub_on_.way_idx_; + const auto to_way_idx = exit_segment.segment_.way_idx_; + + const auto& from_way_instruction_props = + w.way_instruction_properties_[from_way_idx]; + const auto& to_way_instruction_props = + w.way_instruction_properties_[to_way_idx]; + + if (from_way_instruction_props.get_highway() != highway::footish || + to_way_instruction_props.get_highway() != highway::footish) { + return std::nullopt; + } + + const auto left_alts = h.get_exit_neighbors(true); + const auto right_alts = h.get_exit_neighbors(false); + + string_idx_t crossed_street = string_idx_t::invalid(); + for (const auto& left_alt : left_alts) { + const auto& left_way_instruction_props = + w.way_instruction_properties_[left_alt.segment_.way_idx_]; + + if (!is_more_important_by(left_way_instruction_props.get_highway(), footish, + 1)) { + continue; + } + + for (const auto& right_alt : right_alts) { + const auto& right_way_instruction_props = + w.way_instruction_properties_[right_alt.segment_.way_idx_]; + + if (!is_more_important_by(right_way_instruction_props.get_highway(), + footish, 1)) { + continue; + } + + if (!ways_have_same_name(left_alt.segment_.way_idx_, + right_alt.segment_.way_idx_, w)) { + continue; + } + + const auto street_found = w.way_names_[left_alt.segment_.way_idx_]; + if (crossed_street == string_idx_t::invalid()) { + crossed_street = street_found; + } else if (crossed_street != string_idx_t::invalid() && + crossed_street != street_found) { + // crossing multiple streets, cannot give definitive answer + return std::nullopt; + } + } + } + if (crossed_street != string_idx_t::invalid()) { + return std::optional{crossed_street}; + } + + return std::nullopt; +} + +bool is_merge_on_through_street(ways const& w, + traversed_node_hub const& h) { + const auto coming_from_way = h.arrive_hub_on_.way_idx_; + + const auto exit = h.get_exit(); + const auto exit_way = exit.segment_.way_idx_; + if (ways_have_same_name(coming_from_way, exit_way, w)) { + return false; + } + + const bool is_left_merge = exit.angle_with_arrive_ > 0; + + const auto get_opt_exit_predecessor = [&] -> std::optional { + + const auto neighbor_idx_from = is_left_merge ? h.exit_from_hub_idx_ + 1 : 1; + const auto neighbor_idx_to = is_left_merge ? h.alternatives_.size() : h.exit_from_hub_idx_; + for (auto neighbor_idx = neighbor_idx_from; neighbor_idx < neighbor_idx_to; neighbor_idx++) { + const auto neighbor = h.alternatives_[neighbor_idx]; + const auto neighbor_way = neighbor.segment_.way_idx_; + const auto angle_with_exit = h.angle_with_exit_from(w, neighbor_idx); + if (ways_have_same_name(neighbor_way, exit_way, w) && std::abs(angle_with_exit) < 20.0) { + return std::make_optional(h.alternatives_[neighbor_idx]); + } + } + + return std::nullopt; + }; + const auto opt_exit_predecessor = get_opt_exit_predecessor(); + if (opt_exit_predecessor.has_value()) { + return true; + } + return false; +} + +bool is_pedestrian(mode const m) { + return m == mode::kFoot || m == mode::kWheelchair; +} + +} // namespace osr \ No newline at end of file diff --git a/src/routing/instructions/meta_data.cc b/src/routing/instructions/meta_data.cc new file mode 100644 index 00000000..659d2117 --- /dev/null +++ b/src/routing/instructions/meta_data.cc @@ -0,0 +1,568 @@ +#include "osr/routing/instructions/meta_data.h" + +#include "boost/json.hpp" + +#include "osr/routing/instructions/instruction_util.h" +#include "utl/pipes/all.h" +#include "utl/pipes/transform.h" +#include "utl/pipes/vec.h" + +namespace osr { + +using hub_t = traversed_node_hub; +using osm_nodes_range_t = hub_t::way_osm_nodes_idx_range; +using way_segment_t = hub_t::way_segment; +using relative_way_segment_t = hub_t::relative_way_segment; + +osm_nodes_range_t osm_nodes_range_t::flip() const { + return { + .from_ = to_, + .to_ = from_, + }; +} + +bool way_segment_t::is_way_aligned() const { + return osm_node_range_.from_ <= osm_node_range_.to_; +} + +distance_t get_min_exit_segment_distance_for_mode(ways const& w, + way_idx_t const way, + mode const m) { + if (is_pedestrian(m)) { + return 5; + } + const auto highway_type = w.way_instruction_properties_[way].get_highway(); + switch (highway_type) { + case highway::motorway: return 100; + case highway::trunk: return 80; + case highway::primary: + case highway::secondary: return 20; + default: return 10; + } +} + +distance_t get_min_arrival_segment_distance_for_mode(ways const& w, + way_idx_t const way, + mode const m) { + if (is_pedestrian(m)) { + return 0; + } + const auto highway_type = w.way_instruction_properties_[way].get_highway(); + switch (highway_type) { + case highway::motorway: return 30; + case highway::trunk: return 20; + case highway::primary: + case highway::secondary: return 20; + default: return 5; + } +} + +way_segment_t way_segment_t::from(const ways& w, + way_idx_t const way, + osm_node_idx_t const from, + osm_node_idx_t const to, + bool const is_way_aligned) { + + const auto& way_osm_nodes = w.way_osm_nodes_[way]; + + std::uint16_t to_idx; + std::uint16_t from_idx = to_idx = + static_cast(way_osm_nodes.size()); + + const auto first_it = + std::ranges::find(way_osm_nodes, is_way_aligned ? from : to); + const auto second_it = + std::find(first_it, way_osm_nodes.end(), is_way_aligned ? to : from); + + if (first_it != way_osm_nodes.end() && second_it != way_osm_nodes.end()) { + from_idx = static_cast(std::distance( + way_osm_nodes.begin(), is_way_aligned ? first_it : second_it)); + to_idx = static_cast(std::distance( + way_osm_nodes.begin(), is_way_aligned ? second_it : first_it)); + } + + return {.way_idx_ = way, + .osm_node_range_ = { + .from_ = from_idx, + .to_ = to_idx, + }}; +} + +way_segment_t way_segment_t::flip_directions() const { + return {.way_idx_ = way_idx_, .osm_node_range_ = osm_node_range_.flip()}; +} + +osm_node_idx_t way_segment_t::get_from_idx(ways const& w) const { + return w.way_osm_nodes_[way_idx_][osm_node_range_.from_]; +} + +osm_node_idx_t way_segment_t::get_to_idx(ways const& w) const { + return w.way_osm_nodes_[way_idx_][osm_node_range_.to_]; +} + +point way_segment_t::get_from_position(ways const& w) const { + return w.way_polylines_[way_idx_][osm_node_range_.from_]; +} + +point way_segment_t::get_to_position(ways const& w) const { + return w.way_polylines_[way_idx_][osm_node_range_.to_]; +} + + +namespace { + +bool is_accessible(ways const& w, + way_segment_t const& ws, + mode const m) { + + const auto w_props = w.r_->way_properties_[ws.way_idx_]; + switch (m) { + case mode::kBike: + if (!w_props.is_bike_accessible()) return false; + break; + case mode::kCar: + if (!w_props.is_car_accessible()) return false; + break; + case mode::kFerry: + if (!w_props.is_ferry_accessible()) return false; + break; + case mode::kRailway: + if (!w_props.is_railway_accessible()) return false; + break; + case mode::kFoot: + case mode::kWheelchair: + if (!w_props.is_foot_accessible()) return false; + } + + if (m == mode::kFoot || m == mode::kWheelchair) { + return true; + } + + const auto travel_dir = + ws.is_way_aligned() ? direction::kForward : direction::kBackward; + if (m == mode::kBike && w_props.is_oneway_bike() && + travel_dir == direction::kBackward) { + return false; + } + if (m == mode::kCar && w_props.is_oneway_car() && + travel_dir == direction::kBackward) { + return false; + } + + return true; +} + +void emplace_relative_way_segment( + ways const& w, + double const exit_angle, + point const prev_hub, + point const hub, + way_segment_t const& ws, + mode const m, + bool const is_uturn, + std::vector& alternatives) { + const auto& way_polyline = w.way_polylines_[ws.way_idx_]; + + point const to = way_polyline[ws.osm_node_range_.to_]; + double const alt_angle = + get_angle(prev_hub.as_latlng(), hub.as_latlng(), to.as_latlng()); + double const diff = alt_angle - exit_angle; + + bool const can_exit_hub = is_accessible(w, ws, m); + bool const can_enter_hub = is_accessible(w, ws.flip_directions(), m); + double const relative_diff = normalize(diff); + alternatives.emplace_back(relative_diff, alt_angle, can_enter_hub, + can_exit_hub, is_uturn, ws); +} + +struct hub_geometry { + point from_; + point hub_; + point to_; +}; + +std::uint16_t get_next_osm_way_node_idx_or_last(ways const& w, + way_idx_t const way_idx, + std::uint16_t const osm_node_idx, + direction const dir, + distance_t const min_distance = 10) { + const auto polyline = w.way_polylines_[way_idx]; + + if (polyline.size() <= 1) { + return 0; + } + + if (dir == direction::kForward) { + double accumulated_dist = 0.0; + for (std::size_t i = osm_node_idx + 1; i < polyline.size(); ++i) { + accumulated_dist += geo::distance(polyline[i - 1], polyline[i]); + if (accumulated_dist >= min_distance) { + return static_cast(i); + } + } + return static_cast(polyline.size() - 1); + } else { + double accumulated_dist = 0.0; + for (int i = static_cast(osm_node_idx) - 1; i >= 0; --i) { + accumulated_dist += geo::distance(polyline[static_cast(i + 1)], + polyline[static_cast(i)]); + if (accumulated_dist >= min_distance) { + return static_cast(i); + } + } + return 0; + } +} + +hub_geometry get_hub_geometry(ways const& w, + way_segment_t const& arrive, + way_segment_t const& exit, + mode const m) { + const auto& arrive_polyline = w.way_polylines_[arrive.way_idx_]; + const auto& exit_polyline = w.way_polylines_[exit.way_idx_]; + + const point hub = arrive_polyline[arrive.osm_node_range_.to_]; + const auto arrive_prev_idx = get_next_osm_way_node_idx_or_last(w, + arrive.way_idx_, arrive.osm_node_range_.to_, + arrive.is_way_aligned() ? + direction::kBackward : direction::kForward, + get_min_arrival_segment_distance_for_mode(w, arrive.way_idx_, m)); + utl::verify(arrive_prev_idx < arrive_polyline.size(), "out of bounds"); + const point from = arrive_polyline[arrive_prev_idx]; + const auto exit_next_idx = get_next_osm_way_node_idx_or_last(w, + exit.way_idx_, exit.osm_node_range_.from_, + exit.is_way_aligned() ? + direction::kForward : direction::kBackward, + get_min_exit_segment_distance_for_mode(w, exit.way_idx_, m)); + utl::verify(exit_next_idx < exit_polyline.size(), "out of bounds"); + const point to = exit_polyline[exit_next_idx]; + + return {from, hub, to}; +} + +void add_alternatives(ways const& w, + node_idx_t const hub_node, + osm_node_idx_t const osm_hub_node, + way_segment_t const& arrive_hub_on, + way_segment_t const& exit_from_hub, + way_idx_t const exit_way_idx, + double const exit_angle, + point const from_point, + point const hub_point, + mode const m, + std::vector& alternatives) { + const auto& alt_ways = w.r_->node_ways_[hub_node]; + for (const auto alt_way : alt_ways) { + const auto& osm_nodes = w.way_osm_nodes_[alt_way]; + + // Forward Search + const auto forward_it = std::ranges::find(osm_nodes, osm_hub_node); + if (forward_it == osm_nodes.end()) { + continue; + } + + const auto forward_hub_node_idx = + static_cast(std::distance(osm_nodes.begin(), forward_it)); + + if (forward_hub_node_idx + 1U < osm_nodes.size() && + (alt_way != exit_way_idx || !exit_from_hub.is_way_aligned())) { + way_segment_t alt_seg_forw = { + .way_idx_ = alt_way, + .osm_node_range_ = { + .from_ = forward_hub_node_idx, + .to_ = get_next_osm_way_node_idx_or_last( + w, alt_way, forward_hub_node_idx, + direction::kForward, get_min_exit_segment_distance_for_mode(w, alt_way, m))}}; + + const bool is_uturn_alt = + alt_way == arrive_hub_on.way_idx_ && + alt_seg_forw.is_way_aligned() ^ arrive_hub_on.is_way_aligned(); + + emplace_relative_way_segment(w, exit_angle, from_point, hub_point, + alt_seg_forw, m, is_uturn_alt, alternatives); + } + + // Backward search + const auto backward_it = std::find(osm_nodes.rbegin(), osm_nodes.rend(), osm_hub_node); + if (backward_it == osm_nodes.rend()) { + continue; + } + + const auto backward_hub_node_idx = static_cast( + std::distance(osm_nodes.begin(), backward_it.base()) - 1); + + if (backward_hub_node_idx > 0 && + (alt_way != exit_way_idx || exit_from_hub.is_way_aligned())) { + way_segment_t alt_seg_rev = { + .way_idx_ = alt_way, + .osm_node_range_ = { + .from_ = backward_hub_node_idx, + .to_ = get_next_osm_way_node_idx_or_last( + w, alt_way, backward_hub_node_idx, + direction::kBackward, get_min_exit_segment_distance_for_mode(w, alt_way, m))}}; + + const bool is_uturn_alt = + alt_way == arrive_hub_on.way_idx_ && + alt_seg_rev.is_way_aligned() ^ arrive_hub_on.is_way_aligned(); + + emplace_relative_way_segment(w, exit_angle, from_point, hub_point, + alt_seg_rev, m, is_uturn_alt, alternatives); + } + + } +} + +} // namespace + +traversed_node_hub traversed_node_hub::from(ways const& w, + node_idx_t const prev_node, + way_idx_t const arrive_way, + node_idx_t const hub_node, + way_idx_t const depart_way, + node_idx_t const next_node, + bool const is_arrive_way_aligned, + bool const is_exit_way_aligned, + mode const m) { + traversed_node_hub node_hub; + node_hub.hub_node_ = hub_node; + + osm_node_idx_t const osm_hub_node = w.node_to_osm_[node_hub.hub_node_]; + + osm_node_idx_t const osm_prev_hub_node = w.node_to_osm_[prev_node]; + osm_node_idx_t const osm_next_hub_node = w.node_to_osm_[next_node]; + + + + node_hub.arrive_hub_on_ = way_segment::from( + w, arrive_way, osm_prev_hub_node, osm_hub_node, is_arrive_way_aligned); + const auto exit_from_hub = way_segment::from( + w, depart_way, osm_hub_node, osm_next_hub_node, is_exit_way_aligned); + + auto const geo = + get_hub_geometry(w, node_hub.arrive_hub_on_, exit_from_hub, m); + const auto exit_angle = get_angle(geo.from_.as_latlng(), geo.hub_.as_latlng(), + geo.to_.as_latlng()); + + add_alternatives(w, node_hub.hub_node_, osm_hub_node, node_hub.arrive_hub_on_, + exit_from_hub, depart_way, exit_angle, geo.from_, geo.hub_, + m, node_hub.alternatives_); + + const auto u_turn_seg = + std::ranges::find_if(node_hub.alternatives_, [](const auto& rel_way_seg) { + return rel_way_seg.is_opposite_of_arrive_; + }); + + if (u_turn_seg == node_hub.alternatives_.end()) { + utl::fail("U turn segment not found"); + } + + if (u_turn_seg != node_hub.alternatives_.begin()) { + std::iter_swap(node_hub.alternatives_.begin(), u_turn_seg); + } + + std::ranges::sort(node_hub.alternatives_ | std::views::drop(1), + std::ranges::greater{}, + &relative_way_segment::angle_with_arrive_); + + relative_way_segment exit_segment = { + .angle_with_exit_ = 0.0, + .angle_with_arrive_ = exit_angle, + .can_enter_hub_ = is_accessible(w, exit_from_hub.flip_directions(), m), + .can_exit_hub_ = true, + .is_opposite_of_arrive_ = false, + .segment_ = exit_from_hub + }; + + auto iter = std::ranges::upper_bound( + node_hub.alternatives_ | std::views::drop(1), + exit_segment.angle_with_arrive_, std::ranges::greater{}, + &relative_way_segment::angle_with_arrive_); + + auto const inserted_iter = node_hub.alternatives_.insert(iter, exit_segment); + node_hub.exit_from_hub_idx_ = static_cast( + std::distance(node_hub.alternatives_.begin(), inserted_iter)); + + return node_hub; +} + +traversed_node_hub::relative_way_segment traversed_node_hub::get_exit() const { + return alternatives_[exit_from_hub_idx_]; +} + +unsigned long traversed_node_hub::number_of_possible_turns( + bool const consider_uturn) const { + constexpr auto is_possible = [](relative_way_segment_t const& rel_seg) { + return rel_seg.can_exit_hub_; + }; + // drop because we omit the u-turn + const auto count = std::ranges::count_if( + alternatives_ | std::views::drop(consider_uturn ? 0 : 1), is_possible); + return static_cast(count); +} + +unsigned long traversed_node_hub::number_of_visible_turns( + bool const consider_uturn) const { + return static_cast(alternatives_.size() - (consider_uturn ? 0 : 1)); +} + +double traversed_node_hub::angle_with_exit_from( + ways const& w, std::size_t const alt_idx) const { + utl::verify(alt_idx < alternatives_.size(), "Alternative out of bounds"); + if (alt_idx == exit_from_hub_idx_) { + return 0.0; + } + + const auto& from = alternatives_[alt_idx]; + const auto& exit = get_exit(); + + // This must be get_to_idx because the direction + // of alternative way segment at a hub is always from + // hub + const auto from_node_p = from.segment_.get_to_position(w); + const auto hub_node_p = exit.segment_.get_from_position(w); + const auto exit_node_p = exit.segment_.get_to_position(w); + return get_angle(from_node_p.as_latlng(), hub_node_p.as_latlng(), + exit_node_p.as_latlng()); +} + +void traversed_node_hub::print(std::ostream& out, ways const& w) const { + out << "hub_node=" << w.node_to_osm_[hub_node_] + << ", arrive_on=" << w.way_osm_idx_[arrive_hub_on_.way_idx_]; + + auto const print_alts = [&](std::string_view name, + std::vector const& alts) { + out << ", " << name << "=["; + for (auto i = 0U; i < alts.size(); ++i) { + auto const& alt = alts[i]; + out << "{way=" << w.way_osm_idx_[alt.segment_.way_idx_] + << ", angle_with_exit=" << alt.angle_with_exit_ + << ", angle_with_arrive=" << alt.angle_with_arrive_ + << ", can_enter_hub=" << (alt.can_enter_hub_ ? "true" : "false") + << ", can_exit_hub=" << (alt.can_exit_hub_ ? "true" : "false") + << ", is_exit_taken=" << (i == exit_from_hub_idx_ ? "true" : "false") + << "}"; + if (i != alts.size() - 1) { + out << ", "; + } + } + out << "]"; + }; + + print_alts("alternatives", alternatives_); +} + +// TODO +bool traversed_node_hub::is_exit_natural_choice(ways const& w) const { + if (!ways_have_same_name(arrive_hub_on_.way_idx_, + get_exit().segment_.way_idx_, w)) { + return false; + } + + return true; +} + +bool traversed_node_hub::is_end_of_turn_lane(ways const& w, bool left, bool const dedicated) const { + constexpr auto is_dedicated_turn_lane = [](way_instruction_properties const& props, + direction const dir, + bool const l) -> bool { + return (l ? props.has_left_turn_lane(dir) && !props.has_right_turn_lane(dir) + : props.has_right_turn_lane(dir) && !props.has_left_turn_lane(dir)) + && !props.has_through_lane(dir); + }; + + constexpr auto is_turn_lane = [](way_instruction_properties const& props, + direction const dir, + bool const l) -> bool { + return l ? props.has_left_turn_lane(dir) + : props.has_right_turn_lane(dir); + + }; + + const auto applies = dedicated ? is_dedicated_turn_lane : is_turn_lane; + + direction const arrive_dir = arrive_hub_on_.is_way_aligned() ? + direction::kForward : + direction::kBackward; + const auto arrive_props = + w.way_instruction_properties_[arrive_hub_on_.way_idx_]; + const auto is_arrive_dedicated_turn_lane = + applies(arrive_props, arrive_dir, left); + if (!is_arrive_dedicated_turn_lane) { + return false; + } + + const auto& exit = get_exit(); + const auto exit_props = w.way_instruction_properties_[exit.segment_.way_idx_]; + direction const exit_dir = exit.segment_.is_way_aligned() ? + direction::kForward : + direction::kBackward; + return !applies(exit_props,exit_dir , left); +} + +bool traversed_node_hub::does_way_names_change(ways const& w) const { + const auto arrive_way = arrive_hub_on_.way_idx_; + const auto exit_way = get_exit().segment_.way_idx_; + return ! ways_have_same_name(arrive_way, exit_way, w); +} + +std::string traversed_node_hub::to_json(ways const& w) const { + const auto way_segment_to_json = [&w](way_segment const& way_seg) { + if (way_seg.way_idx_ == way_idx_t::invalid()) { + return boost::json::object{{ + "way_id", + 0U, + }, + {"node_range", boost::json::array{0, 0}}}; + } + const auto& way_osm_nodes = w.way_osm_nodes_[way_seg.way_idx_]; + return boost::json::object{ + {"way_id", to_idx(w.way_osm_idx_[way_seg.way_idx_])}, + {"node_range", + boost::json::array{ + to_idx(way_osm_nodes[way_seg.osm_node_range_.from_]), + to_idx(way_osm_nodes[way_seg.osm_node_range_.to_])}}}; + }; + + const auto relative_way_segment_to_json = + [&way_segment_to_json](relative_way_segment const& rel_way_seg) { + return boost::json::object{ + {"segment", way_segment_to_json(rel_way_seg.segment_)}, + {"angle_with_exit", rel_way_seg.angle_with_exit_}, + {"angle_with_arrive", rel_way_seg.angle_with_arrive_}, + {"can_enter_hub", rel_way_seg.can_enter_hub_}, + {"can_exit_hub", rel_way_seg.can_exit_hub_}, + {"is_opposite_of_arrive", rel_way_seg.is_opposite_of_arrive_}}; + }; + + const auto rel_segments_to_json_array = + [&relative_way_segment_to_json]( + std::vector const& rel_segments) { + return utl::all(rel_segments) | + utl::transform([&](relative_way_segment const& rel_way_seg) { + return relative_way_segment_to_json(rel_way_seg); + }) | + utl::emplace_back_to(); + }; + + + return boost::json::serialize(boost::json::object{ + {"arrive_hub_on", way_segment_to_json(arrive_hub_on_)}, + {"hub_node", hub_node_ == node_idx_t::invalid() ? 0U : to_idx(w.node_to_osm_[hub_node_])}, + {"alternatives", rel_segments_to_json_array(alternatives_)}, + {"exit_from_hub_idx", exit_from_hub_idx_} + }); +} + +bool relative_way_segment_t::can_enter_hub() const { + return can_enter_hub_; +} + +bool relative_way_segment_t::can_exit_hub() const { + return can_exit_hub_; +} + +bool relative_way_segment_t::is_opposite_of_arrive() const { + return is_opposite_of_arrive_; +} + +} // namespace osr \ No newline at end of file diff --git a/src/routing/instructions/modules/destination_module.cc b/src/routing/instructions/modules/destination_module.cc new file mode 100644 index 00000000..f434ebfe --- /dev/null +++ b/src/routing/instructions/modules/destination_module.cc @@ -0,0 +1,24 @@ +#include "osr/routing/instructions/modules/destination_module.h" + +#include "osr/debug.h" +#include "osr/routing/instructions/instruction_util.h" + +namespace osr { + +void destination_module::process(ways const& w, + path& p, + segment_contexts_window const& window) { + trace("┃ ╔ DESTINATION MODULE\n"); + if (is_last_segment(window)) { + trace("┃ ╚ inspecting last segment -> annotation=kDestination\n"); + const auto& segment_context = *window.focus(); + segment_context.src_ptr_->instruction_annotation_ = + destination_instruction{}; + return; + } + + trace("┃ ║ not responsible, passing to next module\n"); + instruction_module::process(w, p, window); +} + +} // namespace osr diff --git a/src/routing/instructions/modules/intersection_module.cc b/src/routing/instructions/modules/intersection_module.cc new file mode 100644 index 00000000..633031ea --- /dev/null +++ b/src/routing/instructions/modules/intersection_module.cc @@ -0,0 +1,403 @@ +#include "osr/routing/instructions/modules/intersection_module.h" + +#include +#include + +#include "osr/debug.h" +#include "osr/routing/instructions/directions.h" +#include "osr/routing/instructions/instruction_util.h" +#include "osr/routing/instructions/routing_instruction.h" + +namespace osr { + +using hub_t = traversed_node_hub; +using osm_nodes_range_t = hub_t::way_osm_nodes_idx_range; +using way_segment_t = hub_t::way_segment; +using relative_way_segment_t = hub_t::relative_way_segment; + +bool is_real_turn(relative_direction const dir) { + return dir != relative_direction::kStraight && + dir != relative_direction::kSlightRight && + dir != relative_direction::kSlightLeft && + dir != relative_direction::kInvalid; +} + +routing_instruction turn_action_from(relative_direction dir) { + // Map relative direction to turn instruction + switch (dir) { + case relative_direction::kStraight: + return continue_instruction{}; + case relative_direction::kSlightRight: + case relative_direction::kRight: + case relative_direction::kSharpRight: + case relative_direction::kTurnAround: + case relative_direction::kSharpLeft: + case relative_direction::kLeft: + case relative_direction::kSlightLeft: + return turn_instruction{dir}; + case relative_direction::kInvalid: + return none_instruction{}; + } + return none_instruction{}; +} + +void set_segment_instruction(path::segment& seg, + routing_instruction const action) { + seg.instruction_annotation_ = action; +} + +bool is_simple_u_turn(hub_t const& hub) { + const auto& arrive_on = hub.arrive_hub_on_; + const auto& exit_on = hub.get_exit().segment_; + + return arrive_on.way_idx_ == exit_on.way_idx_ && + (arrive_on.is_way_aligned() ^ exit_on.is_way_aligned()); +} + +std::optional get_next_alternative( + hub_t const& hub, bool const to_left) { + const auto neighbors = hub.get_exit_neighbors(to_left); + if (neighbors.empty()) { + return std::nullopt; + } + + return to_left ? neighbors.back() : neighbors.front(); +} + +std::optional get_other_continue( + hub_t const& hub, double const cutoff_angle) { + std::optional best; + + auto const consider = [&](relative_way_segment_t const& alt) { + if (!alt.can_exit_hub_ || alt.is_opposite_of_arrive_) { + return; + } + double const abs_angle = std::abs(alt.angle_with_arrive_); + if (abs_angle > std::abs(cutoff_angle)) { + return; + } + + if (!best.has_value() || abs_angle < std::abs(best->angle_with_arrive_)) { + best = alt; + } + }; + + for (auto const& alt : hub.get_exit_neighbors(true)) { + consider(alt); + } + for (auto const& alt : hub.get_exit_neighbors(false)) { + consider(alt); + } + + return best; +} + +bool outgoing_edges_are_slower_by_factor(ways const& w, + hub_t const& hub, + double const factor) { + auto const chosen_speed = static_cast( + w.r_->way_properties_[hub.get_exit().segment_.way_idx_] + .max_speed_km_per_h()); + if (chosen_speed <= 0.0) { + return false; + } + + auto const is_relevant = [](relative_way_segment_t const& alt) { + return alt.can_exit_hub_ && !alt.is_opposite_of_arrive_; + }; + + for (auto const& alt : hub.get_exit_neighbors(true)) { + if (!is_relevant(alt)) { + continue; + } + auto const alt_speed = static_cast( + w.r_->way_properties_[alt.segment_.way_idx_].max_speed_km_per_h()); + if (alt_speed >= chosen_speed / factor) { + return false; + } + } + + for (auto const& alt : hub.get_exit_neighbors(false)) { + if (!is_relevant(alt)) { + continue; + } + auto const alt_speed = static_cast( + w.r_->way_properties_[alt.segment_.way_idx_].max_speed_km_per_h()); + if (alt_speed >= chosen_speed / factor) { + return false; + } + } + + return true; +} + +bool same_road_signature(ways const& w, way_idx_t const a, way_idx_t const b) { + if (a == way_idx_t::invalid() || b == way_idx_t::invalid()) { + return false; + } + + if (!ways_have_same_name(a, b, w)) { + return false; + } + + auto const pa = w.way_instruction_properties_[a]; + auto const pb = w.way_instruction_properties_[b]; + return pa.get_highway() == pb.get_highway() && + pa.is_link() == pb.is_link(); +} + +bool is_major_road(way_instruction_properties const props) { + auto const h = props.get_highway(); + return h == motorway || h == trunk || h == primary || h == secondary || + h == tertiary; +} + +routing_instruction handle_real_turn(ways const& w, + segment_context const& seg_context, + segment_context const& next_seg_context) { + const auto& hub = seg_context.traversed_node_hub_.value(); + const relative_direction next_seg_rel_dir = + get_relative_direction(hub.get_exit().angle_with_arrive_); + trace("┃ ║ handling real turn (exit_angle={})\n", + hub.get_exit().angle_with_arrive_); + utl::verify(is_real_turn(next_seg_rel_dir), "Not a real turn"); + + auto& segment = *seg_context.src_ptr_; + const auto& next_segment = *next_seg_context.src_ptr_; + + const routing_instruction potential_turn_type = turn_action_from(next_seg_rel_dir); + + trace("┃ ║ trying to check if a turn instruction is not needed\n"); + + const auto num_possible_turns = + hub.number_of_possible_turns(false); + const auto num_visible_turns = hub.number_of_visible_turns(false); + if (num_visible_turns <= 1) { + trace("┃ ║ number of visible turns ≤ 1 -> no instruction needed!\n"); + return none_instruction{}; + } + + const bool stay_on_same_name = + ways_have_same_name(segment.way_, next_segment.way_, w); + + if (num_possible_turns <= 1) { + if (stay_on_same_name) { + trace( + "┃ ║ number of possible turns ≤ 1 and we continue on same street " + "-> " + "no instruction needed!\n"); + return none_instruction{}; + } else if (!hub.is_end_of_turn_lane(w, true, true) && + !hub.is_end_of_turn_lane(w, false, true)) { + trace( + "┃ ║ number of possible turns ≤ 1 and do not continue on same street " + "-> " + "kContinue is sufficient!\n"); + return continue_instruction{}; + } + } + + // There are viable alternatives when arriving + // at the hub (num_possible_turns > 1) + if (stay_on_same_name && + outgoing_edges_are_slower_by_factor(w, hub, 2.0)) { + trace( + "┃ ║ number of possible turns > 1, continuing on same " + "street, alternatives are less important\n"); + return none_instruction{}; + } + + trace("┃ ║ turn requires instruction\n"); + return potential_turn_type; +} + +routing_instruction handle_slight_turn(ways const& w, + segment_context const& seg_context, + segment_context const& next_seg_context) { + const auto& hub = seg_context.traversed_node_hub_.value(); + trace("┃ ║ handling slight turn (exit_angle={})\n", + hub.get_exit().angle_with_arrive_); + + auto& segment = *seg_context.src_ptr_; + const auto& next_segment = *next_seg_context.src_ptr_; + + const auto next_seg_rel_dir = + get_relative_direction(hub.get_exit().angle_with_arrive_); + const routing_instruction potential_turn_type = + turn_action_from(next_seg_rel_dir); + + trace("┃ ║ trying to check if a turn instruction is not needed\n"); + trace("┃ ║ checking alternatives for a possible more natural continuation\n"); + + constexpr auto continuation_abs_cutoff_angle = 50.; + auto const other_continue = + get_other_continue(hub, continuation_abs_cutoff_angle); + auto const leaving_current_street = + !ways_have_same_name(segment.way_, next_segment.way_, w); + + if (other_continue.has_value()) { + trace( + "┃ ║ found the best alternative continuation in way = {} with degree " + "= {}\n", + other_continue->segment_.way_idx_, other_continue->angle_with_arrive_); + + auto const delta = hub.get_exit().angle_with_arrive_; + auto const other_delta = other_continue->angle_with_arrive_; + + if (std::abs(delta) < std::abs(other_delta) && !leaving_current_street) { + trace( + "┃ ║ the exit is a more natural continuation -> no instruction " + "required!\n"); + return none_instruction{}; + } + + if (ways_have_same_name(other_continue->segment_.way_idx_, segment.way_, + w)) { + trace("┃ ║ found a more natural continuation\n"); + trace("┃ ║ an instruction is needed\n"); + + const bool left_of_other_continuation = other_delta < delta; + trace("┃ ║ exit is to the {} of other continuation\n", + left_of_other_continuation ? "left" : "right"); + trace("┃ ║ we check if there is another alternative further {}\n", + left_of_other_continuation ? "left" : "right"); + + const auto next_alternative = + get_next_alternative(hub, left_of_other_continuation); + + bool relevant_alternative_found = + next_alternative.has_value() && + !next_alternative->is_opposite_of_arrive_; + if (relevant_alternative_found) { + + const auto next_segment_instruction_props = + w.way_instruction_properties_[next_segment.way_]; + + const auto alternative_instruction_props = + w.way_instruction_properties_[next_alternative->segment_.way_idx_]; + + if (is_more_important_by(next_segment_instruction_props.get_highway(), + alternative_instruction_props.get_highway(), + 2)) { + relevant_alternative_found = false; + } + } + + if (!relevant_alternative_found && + (!hub.does_way_names_change(w) || hub.is_end_of_turn_lane(w, left_of_other_continuation, false))) { + trace( + "┃ ║ no other relevant possible turn on the {} and same street -> reside with " + "stay {} " + "instruction\n", + left_of_other_continuation ? "left" : "right", + left_of_other_continuation ? "left" : "right"); + return left_of_other_continuation ? routing_instruction{stay_instruction{relative_direction::kLeft}} + : routing_instruction{stay_instruction{relative_direction::kRight}}; + } + trace("┃ ║ there is another relevant alternative further {} or street change\n", + left_of_other_continuation ? "left" : "right"); + + } + } else { + trace("┃ ║ found no continuation that is more natural than the exit\n"); + trace("┃ ║ check next if this is a merge on through street\n"); + if (is_merge_on_through_street(w, hub)) { + trace("┃ ║ is merge on through street\n"); + if (hub.is_end_of_turn_lane(w, true, true)) { + trace("┃ ║ is merge on through street from dedicated left turn lane -> needs turn annotation\n"); + return turn_instruction{relative_direction::kLeft}; + } else if (hub.is_end_of_turn_lane(w, false, true)) { + trace("┃ ║ is merge on through street from dedicated right turn lane -> needs turn annotation\n"); + return turn_instruction{relative_direction::kRight}; + } + } + + return none_instruction{}; + } + + trace("┃ ║ turn requires turn instruction\n"); + return potential_turn_type; +} + +void intersection_module::process(ways const& w, + path&, + segment_contexts_window const& window) { + trace("┃ ╔ INTERSECTION MODULE\n"); + const auto& segment_context = *window.focus(); + const auto& next_segment_context = *window[1]; + + auto& segment = *segment_context.src_ptr_; + const auto& next_segment = *next_segment_context.src_ptr_; + + trace("┃ ║ -> looking at the next two segments\n"); + trace("┃ ║ Analysing <{}> -- way: {} -> <{}> -- way: {} -> <{}>\n", segment.from_, + segment.way_, segment.to_, next_segment.way_, next_segment.to_); + + const auto& common_node_hub = segment_context.traversed_node_hub_.value(); + + if (is_simple_u_turn(common_node_hub)) { + trace("┃ ║ Detected simple u-turn\n"); + trace("┃ ╚ annotation = kTurnAround\n"); + set_segment_instruction(segment, turn_instruction{relative_direction::kTurnAround}); + return; + } + + const relative_direction next_seg_rel_dir = get_relative_direction(common_node_hub.get_exit().angle_with_arrive_); + if (is_real_turn(next_seg_rel_dir)) { + const auto instruction = handle_real_turn(w, segment_context, next_segment_context); + trace("┃ ╚ annotation = {}\n", + to_string(instruction)); + set_segment_instruction(segment, instruction); + } else if (is_slightish(next_seg_rel_dir)) { + const auto instruction = handle_slight_turn(w, segment_context, next_segment_context); + trace("┃ ╚ annotation = {}\n", + to_string(instruction)); + set_segment_instruction(segment, instruction); + } +} + +void intersection_module::post_process(ways const& w, + path&, + segment_contexts const& segment_contexts) { + constexpr distance_t max_u_turn_bridge = 15U; + + for (auto ctx_it = segment_contexts.begin(); ctx_it != segment_contexts.end(); ++ctx_it) { + if (std::next(ctx_it) == segment_contexts.end()) { + break; + } + + const auto& seg_ctx = *ctx_it; + const auto& next_seg_ctx = *std::next(ctx_it); + if (!seg_ctx.traversed_node_hub_.has_value() || + !next_seg_ctx.traversed_node_hub_.has_value()) { + continue; + } + + auto& seg = *seg_ctx.src_ptr_; + auto& next_seg = *next_seg_ctx.src_ptr_; + if (std::holds_alternative(seg.instruction_annotation_) && + std::holds_alternative(next_seg.instruction_annotation_) && + next_seg.dist_ <= max_u_turn_bridge) { + const auto& first_hub = seg_ctx.traversed_node_hub_.value(); + const auto& second_hub = next_seg_ctx.traversed_node_hub_.value(); + + const auto combined_degree = + std::abs(first_hub.get_exit().angle_with_arrive_ + + second_hub.get_exit().angle_with_arrive_); + + if (combined_degree < 170) { + continue; + } + + if (!ways_have_same_name(first_hub.arrive_hub_on_.way_idx_, + second_hub.get_exit().segment_.way_idx_, w)) { + continue; + } + + seg.instruction_annotation_ = turn_instruction{relative_direction::kTurnAround}; + next_seg.instruction_annotation_ = none_instruction{}; + } + } +} + +} // namespace osr diff --git a/src/routing/instructions/modules/motorway_module.cc b/src/routing/instructions/modules/motorway_module.cc new file mode 100644 index 00000000..3672f409 --- /dev/null +++ b/src/routing/instructions/modules/motorway_module.cc @@ -0,0 +1,237 @@ +#include "osr/routing/instructions/modules/motorway_module.h" + +#include "osr/debug.h" +#include "osr/routing/instructions/instruction_util.h" +#include "osr/routing/instructions/routing_instruction.h" + +namespace osr { + +bool handle_from_motorway(ways const& w, + traversed_node_hub const& hub, + path::segment& seg) { + const auto num_visible_turns = hub.number_of_visible_turns(true); + + const auto get_continue_index = [&](traversed_node_hub const& h) { + for (auto i = 1U; i < h.alternatives_.size(); ++i) { + const auto& alt = h.alternatives_[i]; + + if (!alt.can_exit_hub()) { + continue; + } + + const auto arrive_way = h.arrive_hub_on_.way_idx_; + const auto alt_way = alt.segment_.way_idx_; + + const bool is_same_name = ways_have_same_name(arrive_way, alt_way, w); + const bool is_same_highway = + ways_have_same_highway(arrive_way, alt_way, w); + + if (is_same_name && is_same_highway && !is_link(w, alt_way)) { + return static_cast(i); + } + } + + return -1; + }; + + const auto get_most_likely_continue_index = [&](traversed_node_hub const& h) { + double best = 180.0; + auto best_index = -1; + + for (auto i = 1U; i < h.alternatives_.size(); ++i) { + const auto& alt = h.alternatives_[i]; + const auto alt_way = alt.segment_.way_idx_; + + if (!alt.can_exit_hub() || is_link(w, alt_way) || + !is_motorway_or_trunk(w, alt_way)) { + continue; + } + + if (alt.angle_with_arrive_ < best) { + best = alt.angle_with_arrive_; + best_index = static_cast(i); + } + } + + return best_index; + }; + + const auto get_best_continue_index = [&](traversed_node_hub const& h) { + if (const auto continue_index = get_continue_index(h); + continue_index >= 0) { + return continue_index; + } + + return get_most_likely_continue_index(h); + }; + + const auto best_continue_index = get_best_continue_index(hub); + + if (best_continue_index < 0) { + const auto& alts = hub.alternatives_; + + if (num_visible_turns == 2U) { + // Ramp at the end of the highway + seg.instruction_annotation_ = none_instruction{}; + return true; + } else if (num_visible_turns == 3U) { + if (alts[1].can_exit_hub() && alts[2].can_exit_hub()) { + // splitting Ramp at the end of highway + const auto fork_instructions = get_instruction_for_fork(w, hub, 1, 2); + seg.instruction_annotation_ = hub.exit_from_hub_idx_ == 1 + ? fork_instructions.first + : fork_instructions.second; + } else { + seg.instruction_annotation_ = none_instruction{}; + } + return true; + } else if (num_visible_turns == 4U) { + // triple fork at the end of highway + const auto fork_instruction = get_instruction_for_fork(w, hub, 1, 2, 3); + seg.instruction_annotation_ = hub.exit_from_hub_idx_ == 1 + ? std::get<0>(fork_instruction) + : hub.exit_from_hub_idx_ == 2 ? + std::get<1>(fork_instruction) : + std::get<2>(fork_instruction); + return true; + } + + return false; + } + + const auto& exit = hub.get_exit(); + if (is_link(w, exit.segment_.way_idx_)) { + if (hub.exit_from_hub_idx_ > static_cast(best_continue_index)) { + seg.instruction_annotation_ = ramp_off_instruction{relative_direction::kRight}; + } else { // hub.exit_from_hub_idx_ < best_continue_index + seg.instruction_annotation_ = ramp_off_instruction{relative_direction::kLeft}; + } + return true; + } + + const auto n_exiting_motorways = // >= 1 because continuation exists + std::ranges::count_if(hub.alternatives_, [&](const auto& alt) { + const auto way = alt.segment_.way_idx_; + return alt.can_exit_hub() && is_motorway_or_trunk(w, way) && + !is_link(w, way); + }); + + if (n_exiting_motorways > 1 && + is_motorway_or_trunk(w, exit.segment_.way_idx_)) { + + std::vector exiting_motorways; + + for (size_t i = 1U; i < hub.alternatives_.size(); ++i) { + const auto& candidate = hub.alternatives_[i]; + const auto candidate_way = candidate.segment_.way_idx_; + if (candidate.can_exit_hub() && + is_motorway_or_trunk(w, candidate_way) && + !is_link(w, candidate_way)) { + exiting_motorways.push_back(i); + } + } + + // exit must be in exiting_motorways + if (exiting_motorways.size() == 2U) { + const auto fork_instructions = get_instruction_for_fork( + w, hub, exiting_motorways[0], exiting_motorways[1]); + + if (exiting_motorways[0] == hub.exit_from_hub_idx_) { + seg.instruction_annotation_ = fork_instructions.first; + } else if (exiting_motorways[1] == hub.exit_from_hub_idx_) { + seg.instruction_annotation_ = fork_instructions.second; + } + } else if (exiting_motorways.size() == 3U) { + const auto [il, ic, ir] = + get_instruction_for_fork(w, hub, exiting_motorways[0], + exiting_motorways[1], exiting_motorways[2]); + if (exiting_motorways[0] == hub.exit_from_hub_idx_) { + seg.instruction_annotation_ = il; + } else if (exiting_motorways[1] == hub.exit_from_hub_idx_) { + seg.instruction_annotation_ = ic; + } else if (exiting_motorways[2] == hub.exit_from_hub_idx_) { + seg.instruction_annotation_ = ir; + } + } + + return true; + } + + return false; +} + +bool handle_from_ramp(ways const& w, + traversed_node_hub const& h, + path::segment& seg) { + const auto num_possible_turns = h.number_of_possible_turns(true); + const auto num_visible_turns = h.number_of_visible_turns(true); + + // ramp straight into a motorway/ramp + if (num_visible_turns == 2U && num_possible_turns == 1U) { + seg.instruction_annotation_ = none_instruction{}; + return true; + } else if (num_visible_turns == 3U) { + + if (num_possible_turns == 1U && !is_onto_ramp(w, h)) { + // merging onto a passing highway + if (h.exit_from_hub_idx_ == 2U) { + // exit is second clockwise -> Merge left + seg.instruction_annotation_ = ramp_on_instruction{relative_direction::kLeft}; + } else { + // merge right + seg.instruction_annotation_ = ramp_on_instruction{relative_direction::kRight}; + } + return true; + } else if (num_possible_turns == 2U) { + // num_possible_turns == 2 + // -> Ramp fork + if (h.exit_from_hub_idx_ == 1U) { + seg.instruction_annotation_ = stay_instruction{relative_direction::kLeft}; + } else { + // h.exit_from_hub_idx_ == 2U + seg.instruction_annotation_ = stay_instruction{relative_direction::kRight}; + } + return true; + } + } + + return false; +} + +void motorway_module::process(ways const& w, + path& p, + segment_contexts_window const& window) { + trace("┃ ╔ MOTORWAY MODULE\n"); + if (is_last_segment(window)) { + trace("┃ ║ is last segment, passing to next module\n"); + instruction_module::process(w, p, window); + return; + } + + const auto& segment_context = *window.focus(); + if (!segment_context.traversed_node_hub_.has_value()) { + trace("┃ ║ no hub, cannot annotate, passing to next module\n"); + instruction_module::process(w, p, window); + return; + } + + const auto& hub = segment_context.traversed_node_hub_.value(); + bool annotated = false; + if (is_from_motorway(w, hub) && + (is_onto_motorway(w, hub) || is_onto_ramp(w, hub))) { + annotated = handle_from_motorway(w, hub, *segment_context.src_ptr_); + } + if (is_from_ramp(w, hub) && + (is_onto_motorway(w, hub) || is_onto_ramp(w, hub))) { + annotated = handle_from_ramp(w, hub, *segment_context.src_ptr_); + } + + if (annotated) { + return; + } + + trace("┃ ╚ not responsible, passing to next module\n"); + instruction_module::process(w, p, window); +} + +} // namespace osr diff --git a/src/routing/instructions/modules/pedestrian_module.cc b/src/routing/instructions/modules/pedestrian_module.cc new file mode 100644 index 00000000..b6a27ca2 --- /dev/null +++ b/src/routing/instructions/modules/pedestrian_module.cc @@ -0,0 +1,188 @@ +#include "osr/routing/instructions/modules/pedestrian_module.h" + +#include "osr/debug.h" +#include "osr/routing/instructions/instruction_util.h" + +namespace osr { + +void pedestrian_module::process(ways const& w, + path& p, + segment_contexts_window const& window) { + trace("┃ ╔ PEDESTRIAN MODULE\n"); + if (is_last_segment(window)) { + trace("┃ ║ is last segment, passing to next module\n"); + instruction_module::process(w, p, window); + return; + } + + const auto& segment_context = *window.focus(); + const auto& next_segment_context = *window[1]; + const auto& next_segment = *next_segment_context.src_ptr_; + + if (!is_pedestrian(next_segment.mode_)) { + trace("┃ ║ next segment is not foot or wheelchair, passing to next module\n"); + instruction_module::process(w, p, window); + return; + } + + const auto next_seg_way = get_way_from(next_segment_context); + if (next_seg_way == way_idx_t::invalid()) { + trace("┃ ║ way of next segment is invalid, passing to next module\n"); + instruction_module::process(w, p, window); + return; + } + + const auto& next_seg_instruction_props = + w.way_instruction_properties_[next_seg_way]; + const auto& opt_hub = segment_context.traversed_node_hub_; + + if (next_seg_instruction_props.is_stairs()) { + trace("┃ ║ next segment are stairs\n"); + if (opt_hub.has_value()) { + const auto& hub = opt_hub.value(); + const auto& exit = hub.get_exit(); + + bool const is_in_way_direction = exit.segment_.is_way_aligned(); + bool const is_stairs_up = is_in_way_direction ^ next_seg_instruction_props.is_stairs_down(); + + segment_context.src_ptr_->instruction_annotation_ = + stairs_instruction{ + .direction_ = is_stairs_up ? vertical_direction::kUp : vertical_direction::kDown, + .relative_direction_ = get_relative_direction(exit.angle_with_arrive_), + }; + trace("┃ ╚ annotation = {}\n", to_string(segment_context.src_ptr_->instruction_annotation_)); + return; + } + } + + if (opt_hub.has_value()) { + trace("┃ ║ check if hub is crossing street\n"); + trace("┃ ║ trying to find crossed street\n"); + const auto opt_crossed_street = get_crossing_street(w, opt_hub.value()); + if (opt_crossed_street.has_value()) { + trace("┃ ║ crossed street found, [{}]\n", + w.strings_[opt_crossed_street.value()].view()); + segment_context.src_ptr_->instruction_annotation_ = crossing_instruction{ + .street_ = opt_crossed_street.value()}; + return; + } else { + trace("┃ ║ could not find crossed street\n"); + } + } + + + trace("┃ ║ not responsible, passing to next module\n"); + instruction_module::process(w, p, window); +} + +void pedestrian_module::post_process(ways const& w, + path& p, + segment_contexts const&) { + trace("┃ ╔ PEDESTRIAN MODULE POST PROCESSING\n"); + + constexpr distance_t composite_crossing_cutoff = 25U; + for (auto first_segment_it = p.segments_.begin(); + first_segment_it != p.segments_.end(); ++first_segment_it) { + + if (!std::holds_alternative( + first_segment_it->instruction_annotation_)) { + continue; + } + const auto first_crossing = std::get( + first_segment_it->instruction_annotation_); + const auto first_street = first_crossing.street_; + if (first_street == string_idx_t::invalid()) { + trace("┃ ║ found crossing but street name invalid, continue\n"); + continue; + } + trace("┃ ║ found crossing of {}\n", + w.strings_[first_crossing.street_].view()); + + trace("┃ ║ searching for second crossing\n"); + distance_t dist_to_first = 0U; + for (auto second_segment_it = std::next(first_segment_it); + second_segment_it != p.segments_.end(); ++second_segment_it) { + dist_to_first += second_segment_it->dist_; + if (dist_to_first > composite_crossing_cutoff) { + trace("┃ ║ too far from first, stopping search\n"); + break; + } + + if (!std::holds_alternative( + second_segment_it->instruction_annotation_)) { + continue; + } + const auto second_crossing = std::get( + second_segment_it->instruction_annotation_); + const auto second_street = second_crossing.street_; + if (second_street == string_idx_t::invalid()) { + trace( + "┃ ║ found another crossing but street name invalid, stopping " + "search\n"); + break; + } else if (second_street != first_street) { + trace( + "┃ ║ found another crossing but street name different, stopping " + "search\n"); + break; + } + + trace("┃ ║ found another crossing of {}\n", + w.strings_[second_street].view()); + trace("┃ ║ removing annotation, then continue search\n"); + second_segment_it->instruction_annotation_ = none_instruction{}; + } + } + + constexpr distance_t composite_stairs_cutoff = 10U; + for (auto first_segment_it = p.segments_.begin(); + first_segment_it != p.segments_.end(); ++first_segment_it) { + if (!std::holds_alternative( + first_segment_it->instruction_annotation_)) { + continue; + } + + const auto first_stairs = + std::get(first_segment_it->instruction_annotation_); + + distance_t dist_to_first = 0U; + for (auto second_segment_it = std::next(first_segment_it); + second_segment_it != p.segments_.end(); ++second_segment_it) { + dist_to_first += second_segment_it->dist_; + if (dist_to_first > composite_stairs_cutoff) { + trace("┃ ║ too far from first stairs, stopping search\n"); + break; + } + + if (std::holds_alternative( + second_segment_it->instruction_annotation_)) { + continue; + } + + if (!std::holds_alternative( + second_segment_it->instruction_annotation_)) { + break; + } + + const auto second_stairs = std::get( + second_segment_it->instruction_annotation_); + if (second_stairs.direction_ != first_stairs.direction_) { + trace( + "┃ ║ stairs have different vertical directions -> stopping search\n"); + break; + } + + if (second_stairs.relative_direction_ != relative_direction::kStraight) { + trace( + "┃ ║ second stairs has direction modifier -> stopping search\n"); + break; + } + + trace("┃ ║ found another straight stairs\n"); + trace("┃ ║ removing annotation, then continue search\n"); + second_segment_it->instruction_annotation_ = none_instruction{}; + } + } +} + +} // namespace osr diff --git a/src/routing/instructions/modules/roundabout_module.cc b/src/routing/instructions/modules/roundabout_module.cc new file mode 100644 index 00000000..190007ce --- /dev/null +++ b/src/routing/instructions/modules/roundabout_module.cc @@ -0,0 +1,98 @@ +#include "osr/routing/instructions/modules/roundabout_module.h" + +#include "osr/debug.h" +#include "osr/routing/instructions/instruction_util.h" + +namespace osr { + +void roundabout_module::process(ways const& w, + path& p, + segment_contexts_window const& window) { + trace("┃ ╔ ROUNDABOUT MODULE\n"); + if (is_last_segment(window)) { + trace("┃ ║ is last segment, passing to next module\n"); + instruction_module::process(w, p, window); + return; + } + auto& segment = *window.focus()->src_ptr_; + auto const& next_segment = *window[1]->src_ptr_; + + auto const current_is_roundabout = is_roundabout_way(w, segment.way_); + auto const next_is_roundabout = is_roundabout_way(w, next_segment.way_); + + if (!current_is_roundabout && next_is_roundabout) { + trace("┃ ╚ entering roundabout -> annotation=kEnterRoundabout\n"); + segment.instruction_annotation_ = enter_roundabout_instruction{}; + return; + } + + if (current_is_roundabout && !next_is_roundabout) { + trace("┃ ╚ exiting roundabout -> annotation=kExitRoundabout\n"); + segment.instruction_annotation_ = exit_roundabout_instruction{}; + return; + } + + if (current_is_roundabout && next_is_roundabout) { + segment.instruction_annotation_ = none_instruction{}; + return; + } + + trace("┃ ╚ not responsible, passing to next module\n"); + instruction_module::process(w, p, window); +} + +void roundabout_module::post_process(ways const& w, + path&, + segment_contexts const& contexts) { + + bool in_roundabout = false; + bool is_clockwise = false; + std::uint16_t n_exits_encountered = 0U; + for (const auto& context : contexts) { + auto& current_segment = *context.src_ptr_; + const auto& hub_opt = context.traversed_node_hub_; + if (!hub_opt.has_value()) { + continue; + } + + if (std::holds_alternative( + current_segment.instruction_annotation_)) { + in_roundabout = true; + + const auto& hub = hub_opt.value(); + const auto& exit = hub.get_exit(); + is_clockwise = exit.angle_with_arrive_ > 0; + n_exits_encountered = 0U; + continue; + } + + if (std::holds_alternative( + current_segment.instruction_annotation_)) { + if (!in_roundabout) { + continue; + } + in_roundabout = false; + std::uint16_t const exit = n_exits_encountered + 1U; + current_segment.instruction_annotation_ = + exit_roundabout_instruction{exit}; + continue; + } + + if (in_roundabout && hub_opt.has_value()) { + const auto& hub = hub_opt.value(); + bool has_non_roundabout_exit = false; + for (auto const& alt : hub.get_exit_neighbors(is_clockwise)) { + if (alt.can_exit_hub() && + !is_roundabout_way(w, alt.segment_.way_idx_)) { + has_non_roundabout_exit = true; + break; + } + } + if (has_non_roundabout_exit) { + ++n_exits_encountered; + } + } + } +} + +} // namespace osr diff --git a/src/routing/instructions/routing_instruction.cc b/src/routing/instructions/routing_instruction.cc new file mode 100644 index 00000000..98b0ef4f --- /dev/null +++ b/src/routing/instructions/routing_instruction.cc @@ -0,0 +1,245 @@ +#include "osr/routing/instructions/routing_instruction.h" + +#include +#include "utl/overloaded.h" +#include "osr/ways.h" + +namespace osr { + +std::string direction_to_string(relative_direction const dir) { + switch (dir) { + case relative_direction::kTurnAround: return "turn_around"; + case relative_direction::kSharpRight: return "sharp_right"; + case relative_direction::kRight: return "right"; + case relative_direction::kSlightRight: return "slight_right"; + case relative_direction::kSharpLeft: return "sharp_left"; + case relative_direction::kLeft: return "left"; + case relative_direction::kSlightLeft: return "slight_left"; + case relative_direction::kStraight: return "straight"; + case relative_direction::kInvalid: return "invalid"; + } + return "invalid"; +} + +relative_direction direction_from_string(std::string_view const s) { + if (s == "turn_around") return relative_direction::kTurnAround; + if (s == "sharp_right") return relative_direction::kSharpRight; + if (s == "right") return relative_direction::kRight; + if (s == "slight_right") return relative_direction::kSlightRight; + if (s == "sharp_left") return relative_direction::kSharpLeft; + if (s == "left") return relative_direction::kLeft; + if (s == "slight_left") return relative_direction::kSlightLeft; + if (s == "straight") return relative_direction::kStraight; + return relative_direction::kInvalid; +} + +std::string vertical_direction_to_string(vertical_direction const dir) { + switch (dir) { + case vertical_direction::kUp: return "up"; + case vertical_direction::kDown: return "down"; + } + return "up"; +} + +vertical_direction vertical_direction_from_string(std::string_view const s) { + if (s == "down") return vertical_direction::kDown; + return vertical_direction::kUp; +} + +boost::json::value to_json(routing_instruction const& instruction, ways const& w) { + return std::visit( + utl::overloaded{ + [](none_instruction const&) -> boost::json::value { + return {{"type", "none"}}; + }, + [](destination_instruction const& inst) -> boost::json::value { + return {{"type", "destination"}, {"direction", direction_to_string(inst.direction_)}}; + }, + [](becomes_instruction const&) -> boost::json::value { + return {{"type", "becomes"}}; + }, + [](continue_instruction const&) -> boost::json::value { + return {{"type", "continue"}}; + }, + [](turn_instruction const& inst) -> boost::json::value { + return {{"type", "turn"}, {"direction", direction_to_string(inst.direction_)}}; + }, + [](ramp_on_instruction const& inst) -> boost::json::value { + return {{"type", "ramp_on"}, {"direction", direction_to_string(inst.direction_)}}; + }, + [](ramp_off_instruction const& inst) -> boost::json::value { + return {{"type", "ramp_off"}, {"direction", direction_to_string(inst.direction_)}}; + }, + [](stay_instruction const& inst) -> boost::json::value { + return {{"type", "stay"}, {"direction", direction_to_string(inst.direction_)}}; + }, + [](enter_roundabout_instruction const&) -> boost::json::value { + return {{"type", "enter_roundabout"}}; + }, + [](exit_roundabout_instruction const& inst) -> boost::json::value { + return {{"type", "exit_roundabout"}, {"exit", inst.exit_number_}}; + }, + [](elevator_instruction const& inst) -> boost::json::value { + return {{"type", "elevator"}, + {"direction", vertical_direction_to_string(inst.direction_)}, + {"relative_direction", direction_to_string(inst.relative_direction_)}}; + }, + [](stairs_instruction const& inst) -> boost::json::value { + return {{"type", "stairs"}, + {"direction", vertical_direction_to_string(inst.direction_)}, + {"relative_direction", direction_to_string(inst.relative_direction_)}}; + }, + [&w](crossing_instruction const& inst) -> boost::json::value { + auto const street_name = inst.street_ == string_idx_t::invalid() + ? "" + : w.strings_[inst.street_].view(); + return {{"type", "crossing"}, + {"street", street_name}}; + }}, + instruction); +} + +routing_instruction from_json(boost::json::value const& json, ways const& w) { + if (!json.is_object()) { + return none_instruction{}; + } + auto const& obj = json.as_object(); + if (!obj.contains("type")) { + return none_instruction{}; + } + auto const type_val = obj.at("type").as_string(); + if (type_val == "none") { + return none_instruction{}; + } else if (type_val == "destination") { + relative_direction direction = relative_direction::kInvalid; + if (obj.contains("direction")) { + direction = direction_from_string(obj.at("direction").as_string()); + } + return destination_instruction{direction}; + } else if (type_val == "becomes") { + return becomes_instruction{}; + } else if (type_val == "continue") { + return continue_instruction{}; + } else if (type_val == "turn") { + relative_direction direction = relative_direction::kInvalid; + if (obj.contains("direction")) { + direction = direction_from_string(obj.at("direction").as_string()); + } + return turn_instruction{direction}; + } else if (type_val == "ramp_on") { + relative_direction direction = relative_direction::kInvalid; + if (obj.contains("direction")) { + direction = direction_from_string(obj.at("direction").as_string()); + } + return ramp_on_instruction{direction}; + } else if (type_val == "ramp_off") { + relative_direction direction = relative_direction::kInvalid; + if (obj.contains("direction")) { + direction = direction_from_string(obj.at("direction").as_string()); + } + return ramp_off_instruction{direction}; + } else if (type_val == "stay") { + relative_direction direction = relative_direction::kInvalid; + if (obj.contains("direction")) { + direction = direction_from_string(obj.at("direction").as_string()); + } + return stay_instruction{direction}; + } else if (type_val == "enter_roundabout") { + return enter_roundabout_instruction{}; + } else if (type_val == "exit_roundabout") { + std::uint16_t exit = 0; + if (obj.contains("exit")) { + exit = static_cast(obj.at("exit").as_int64()); + } + return exit_roundabout_instruction{exit}; + } else if (type_val == "elevator") { + vertical_direction direction = vertical_direction::kUp; + relative_direction rel_dir = relative_direction::kInvalid; + if (obj.contains("direction")) { + direction = vertical_direction_from_string(obj.at("direction").as_string()); + } + if (obj.contains("relative_direction")) { + rel_dir = direction_from_string(obj.at("relative_direction").as_string()); + } + return elevator_instruction{direction, rel_dir}; + } else if (type_val == "stairs") { + vertical_direction direction = vertical_direction::kUp; + relative_direction rel_dir = relative_direction::kInvalid; + if (obj.contains("direction")) { + direction = vertical_direction_from_string(obj.at("direction").as_string()); + } + if (obj.contains("relative_direction")) { + rel_dir = direction_from_string(obj.at("relative_direction").as_string()); + } + return stairs_instruction{direction, rel_dir}; + } else if (type_val == "crossing") { + string_idx_t street = string_idx_t::invalid(); + if (obj.contains("street")) { + auto const name = obj.at("street").as_string(); + const auto opt_idx = w.find_string(name.c_str()); + if (opt_idx.has_value()) { + street = opt_idx.value(); + } + } + return crossing_instruction{street}; + } + return none_instruction{}; +} + +std::string to_string(routing_instruction const& instruction) { + return std::visit( + utl::overloaded{ + [](none_instruction const&) -> std::string { return "kNone"; }, + [](destination_instruction const& inst) -> std::string { + if (inst.direction_ == relative_direction::kLeft) return "kDestinationLeft"; + if (inst.direction_ == relative_direction::kRight) return "kDestinationRight"; + return "kDestination"; + }, + [](becomes_instruction const&) -> std::string { return "kBecomes"; }, + [](continue_instruction const&) -> std::string { return "kContinue"; }, + [](turn_instruction const& inst) -> std::string { + switch (inst.direction_) { + case relative_direction::kSlightRight: return "kTurnSlightRight"; + case relative_direction::kRight: return "kTurnRight"; + case relative_direction::kSharpRight: return "kTurnSharpRight"; + case relative_direction::kTurnAround: return "kTurnAround"; + case relative_direction::kSharpLeft: return "kTurnSharpLeft"; + case relative_direction::kLeft: return "kTurnLeft"; + case relative_direction::kSlightLeft: return "kTurnSlightLeft"; + default: return "kContinue"; + } + }, + [](ramp_on_instruction const& inst) -> std::string { + switch (inst.direction_) { + case relative_direction::kLeft: return "kRampLeft"; + case relative_direction::kRight: return "kRampRight"; + default: return "kRampStraight"; + } + }, + [](ramp_off_instruction const& inst) -> std::string { + switch (inst.direction_) { + case relative_direction::kLeft: return "kExitLeft"; + case relative_direction::kRight: return "kExitRight"; + default: return "kExitRight"; + } + }, + [](stay_instruction const& inst) -> std::string { + switch (inst.direction_) { + case relative_direction::kStraight: return "kStayStraight"; + case relative_direction::kSlightLeft: return "kStaySlightLeft"; + case relative_direction::kLeft: return "kStayLeft"; + case relative_direction::kSlightRight: return "kStaySlightRight"; + case relative_direction::kRight: return "kStayRight"; + default: return "kStayStraight"; + } + }, + [](enter_roundabout_instruction const&) -> std::string { return "kEnterRoundabout"; }, + [](exit_roundabout_instruction const&) -> std::string { return "kExitRoundabout"; }, + [](elevator_instruction const&) -> std::string { return "kElevator"; }, + [](stairs_instruction const&) -> std::string { return "kStairs"; }, + [](crossing_instruction const&) -> std::string { return "kCrossing"; }}, + instruction); +} + +} + diff --git a/src/ways.cc b/src/ways.cc index 2898d2f3..b51fcb9d 100644 --- a/src/ways.cc +++ b/src/ways.cc @@ -63,6 +63,7 @@ ways::ways(std::filesystem::path p, cista::mmap::protection const mode) strings_{mm_vec(mm("strings_data.bin")), mm_vec(mm("strings_idx.bin"))}, way_names_{mm("way_names.bin")}, + way_instruction_properties_{mm("way_instruction_properties.bin")}, way_has_conditional_access_no_{ mm_vec(mm("way_has_conditional_access_no"))}, way_conditional_access_no_{mm("way_conditional_access_no")} {} @@ -332,6 +333,7 @@ void ways::sync() { strings_.data_.mmap_.sync(); strings_.bucket_starts_.mmap_.sync(); way_names_.mmap_.sync(); + way_instruction_properties_.mmap_.sync(); } std::optional ways::get_access_restriction( diff --git a/test/biarritz-roundabouts.osm.pbf b/test/biarritz-roundabouts.osm.pbf new file mode 100644 index 00000000..c735ed02 Binary files /dev/null and b/test/biarritz-roundabouts.osm.pbf differ diff --git "a/test/darmstadt-schlo\303\237-pbf.osm.pbf" "b/test/darmstadt-schlo\303\237-pbf.osm.pbf" new file mode 100644 index 00000000..bb8ea635 Binary files /dev/null and "b/test/darmstadt-schlo\303\237-pbf.osm.pbf" differ diff --git a/test/extract_test.cc b/test/extract_test.cc index 966fe444..2541232a 100644 --- a/test/extract_test.cc +++ b/test/extract_test.cc @@ -32,6 +32,63 @@ TEST(extract, string_cache) { ASSERT_EQ(name_idx1, name_idx2); } + +TEST(extract, lanes) { + auto p = fs::temp_directory_path() / "osr_test"; + auto ec = std::error_code{}; + fs::remove_all(p, ec); + fs::create_directories(p, ec); + + extract(false, "test/karlsruhe-kirchfeld.osm.pbf", p, {}); + + auto w = ways{p, cista::mmap::protection::READ}; + const auto linkenheimer_street = w.find_way(osm_way_idx_t{638750467}); + ASSERT_TRUE(linkenheimer_street.has_value()); + + const auto linkenheimer_way_instruction_props = + w.way_instruction_properties_[linkenheimer_street.value()]; + ASSERT_TRUE(linkenheimer_way_instruction_props.has_lanes()); + ASSERT_EQ(linkenheimer_way_instruction_props.lanes_forward(), 2); + ASSERT_EQ(linkenheimer_way_instruction_props.lanes_backward(), 0); + + const auto laerchen_street = w.find_way(osm_way_idx_t{4242890}); + ASSERT_TRUE(laerchen_street.has_value()); + + const auto laerchen_way_instruction_props = + w.way_instruction_properties_[laerchen_street.value()]; + ASSERT_FALSE(laerchen_way_instruction_props.has_lanes()); + ASSERT_EQ(laerchen_way_instruction_props.lanes_forward(), 0); + ASSERT_EQ(laerchen_way_instruction_props.lanes_backward(), 0); + + const auto forlenweg = w.find_way(osm_way_idx_t{4242887}); + ASSERT_TRUE(forlenweg.has_value()); + + const auto forlwenweg_way_instruction_props = + w.way_instruction_properties_[forlenweg.value()]; + ASSERT_TRUE(forlwenweg_way_instruction_props.has_lanes()); + ASSERT_EQ(forlwenweg_way_instruction_props.lanes_forward(), 1); + ASSERT_EQ(forlwenweg_way_instruction_props.lanes_backward(), 1); + + const auto untere_hardtstrasse = w.find_way(osm_way_idx_t{261412003}); + ASSERT_TRUE(untere_hardtstrasse.has_value()); + + const auto untere_way_instruction_props = + w.way_instruction_properties_[untere_hardtstrasse.value()]; + ASSERT_TRUE(untere_way_instruction_props.has_lanes()); + ASSERT_EQ(untere_way_instruction_props.lanes_forward(), 2); + ASSERT_EQ(untere_way_instruction_props.lanes_backward(), 1); + + const auto neureuter_querallee = w.find_way(osm_way_idx_t{4242628}); + ASSERT_TRUE(neureuter_querallee.has_value()); + + const auto neureuter_way_instruction_props = + w.way_instruction_properties_[neureuter_querallee.value()]; + + ASSERT_TRUE(neureuter_way_instruction_props.has_lanes()); + ASSERT_EQ(neureuter_way_instruction_props.lanes_forward(), 3); + ASSERT_EQ(neureuter_way_instruction_props.lanes_backward(), 1); +} + TEST(extract, bus_only_on_highway) { auto p = fs::temp_directory_path() / "osr_test"; auto ec = std::error_code{}; @@ -48,3 +105,109 @@ TEST(extract, bus_only_on_highway) { ASSERT_FALSE(wp.is_bus_accessible()); ASSERT_TRUE(wp.is_foot_accessible()); } + +TEST(extract, stairs) { + auto p = fs::temp_directory_path() / "osr_test"; + auto ec = std::error_code{}; + fs::remove_all(p, ec); + fs::create_directories(p, ec); + + extract(false, "test/da_hbf_2.osm.pbf", p, {}); + + auto w = ways{p, cista::mmap::protection::READ}; + auto const platform_1_stairs = w.find_way(osm_way_idx_t{540718863}); + ASSERT_TRUE(platform_1_stairs.has_value()); + + const auto platform_1_way_instruction_props = + w.way_instruction_properties_[platform_1_stairs.value()]; + ASSERT_TRUE(platform_1_way_instruction_props.is_stairs()); + ASSERT_TRUE(platform_1_way_instruction_props.is_stairs_up()); + ASSERT_FALSE(platform_1_way_instruction_props.is_stairs_down()); + + auto const hbf_bridge = w.find_way(osm_way_idx_t{38182079}); + ASSERT_TRUE(hbf_bridge.has_value()); + + const auto hbf_bridge_instruction_properties = + w.way_instruction_properties_[hbf_bridge.value()]; + ASSERT_FALSE(hbf_bridge_instruction_properties.is_stairs()); + ASSERT_FALSE(hbf_bridge_instruction_properties.is_stairs_up()); + ASSERT_FALSE(hbf_bridge_instruction_properties.is_stairs_down()); +} + +TEST(extract, turn_lanes) { + auto const raw_data = "test/hamburg.osm.pbf"; + + if (!fs::exists(raw_data)) { + GTEST_SKIP() << raw_data << " not found"; + } + + auto p = fs::temp_directory_path() / "osr_test"; + auto ec = std::error_code{}; + fs::remove_all(p, ec); + fs::create_directories(p, ec); + + extract(false, raw_data, p, {}); + auto w = ways{p, cista::mmap::protection::READ}; + + /* + * Annotations: + * turn:lanes:backward = through + * turn:lanes:both_ways = left;right + * turn:lanes:forward = through + */ + auto const aussschlager_weg = w.find_way(osm_way_idx_t{940905385}); + ASSERT_TRUE(aussschlager_weg.has_value()); + const auto auschlager_weg_instruction_props = + w.way_instruction_properties_[aussschlager_weg.value()]; + ASSERT_TRUE(auschlager_weg_instruction_props.has_left_turn_lane(direction::kForward)); + ASSERT_TRUE(auschlager_weg_instruction_props.has_right_turn_lane(direction::kForward)); + ASSERT_TRUE(auschlager_weg_instruction_props.has_through_lane(direction::kForward)); + ASSERT_TRUE(auschlager_weg_instruction_props.has_left_turn_lane(direction::kBackward)); + ASSERT_TRUE(auschlager_weg_instruction_props.has_right_turn_lane(direction::kBackward)); + ASSERT_TRUE(auschlager_weg_instruction_props.has_through_lane(direction::kBackward)); + + /* + * Annotations: + * turn:lanes:forward = through|through|right + */ + auto const landwehr = w.find_way(osm_way_idx_t{640885955}); + ASSERT_TRUE(landwehr.has_value()); + const auto landwehr_instruction_props = + w.way_instruction_properties_[landwehr.value()]; + ASSERT_FALSE(landwehr_instruction_props.has_left_turn_lane(direction::kForward)); + ASSERT_TRUE(landwehr_instruction_props.has_right_turn_lane(direction::kForward)); + ASSERT_TRUE(landwehr_instruction_props.has_through_lane(direction::kForward)); + ASSERT_FALSE(landwehr_instruction_props.has_left_turn_lane(direction::kBackward)); + ASSERT_FALSE(landwehr_instruction_props.has_right_turn_lane(direction::kBackward)); + ASSERT_FALSE(landwehr_instruction_props.has_through_lane(direction::kBackward)); + + /* + * Annotations: + * turn:lanes:backward = left|through;right + */ + auto const aussschlager_weg_2 = w.find_way(osm_way_idx_t{177037597}); + ASSERT_TRUE(aussschlager_weg_2.has_value()); + const auto aussschlager_weg_2_instruction_props = + w.way_instruction_properties_[aussschlager_weg_2.value()]; + ASSERT_FALSE(aussschlager_weg_2_instruction_props.has_left_turn_lane(direction::kForward)); + ASSERT_FALSE(aussschlager_weg_2_instruction_props.has_right_turn_lane(direction::kForward)); + ASSERT_FALSE(aussschlager_weg_2_instruction_props.has_through_lane(direction::kForward)); + ASSERT_TRUE(aussschlager_weg_2_instruction_props.has_left_turn_lane(direction::kBackward)); + ASSERT_TRUE(aussschlager_weg_2_instruction_props.has_right_turn_lane(direction::kBackward)); + ASSERT_TRUE(aussschlager_weg_2_instruction_props.has_through_lane(direction::kBackward)); + + /* + * Annotations: + * turn:lanes = left|through|through|right + */ + auto const sievekingsallee = w.find_way(osm_way_idx_t{458542851}); + ASSERT_TRUE(sievekingsallee.has_value()); + const auto sievekingsallee_instruction_props = + w.way_instruction_properties_[sievekingsallee.value()]; + ASSERT_TRUE(sievekingsallee_instruction_props.has_left_turn_lane(direction::kForward)); + ASSERT_TRUE(sievekingsallee_instruction_props.has_right_turn_lane(direction::kForward)); + ASSERT_TRUE(sievekingsallee_instruction_props.has_through_lane(direction::kForward)); + ASSERT_FALSE(sievekingsallee_instruction_props.has_left_turn_lane(direction::kBackward)); + ASSERT_FALSE(sievekingsallee_instruction_props.has_right_turn_lane(direction::kBackward)); + ASSERT_FALSE(sievekingsallee_instruction_props.has_through_lane(direction::kBackward)); +} diff --git a/test/frankfurter-kreuz.osm.pbf b/test/frankfurter-kreuz.osm.pbf new file mode 100644 index 00000000..2b2def8c Binary files /dev/null and b/test/frankfurter-kreuz.osm.pbf differ diff --git a/test/include/osr/routing/instructions/routing_instructions_input.h b/test/include/osr/routing/instructions/routing_instructions_input.h new file mode 100644 index 00000000..ce0f324c --- /dev/null +++ b/test/include/osr/routing/instructions/routing_instructions_input.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "osr/location.h" +#include "osr/routing/algorithms.h" +#include "boost/json.hpp" +#include "osr/routing/profile.h" + +namespace osr::test { + +struct routing_instructions_input { + std::string test_name; + std::string osm_file; + location start; + location end; + search_profile profile; + direction direction; + routing_algorithm routing_algo; + std::vector expected_actions; +}; + +std::vector load_from_json( + std::filesystem::path const& test_cases_path, + std::filesystem::path const& osm_pbf_dir_path); + +} diff --git a/test/include/osr/routing/instructions/routing_instructions_test_fixture.h b/test/include/osr/routing/instructions/routing_instructions_test_fixture.h new file mode 100644 index 00000000..aab9effe --- /dev/null +++ b/test/include/osr/routing/instructions/routing_instructions_test_fixture.h @@ -0,0 +1,11 @@ +#pragma once + +#include "gtest/gtest.h" +#include "osr/routing/instructions/routing_instructions_input.h" + +namespace osr::test { + +class routing_instructions_test_fixture + : public testing::TestWithParam {}; + +} // namespace osr diff --git a/test/include/osr/routing/routing_test_util.h b/test/include/osr/routing/routing_test_util.h new file mode 100644 index 00000000..6dfb6498 --- /dev/null +++ b/test/include/osr/routing/routing_test_util.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "osr/routing/path.h" +#include "osr/location.h" +#include "osr/geojson.h" +#include "osr/lookup.h" +#include "osr/routing/route.h" + +namespace osr::test { + +std::optional route(ways const& w, lookup const& l, location const& from, + location const& to, profile_parameters const& params, + search_profile sp, direction dir, + routing_algorithm algo); + +std::optional extract_and_route(std::string_view path, + location const& from, location const& to, + profile_parameters const& params, + search_profile sp, direction dir, + routing_algorithm algo); + +std::string extract_route_to_feature(std::string_view path, + location const& from, location const& to, + profile_parameters const& params, + search_profile sp, direction dir, + routing_algorithm algo); +} // namespace osr::test \ No newline at end of file diff --git a/test/instruction-test-cases.json b/test/instruction-test-cases.json new file mode 100644 index 00000000..baf7e950 --- /dev/null +++ b/test/instruction-test-cases.json @@ -0,0 +1,3860 @@ +[ + { + "name": "intersection_right_turn", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9883621E1, + "lng": 8.677029E0, + "lvl": 0 + }, + "end": { + "lat": 4.9885453E1, + "lng": 8.67891E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_left_turn", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.988452E1, + "lng": 8.675994E0, + "lvl": 0 + }, + "end": { + "lat": 4.9884308E1, + "lng": 8.674567E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_continue", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9883621E1, + "lng": 8.677029E0, + "lvl": 0 + }, + "end": { + "lat": 4.9884755E1, + "lng": 8.675667E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_composition_1", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9882061E1, + "lng": 8.679368E0, + "lvl": 0 + }, + "end": { + "lat": 4.9883765E1, + "lng": 8.679143E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_composition_2", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9880222E1, + "lng": 8.67336E0, + "lvl": 0 + }, + "end": { + "lat": 4.9886988E1, + "lng": 8.677882E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "turning_at_crossing_islands_1", + "osm_file": "spessartring-fiedlerweg-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9879340086605566E1, + "lng": 8.670534089302151E0, + "lvl": 0 + }, + "end": { + "lat": 4.987437815260495E1, + "lng": 8.669649757714751E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "turning_at_crossing_islands_2", + "osm_file": "spessartring-fiedlerweg-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9879340086605566E1, + "lng": 8.670534089302151E0, + "lvl": 0 + }, + "end": { + "lat": 4.987560632642536E1, + "lng": 8.671821524609669E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "turning_at_crossing_islands_3", + "osm_file": "spessartring-fiedlerweg-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9877715328501154E1, + "lng": 8.67073281287631E0, + "lvl": 0 + }, + "end": { + "lat": 4.987669864589475E1, + "lng": 8.671015668239875E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_with_turn_lanes_1", + "osm_file": "darmstadt-schloß-pbf.osm.pbf", + "start": { + "lat": 4.9873222391166394E1, + "lng": 8.657688853205684E0, + "lvl": 0 + }, + "end": { + "lat": 4.987455729158083E1, + "lng": 8.65503993885622E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "slight_left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_with_turn_lanes_2", + "osm_file": "darmstadt-schloß-pbf.osm.pbf", + "start": { + "lat": 4.9873222391166394E1, + "lng": 8.657688853205684E0, + "lvl": 0 + }, + "end": { + "lat": 4.9874871763009025E1, + "lng": 8.657660442236391E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_with_turn_lanes_3", + "osm_file": "darmstadt-schloß-pbf.osm.pbf", + "start": { + "lat": 4.987239907237583E1, + "lng": 8.657115581934875E0, + "lvl": 0 + }, + "end": { + "lat": 4.9873198674651206E1, + "lng": 8.658650183543898E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_1", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.34646489450472E1, + "lng": -1.500705396949627E0, + "lvl": 0 + }, + "end": { + "lat": 4.346430727226817E1, + "lng": -1.4990421475397682E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "exit_roundabout", + "exit": 1 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_2", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346470404546079E1, + "lng": -1.5005710994199433E0, + "lvl": 0 + }, + "end": { + "lat": 4.346615038341835E1, + "lng": -1.4989085541240286E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 2 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_3", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346585363422E1, + "lng": -1.4990140852794411E0, + "lvl": 0 + }, + "end": { + "lat": 4.3467167907318725E1, + "lng": -1.4974530267344903E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "exit_roundabout", + "exit": 1 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_4", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346585363422E1, + "lng": -1.4990140852794411E0, + "lvl": 0 + }, + "end": { + "lat": 4.346832448276325E1, + "lng": -1.496589681324906E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 2 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_5", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346706617279216E1, + "lng": -1.4985351188039715E0, + "lvl": 0 + }, + "end": { + "lat": 4.346859985122825E1, + "lng": -1.4975925480058834E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 3 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_6", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346706617279216E1, + "lng": -1.4985351188039715E0, + "lvl": 0 + }, + "end": { + "lat": 4.3468176797315294E1, + "lng": -1.4982018514449464E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 4 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_7", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346630474302367E1, + "lng": -1.4962553803228502E0, + "lvl": 0 + }, + "end": { + "lat": 4.3466354944983266E1, + "lng": -1.493485413059943E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 2 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_8", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346630474302367E1, + "lng": -1.4962553803228502E0, + "lvl": 0 + }, + "end": { + "lat": 4.3464433743023335E1, + "lng": -1.4954871908480527E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "exit_roundabout", + "exit": 1 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_9", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346630474302367E1, + "lng": -1.4962553803228502E0, + "lvl": 0 + }, + "end": { + "lat": 4.346700961219898E1, + "lng": -1.4944559790516792E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 3 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "right_hand_roundabout_10", + "osm_file": "biarritz-roundabouts.osm.pbf", + "start": { + "lat": 4.346374948220381E1, + "lng": -1.4956169045163392E0, + "lvl": 0 + }, + "end": { + "lat": 4.346335939417662E1, + "lng": -1.4955744422604766E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 4 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_1", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.2039354635275714E1, + "lng": -7.772930495326875E-1, + "lvl": 0 + }, + "end": { + "lat": 5.2041241498726606E1, + "lng": -7.75970513089419E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 1 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_2", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.2039354635275714E1, + "lng": -7.772930495326875E-1, + "lvl": 0 + }, + "end": { + "lat": 5.2040781024380635E1, + "lng": -7.723610680176307E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 2 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_3", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.2039354635275714E1, + "lng": -7.772930495326875E-1, + "lvl": 0 + }, + "end": { + "lat": 5.203890113249673E1, + "lng": -7.73495153167147E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 3 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_4", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.2039354635275714E1, + "lng": -7.772930495326875E-1, + "lvl": 0 + }, + "end": { + "lat": 5.2039493532197355E1, + "lng": -7.7593470859685E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 4 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_5", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.204037336020693E1, + "lng": -7.740829714938968E-1, + "lvl": 0 + }, + "end": { + "lat": 5.204300520333831E1, + "lng": -7.669083325009467E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 2 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_6", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.204038475959783E1, + "lng": -7.73959673387111E-1, + "lvl": 0 + }, + "end": { + "lat": 5.204254352110402E1, + "lng": -7.69589585974046E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "exit_roundabout", + "exit": 1 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_7", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.203836485381317E1, + "lng": -7.797354417295708E-1, + "lvl": 0 + }, + "end": { + "lat": 5.2036357996390535E1, + "lng": -7.847691437542608E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 2 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_8", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.203836485381317E1, + "lng": -7.797354417295708E-1, + "lvl": 0 + }, + "end": { + "lat": 5.203870177607047E1, + "lng": -7.844374519932558E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 3 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "left_hand_roundabout_9", + "osm_file": "milton-keynes-roundabouts.osm.pbf", + "start": { + "lat": 5.203836485381317E1, + "lng": -7.797354417295708E-1, + "lvl": 0 + }, + "end": { + "lat": 5.203611157903134E1, + "lng": -7.794884245121523E-1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "exit_roundabout", + "exit": 1 + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_through", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.0032259584389806E1, + "lng": 8.594255760472038E0, + "lvl": 0 + }, + "end": { + "lat": 5.0066827716208735E1, + "lng": 8.611192523278106E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_ramp_off_1", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.0032259584389806E1, + "lng": 8.594255760472038E0, + "lvl": 0 + }, + "end": { + "lat": 5.005214683967307E1, + "lng": 8.615370911321406E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "ramp_off", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_ramp_off_2", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.0032259584389806E1, + "lng": 8.594255760472038E0, + "lvl": 0 + }, + "end": { + "lat": 5.0053401543938634E1, + "lng": 8.604829706814456E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "ramp_off", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_ramp_off_and_on", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.0032259584389806E1, + "lng": 8.594255760472038E0, + "lvl": 0 + }, + "end": { + "lat": 5.005324599915372E1, + "lng": 8.573831432075764E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "ramp_off", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "ramp_on", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_ramp_on_1", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.005791941387463E1, + "lng": 8.597273495597165E0, + "lvl": 0 + }, + "end": { + "lat": 5.0040185174932475E1, + "lng": 8.597645010598825E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "ramp_off", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "ramp_on", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_ramp_on_2", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.005791941387463E1, + "lng": 8.597273495597165E0, + "lvl": 0 + }, + "end": { + "lat": 5.006094038408136E1, + "lng": 8.60827006018394E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "ramp_off", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "ramp_on", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_through_2", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.005791941387463E1, + "lng": 8.597273495597165E0, + "lvl": 0 + }, + "end": { + "lat": 5.005650844189552E1, + "lng": 8.612083847258845E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_through_on_off_with_roundabout", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.005982174092185E1, + "lng": 8.607222060350068E0, + "lvl": 0 + }, + "end": { + "lat": 5.005773535792088E1, + "lng": 8.60029967017067E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "enter_roundabout" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "exit_roundabout", + "exit": 2 + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "ramp_on", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_off_on_2", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.005375527478981E1, + "lng": 8.584174924930267E0, + "lvl": 0 + }, + "end": { + "lat": 5.004362118438965E1, + "lng": 8.599316744996656E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "ramp_off", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "ramp_on", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "motorway_u_turn", + "osm_file": "frankfurter-kreuz.osm.pbf", + "start": { + "lat": 5.004744288897024E1, + "lng": 8.601198624659474E0, + "lvl": 0 + }, + "end": { + "lat": 5.003679995654403E1, + "lng": 8.596481031787249E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "ramp_off", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "stairs_left", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.988533175489982E1, + "lng": 8.683276613884857E0, + "lvl": 0 + }, + "end": { + "lat": 4.988439095168064E1, + "lng": 8.683106421215285E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "stairs", + "direction": "up", + "relative_direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "stairs_right", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.988459264152897E1, + "lng": 8.681981351700415E0, + "lvl": 0 + }, + "end": { + "lat": 4.988439095168064E1, + "lng": 8.683106421215285E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stairs", + "direction": "up", + "relative_direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "stairs_down", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9884459199029465E1, + "lng": 8.683079285543727E0, + "lvl": 0 + }, + "end": { + "lat": 4.988526918808148E1, + "lng": 8.683190993508731E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "stairs", + "direction": "down", + "relative_direction": "straight" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "stairs_down_and_up", + "osm_file": "komponistenviertel-darmstadt-pbf.osm.pbf", + "start": { + "lat": 4.9884459199029465E1, + "lng": 8.683079285543727E0, + "lvl": 0 + }, + "end": { + "lat": 4.988447930041048E1, + "lng": 8.682549015845979E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "stairs", + "direction": "down", + "relative_direction": "straight" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "stairs", + "direction": "up", + "relative_direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "street_crossing_elbchausee", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.354926853152634E1, + "lng": 9.851434038791496E0, + "lvl": 0 + }, + "end": { + "lat": 5.354945176982216E1, + "lng": 9.85129625321474E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "crossing", + "street": "Elbchaussee" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Elbschloßstraße" + }, + { + "type": "stairs", + "direction": "up", + "relative_direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "street_crossing_margarete_mrosek", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.348542436090705E1, + "lng": 1.0158613381862892E1, + "lvl": 0 + }, + "end": { + "lat": 5.348528766950329E1, + "lng": 1.0158048845331647E1, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "crossing", + "street": "Margarete-Mrosek-Bogen" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "crossing_two_streets", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.357453883137305E1, + "lng": 9.975295334461606E0, + "lvl": 0 + }, + "end": { + "lat": 5.357423557112031E1, + "lng": 9.97678766010128E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Gustav-Falke-Straße" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Helene-Lange-Straße" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "crossing_seperated_street", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.358082827090095E1, + "lng": 9.946933387458444E0, + "lvl": 0 + }, + "end": { + "lat": 5.3580722911095506E1, + "lng": 9.94754014675354E0, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Methfesselstraße" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "crossing_grevenweg_1", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.355532025058787E1, + "lng": 1.0040549876621697E1, + "lvl": 0 + }, + "end": { + "lat": 5.355536300039128E1, + "lng": 1.0041125323237653E1, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Grevenweg" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "crossing_grevenweg_2", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3555346282446635E1, + "lng": 1.0040344299203184E1, + "lvl": 0 + }, + "end": { + "lat": 5.355536300039128E1, + "lng": 1.0041125323237653E1, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Grevenweg" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "crossing_grevenweg_3", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3555346282446635E1, + "lng": 1.0040344299203184E1, + "lvl": 0 + }, + "end": { + "lat": 5.3555322385464706E1, + "lng": 1.0040759455062217E1, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Grevenweg" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "crossing_grevenweg_4", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.355532135066002E1, + "lng": 1.0040550447383566E1, + "lvl": 0 + }, + "end": { + "lat": 5.3555341601387425E1, + "lng": 1.0040846537930436E1, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "crossing", + "street": "Grevenweg" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "composite_u_turn_1", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3561102591275045E1, + "lng": 1.005151327969824E1, + "lvl": 0 + }, + "end": { + "lat": 5.35609894766971E1, + "lng": 1.0051235830037626E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "turn_around" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "stay_left_turn_left", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3556758465188295E1, + "lng": 1.0039910326929828E1, + "lvl": 0 + }, + "end": { + "lat": 5.3558383129953256E1, + "lng": 1.0036018930616391E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "stay_left_turn_left_2", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3560623055010154E1, + "lng": 1.0037864819539578E1, + "lvl": 0 + }, + "end": { + "lat": 5.356032080557574E1, + "lng": 1.0040984991173955E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "big_intersection_turn_left_1", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.357429942480792E1, + "lng": 1.0036827067467044E1, + "lvl": 0 + }, + "end": { + "lat": 5.357416443364653E1, + "lng": 1.0034304088026772E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "big_intersection_turn_left_2", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3557272288340954E1, + "lng": 1.002546915365616E1, + "lvl": 0 + }, + "end": { + "lat": 5.3557330143608766E1, + "lng": 1.0020487217420223E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "big_intersection_turn_left_3", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.361274298566116E1, + "lng": 1.0154556417747528E1, + "lvl": 0 + }, + "end": { + "lat": 5.3615740687498146E1, + "lng": 1.015249083766247E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "big_intersection_turn_left_4", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.357907965720637E1, + "lng": 1.0043865197252217E1, + "lvl": 0 + }, + "end": { + "lat": 5.357870015546928E1, + "lng": 1.0039238473899218E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_right_with_link", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.360003006503504E1, + "lng": 1.0067684180553924E1, + "lvl": 0 + }, + "end": { + "lat": 5.359933162630392E1, + "lng": 1.007024955066612E1, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "double_straight_stairs_1", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3550030210691034E1, + "lng": 1.0008959192290888E1, + "lvl": 0 + }, + "end": { + "lat": 5.3550137138556806E1, + "lng": 1.000913396144952E1, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "stairs", + "direction": "down", + "relative_direction": "straight" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "double_straight_stairs_2", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.35502005770816E1, + "lng": 1.0008965449658264E1, + "lvl": 0 + }, + "end": { + "lat": 5.355019553096648E1, + "lng": 1.0008780141530394E1, + "lvl": 0 + }, + "profile": "foot", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "stairs", + "direction": "up", + "relative_direction": "straight" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "dedicated_left_turn_lane_intersection", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.347114602553626E1, + "lng": 9.942823168020368E0, + "lvl": 0 + }, + "end": { + "lat": 5.3477578761622226E1, + "lng": 9.927004897337952E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_left_turn_1", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.347541933495617E1, + "lng": 9.945460062546658E0, + "lvl": 0 + }, + "end": { + "lat": 5.347501565683655E1, + "lng": 9.949633334064655E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "intersection_left_turn_2", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3459557738719525E1, + "lng": 9.985788515205968E0, + "lvl": 0 + }, + "end": { + "lat": 5.346064181114801E1, + "lng": 9.986372512856748E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "turn_right_with_dedicated_turn_lane_1", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.352002314782041E1, + "lng": 9.985766631838914E0, + "lvl": 0 + }, + "end": { + "lat": 5.352090677060616E1, + "lng": 9.98193479434994E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "stay", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "right" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + }, + { + "name": "follow_street_with_left_turn", + "osm_file": "hamburg.osm.pbf", + "start": { + "lat": 5.3512154935914765E1, + "lng": 9.973060365558496E0, + "lvl": 0 + }, + "end": { + "lat": 5.351626618908929E1, + "lng": 9.968133436474716E0, + "lvl": 0 + }, + "profile": "car", + "direction": "forward", + "routing_algo": "dijkstra", + "expected_actions": [ + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "none" + }, + { + "type": "turn", + "direction": "left" + }, + { + "type": "none" + }, + { + "type": "destination", + "direction": "invalid" + } + ] + } +] \ No newline at end of file diff --git a/test/komponistenviertel-darmstadt-pbf.osm.pbf b/test/komponistenviertel-darmstadt-pbf.osm.pbf new file mode 100644 index 00000000..54cb3fcc Binary files /dev/null and b/test/komponistenviertel-darmstadt-pbf.osm.pbf differ diff --git a/test/milton-keynes-roundabouts.osm.pbf b/test/milton-keynes-roundabouts.osm.pbf new file mode 100644 index 00000000..75285896 Binary files /dev/null and b/test/milton-keynes-roundabouts.osm.pbf differ diff --git a/test/routing/instructions/routing_instruction_tests.cc b/test/routing/instructions/routing_instruction_tests.cc new file mode 100644 index 00000000..a56ff59f --- /dev/null +++ b/test/routing/instructions/routing_instruction_tests.cc @@ -0,0 +1,14 @@ +#include "osr/routing/instructions/routing_instructions_test_fixture.h" + +using namespace osr::test; + +const std::filesystem::path test_data_dir = TEST_DATA_DIR; + +INSTANTIATE_TEST_SUITE_P( + JsonRoutingTests, + routing_instructions_test_fixture, + testing::ValuesIn(load_from_json(test_data_dir / "instruction-test-cases.json", test_data_dir)), + [](const testing::TestParamInfo& info) { + return info.param.test_name; + }); + diff --git a/test/routing/instructions/routing_instructions_input.cc b/test/routing/instructions/routing_instructions_input.cc new file mode 100644 index 00000000..894ca25e --- /dev/null +++ b/test/routing/instructions/routing_instructions_input.cc @@ -0,0 +1,87 @@ +#include "osr/routing/instructions/routing_instructions_input.h" +#include +#include +#include + +#include "boost/json/parse.hpp" + +namespace osr::test { + +search_profile profile_from_string(std::string_view const s) { + if (s == "foot") return search_profile::kFoot; + if (s == "car") return search_profile::kCar; + if (s == "bike") return search_profile::kBike; + if (s == "wheelchair") return search_profile::kWheelchair; + if (s == "bike_elevation_low") return search_profile::kBikeElevationLow; + if (s == "bike_elevation_high") return search_profile::kBikeElevationHigh; + return search_profile::kFoot; +} + +direction direction_from_string(std::string_view const s) { + if (s == "backward") return direction::kBackward; + return direction::kForward; +} + +routing_algorithm routing_algo_from_string(std::string_view const s) { + if (s == "bidirectional") return routing_algorithm::kAStarBi; + return routing_algorithm::kDijkstra; +} + +std::vector load_from_json( + std::filesystem::path const& test_cases_path, + std::filesystem::path const& osm_pbf_dir_path) { + std::ifstream ifs(test_cases_path); + std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + + auto value = boost::json::parse(content); + auto& arr = value.as_array(); + std::vector cases; + + for (auto& item : arr) { + auto& test_case_obj = item.as_object(); + + // Parse Start/End + auto& s = test_case_obj.at("start").as_object(); + location const start = { + static_cast(s.at("lat").as_double()), + static_cast(s.at("lng").as_double()), + level_t{static_cast(s.at("lvl").as_int64())}}; + + auto& e = test_case_obj.at("end").as_object(); + location const end = {static_cast(e.at("lat").as_double()), + static_cast(e.at("lng").as_double()), + level_t{static_cast(e.at("lvl").as_int64())}}; + + // Parse Profile + const auto profile = + profile_from_string(test_case_obj.at("profile").as_string()); + + // Parse Direction + const auto direction = + direction_from_string(test_case_obj.at("direction").as_string()); + + // Parse routing algorithm + const auto algo = + routing_algo_from_string(test_case_obj.at("routing_algo").as_string()); + + // Parse Actions Array + std::vector expected_actions; + for (auto& act : test_case_obj.at("expected_actions").as_array()) { + expected_actions.push_back(act); + } + + cases.emplace_back( + test_case_obj.at("name").as_string().c_str(), + osm_pbf_dir_path / test_case_obj.at("osm_file").as_string().c_str(), + start, end, profile, direction, algo, std::move(expected_actions)); + } + std::stable_sort(cases.begin(), cases.end(), + [](routing_instructions_input const& a, + routing_instructions_input const& b) { + return a.osm_file < b.osm_file; + }); + return cases; +} + +} \ No newline at end of file diff --git a/test/routing_instructions_test_fixture.cc b/test/routing_instructions_test_fixture.cc new file mode 100644 index 00000000..68192795 --- /dev/null +++ b/test/routing_instructions_test_fixture.cc @@ -0,0 +1,91 @@ +#include "osr/routing/instructions/routing_instructions_test_fixture.h" + +#include +#include +#include + +#include "osr/extract/extract.h" +#include "osr/lookup.h" +#include "osr/routing/instructions/instruction_annotator.h" +#include "osr/routing/routing_test_util.h" + +namespace fs = std::filesystem; + +namespace osr::test { + +struct cached_dataset { + fs::path dir_; + std::unique_ptr w_; + std::unique_ptr l_; +}; + +static std::mutex g_cache_mutex; +static std::unordered_map> g_dataset_cache; + +std::shared_ptr get_dataset(fs::path const& pbf_path) { + std::lock_guard lock(g_cache_mutex); + auto const key = pbf_path.string(); + auto it = g_dataset_cache.find(key); + if (it == g_dataset_cache.end()) { + auto const dir = fmt::format("/tmp/osr_test/{}", pbf_path.filename().string()); + auto ec = std::error_code{}; + fs::remove_all(dir, ec); + fs::create_directories(dir, ec); + extract(false, pbf_path, dir, {}); + + auto dataset = std::make_shared(); + dataset->dir_ = dir; + dataset->w_ = std::make_unique(dir, cista::mmap::protection::READ); + dataset->l_ = std::make_unique(*dataset->w_, dir, cista::mmap::protection::READ); + g_dataset_cache[key] = dataset; + return dataset; + } + return it->second; +} + +void test_instructions( + fs::path const& pbf_path, + location const& from, + location const& to, + search_profile const sp, + routing_algorithm const algo, + direction const direction, + std::vector const& expected_actions_json) { + try { + auto const dataset = get_dataset(pbf_path); + const auto& w = *dataset->w_; + const auto& l = *dataset->l_; + + auto p = route(w, l, from, to, osr::get_parameters(sp), sp, direction, algo); + ASSERT_TRUE(p.has_value()); + instruction_annotator annotator(w); + annotator.annotate(p.value()); + + std::vector expected_actions; + expected_actions.reserve(expected_actions_json.size()); + for (auto const& val : expected_actions_json) { + expected_actions.push_back(from_json(val, w)); + } + + std::vector actions; + actions.reserve(p->segments_.size()); + for (auto const& s : p->segments_) { + actions.push_back(s.instruction_annotation_); + } + + EXPECT_EQ(expected_actions, actions); + } catch (...) { + GTEST_SKIP(); + } +} + +TEST_P(routing_instructions_test_fixture, ValidatesInstructions) { + const auto& test_params = GetParam(); + + test_instructions(test_params.osm_file, test_params.start, test_params.end, + test_params.profile, test_params.routing_algo, + test_params.direction, test_params.expected_actions); +} + +} + diff --git a/test/routing_test_util.cc b/test/routing_test_util.cc new file mode 100644 index 00000000..88c62de1 --- /dev/null +++ b/test/routing_test_util.cc @@ -0,0 +1,65 @@ +#include "osr/routing/routing_test_util.h" + +#include "osr/extract/extract.h" + +namespace osr::test { + +std::optional route(ways const& w, + lookup const& l, + location const& from, + location const& to, + profile_parameters const& params, + search_profile const sp, + direction const dir, + routing_algorithm const algo) { + return osr::route(params, w, l, sp, from, {to}, 900, dir, + 250.0, nullptr, nullptr, nullptr, algo); +} + +std::optional extract_and_route(std::string_view path, + location const& from, + location const& to, + profile_parameters const& params, + search_profile const sp, + direction const dir, + routing_algorithm const algo) { + auto const directory = fmt::format("/tmp/{}", path); + auto ec = std::error_code{}; + std::filesystem::remove_all(directory, ec); + std::filesystem::create_directories(directory, ec); + + extract(false, path, directory, {}); + + const auto w = ways{directory, cista::mmap::protection::READ}; + const auto l = lookup{w, directory, cista::mmap::protection::READ}; + + auto const p = route(w, l, from, to, params, sp, dir, algo); + utl::verify(p.has_value(), "{}: from={} to={} -> no route", path, + fmt::streamed(from), fmt::streamed(to)); + return p; +} + +std::string extract_route_to_feature(std::string_view path, + location const& from, + location const& to, + profile_parameters const& params, + search_profile const sp, + direction const dir, + routing_algorithm const algo) { + auto const directory = fmt::format("/tmp/{}", path); + auto ec = std::error_code{}; + std::filesystem::remove_all(directory, ec); + std::filesystem::create_directories(directory, ec); + + extract(false, path, directory, {}); + + const auto w = ways{directory, cista::mmap::protection::READ}; + const auto l = lookup{w, directory, cista::mmap::protection::READ}; + + auto const p = route(w, l, from, to, params, sp, dir, algo); + utl::verify(p.has_value(), "{}: from={} to={} -> no route", path, + fmt::streamed(from), fmt::streamed(to)); + return to_featurecollection(w, *p, false); +} + +} diff --git a/test/routing_tests.cc b/test/routing_tests.cc index 502895d1..fba5f5ff 100644 --- a/test/routing_tests.cc +++ b/test/routing_tests.cc @@ -3,45 +3,22 @@ #include "gtest/gtest.h" #include "osr/geojson.h" +#include "include/osr/routing/routing_test_util.h" #include "osr/extract/extract.h" #include "osr/lookup.h" #include "osr/routing/profiles/foot.h" #include "osr/routing/route.h" #include "osr/ways.h" -namespace fs = std::filesystem; - -std::string extract_and_route( - std::string_view path, - osr::location const& from, - osr::location const& to, - osr::profile_parameters const& params = - osr::foot::parameters{}, - osr::search_profile const profile = osr::search_profile::kFoot) { - auto const dir = fs::temp_directory_path() / path; - auto ec = std::error_code{}; - fs::remove_all(dir, ec); - fs::create_directories(dir, ec); - - osr::extract(false, path, dir, {}); - - auto w = osr::ways{dir, cista::mmap::protection::READ}; - auto l = osr::lookup{w, dir, cista::mmap::protection::READ}; - - auto const p = osr::route(params, w, l, profile, from, {to}, 900, - osr::direction::kForward, 250.0, nullptr, nullptr, - nullptr, osr::routing_algorithm::kDijkstra); - utl::verify(p.has_value(), "{}: from={} to={} -> no route", path, - fmt::streamed(from), fmt::streamed(to)); - return osr::to_featurecollection(w, *p, false); -} - TEST(routing, foot_island) { auto const from = osr::location{49.872715, 8.651534, osr::level_t{0.F}}; auto const to = osr::location{49.873023, 8.651523, osr::level_t{0.F}}; EXPECT_EQ( - R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.651514431787469E0,4.987274386418564E1],[8.6515174E0,4.98727447E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551426,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.6515174E0,4.98727447E1],[8.6515563E0,4.98727556E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":22937760,"cost":2,"distance":2},"geometry":{"type":"LineString","coordinates":[[8.6515563E0,4.98727556E1],[8.6515406E0,4.98727706E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":22937760,"cost":10,"distance":10},"geometry":{"type":"LineString","coordinates":[[8.6515406E0,4.98727706E1],[8.6514528E0,4.98728367E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":847710844,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[8.6514528E0,4.98728367E1],[8.6514754E0,4.98728959E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":23511479,"cost":8,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.6514754E0,4.98728959E1],[8.651539E0,4.98729497E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":23511479,"cost":2,"distance":2},"geometry":{"type":"LineString","coordinates":[[8.651539E0,4.98729497E1],[8.6515596E0,4.98729618E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":8,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6515596E0,4.98729618E1],[8.651493334251755E0,4.987300334757231E1]]}}]})", - extract_and_route("test/luisenplatz-darmstadt.osm.pbf", from, to)); + R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.651514431787469E0,4.987274386418564E1],[8.6515174E0,4.98727447E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551426,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.6515174E0,4.98727447E1],[8.6515563E0,4.98727556E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":22937760,"cost":12,"distance":12},"geometry":{"type":"LineString","coordinates":[[8.6515563E0,4.98727556E1],[8.6515406E0,4.98727706E1],[8.6514528E0,4.98728367E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":847710844,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[8.6514528E0,4.98728367E1],[8.6514754E0,4.98728959E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":23511479,"cost":10,"distance":10},"geometry":{"type":"LineString","coordinates":[[8.6514754E0,4.98728959E1],[8.651539E0,4.98729497E1],[8.6515596E0,4.98729618E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":8,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6515596E0,4.98729618E1],[8.651493334251755E0,4.987300334757231E1]]}}]})", + osr::test::extract_route_to_feature( + "test/luisenplatz-darmstadt.osm.pbf", from, to, + osr::foot::parameters{}, osr::search_profile::kFoot, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, foot_ferry) { @@ -49,7 +26,10 @@ TEST(routing, foot_ferry) { auto const to = osr::location{41.921436, 8.740166, osr::level_t{0.F}}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":84,"distance":101},"geometry":{"type":"LineString","coordinates":[[8.7415295E0,4.19221369E1],[8.7415321E0,4.19222147E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":147,"distance":177},"geometry":{"type":"LineString","coordinates":[[8.7415321E0,4.19222147E1],[8.7408991E0,4.19222267E1],[8.7400905E0,4.19218375E1]]}}]})", - extract_and_route("test/ajaccio-ferry.osm.pbf", from, to)); + osr::test::extract_route_to_feature( + "test/ajaccio-ferry.osm.pbf", from, to, + osr::foot::parameters{}, osr::search_profile::kFoot, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, foot_sidewalk_separate) { @@ -57,7 +37,10 @@ TEST(routing, foot_sidewalk_separate) { auto const to = osr::location{46.17249, 8.800507, osr::level_t{0.F}}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":9,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.799938095816453E0,4.617140034140192E1],[8.7999968E0,4.61714749E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":313626022,"cost":52,"distance":52},"geometry":{"type":"LineString","coordinates":[[8.7999968E0,4.61714749E1],[8.8001826E0,4.61717214E1],[8.8002241E0,4.61717698E1],[8.8002659E0,4.61718363E1],[8.8002881E0,4.61718996E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":37078706,"cost":157,"distance":22},"geometry":{"type":"LineString","coordinates":[[8.8002881E0,4.61718996E1],[8.800417E0,4.61718962E1],[8.8005511E0,4.61719431E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":352238316,"cost":10,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.8005511E0,4.61719431E1],[8.8005367E0,4.61720098E1],[8.8005388E0,4.61720441E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":352238316,"cost":29,"distance":32},"geometry":{"type":"LineString","coordinates":[[8.8005388E0,4.61720441E1],[8.8005458E0,4.61721137E1],[8.8005774E0,4.61723293E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1214507783,"cost":9,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.8005774E0,4.61723293E1],[8.8006558E0,4.61724109E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1214515049,"cost":15,"distance":15},"geometry":{"type":"LineString","coordinates":[[8.8006558E0,4.61724109E1],[8.8004622E0,4.61724207E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":144,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.8004622E0,4.61724207E1],[8.8004859E0,4.61724675E1],[8.800498396766157E0,4.6172492096108655E1]]}}]})", - extract_and_route("test/locarno.osm.pbf", from, to)); + osr::test::extract_route_to_feature( + "test/locarno.osm.pbf", from, to, osr::foot::parameters{}, + osr::search_profile::kFoot, osr::direction::kForward, + osr::routing_algorithm::kDijkstra)); } TEST(routing, foot_corridor) { @@ -67,7 +50,10 @@ TEST(routing, foot_corridor) { osr::location{51.547004658329, -0.05694437499428773, osr::level_t{0.F}}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":1,"distance":1},"geometry":{"type":"LineString","coordinates":[[-5.6227579350404025E-2,5.154663151628954E1],[-5.62413E-2,5.15466308E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1057782512,"cost":6,"distance":6},"geometry":{"type":"LineString","coordinates":[[-5.62413E-2,5.15466308E1],[-5.62459E-2,5.15466862E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103497,"cost":3,"distance":4},"geometry":{"type":"LineString","coordinates":[[-5.62459E-2,5.15466862E1],[-5.62474E-2,5.15467056E1],[-5.62483E-2,5.15467228E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103497,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[-5.62483E-2,5.15467228E1],[-5.62524E-2,5.15467815E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1079378708,"cost":5,"distance":6},"geometry":{"type":"LineString","coordinates":[[-5.62524E-2,5.15467815E1],[-5.62531E-2,5.15468061E1],[-5.62538E-2,5.15468316E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1079378709,"cost":3,"distance":4},"geometry":{"type":"LineString","coordinates":[[-5.62538E-2,5.15468316E1],[-5.62546E-2,5.15468484E1],[-5.62621E-2,5.15468663E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103498,"cost":3,"distance":4},"geometry":{"type":"LineString","coordinates":[[-5.62621E-2,5.15468663E1],[-5.62628E-2,5.15469052E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1063762150,"cost":1,"distance":1},"geometry":{"type":"LineString","coordinates":[[-5.62628E-2,5.15469052E1],[-5.6263E-2,5.15469163E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1063762149,"cost":2,"distance":3},"geometry":{"type":"LineString","coordinates":[[-5.6263E-2,5.15469163E1],[-5.62635E-2,5.1546946E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103496,"cost":7,"distance":9},"geometry":{"type":"LineString","coordinates":[[-5.62635E-2,5.1546946E1],[-5.62657E-2,5.15470109E1],[-5.62908E-2,5.15470107E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":41,"distance":49},"geometry":{"type":"LineString","coordinates":[[-5.62908E-2,5.15470107E1],[-5.62911E-2,5.1547031E1],[-5.66186E-2,5.15470287E1],[-5.69331E-2,5.15470277E1],[-5.694401949978242E-2,5.1547027764967275E1]]}}]})", - extract_and_route("test/london-corridor.osm.pbf", from, to)); + osr::test::extract_route_to_feature( + "test/london-corridor.osm.pbf", from, to, + osr::foot::parameters{}, osr::search_profile::kFoot, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, foot_stop_area) { @@ -77,7 +63,10 @@ TEST(routing, foot_stop_area) { osr::location{48.725480463902784, 2.2588322597458728, osr::level_t{0.F}}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":13,"distance":13},"geometry":{"type":"LineString","coordinates":[[2.261314500627132E0,4.8725266362834425E1],[2.2613838E0,4.87253219E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1290289280,"cost":6,"distance":6},"geometry":{"type":"LineString","coordinates":[[2.2613838E0,4.87253219E1],[2.2613206E0,4.87253586E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1290289280,"cost":5,"distance":5},"geometry":{"type":"LineString","coordinates":[[2.2613206E0,4.87253586E1],[2.2612721E0,4.87253867E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1290289280,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[2.2612721E0,4.87253867E1],[2.2612448E0,4.87254022E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":892871341,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[2.2612448E0,4.87254022E1],[2.2611731E0,4.87254412E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1419149027,"cost":7,"distance":9},"geometry":{"type":"LineString","coordinates":[[2.2611731E0,4.87254412E1],[2.2610764E0,4.87254927E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1419149026,"cost":16,"distance":19},"geometry":{"type":"LineString","coordinates":[[2.2610764E0,4.87254927E1],[2.260814E0,4.87254962E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1359929257,"cost":20,"distance":24},"geometry":{"type":"LineString","coordinates":[[2.260814E0,4.87254962E1],[2.2605996E0,4.87253278E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1429030767,"cost":10,"distance":12},"geometry":{"type":"LineString","coordinates":[[2.2605996E0,4.87253278E1],[2.2604973E0,4.87252419E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1429030769,"cost":2,"distance":2},"geometry":{"type":"LineString","coordinates":[[2.2604973E0,4.87252419E1],[2.2604761E0,4.87252248E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1429030768,"cost":7,"distance":8},"geometry":{"type":"LineString","coordinates":[[2.2604761E0,4.87252248E1],[2.2604036E0,4.87251661E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1359929257,"cost":22,"distance":26},"geometry":{"type":"LineString","coordinates":[[2.2604036E0,4.87251661E1],[2.2602208E0,4.87250223E1],[2.2601787E0,4.87249893E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":25,"distance":30},"geometry":{"type":"LineString","coordinates":[[2.2601787E0,4.87249893E1],[2.2600672E0,4.87250507E1],[2.2600272E0,4.87250721E1],[2.2598576E0,4.87251608E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":11,"distance":13},"geometry":{"type":"LineString","coordinates":[[2.2598576E0,4.87251608E1],[2.2597231E0,4.87252337E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":4,"distance":5},"geometry":{"type":"LineString","coordinates":[[2.2597231E0,4.87252337E1],[2.2596673E0,4.87252638E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":93,"distance":112},"geometry":{"type":"LineString","coordinates":[[2.2596673E0,4.87252638E1],[2.2596292E0,4.8725284E1],[2.2584789E0,4.87258964E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":13,"distance":16},"geometry":{"type":"LineString","coordinates":[[2.2584789E0,4.87258964E1],[2.2583131E0,4.87259901E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642263621,"cost":8,"distance":10},"geometry":{"type":"LineString","coordinates":[[2.2583131E0,4.87259901E1],[2.2582647E0,4.87259491E1],[2.2582257E0,4.8725916E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642578765,"cost":2,"distance":3},"geometry":{"type":"LineString","coordinates":[[2.2582257E0,4.8725916E1],[2.2581937E0,4.87259329E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642263625,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[2.2581937E0,4.87259329E1],[2.2581458E0,4.87258947E1],[2.2581625E0,4.87258856E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642578757,"cost":7,"distance":8},"geometry":{"type":"LineString","coordinates":[[2.2581625E0,4.87258856E1],[2.2582443E0,4.87258411E1]]}},{"type":"Feature","properties":{"level":5E-1,"osm_way_id":642263631,"cost":12,"distance":14},"geometry":{"type":"LineString","coordinates":[[2.2582443E0,4.87258411E1],[2.2581285E0,4.87257422E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1192127892,"cost":1,"distance":1},"geometry":{"type":"LineString","coordinates":[[2.2581285E0,4.87257422E1],[2.2581167E0,4.87257321E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1192127892,"cost":2,"distance":2},"geometry":{"type":"LineString","coordinates":[[2.2581167E0,4.87257321E1],[2.2581393E0,4.87257206E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":55,"distance":66},"geometry":{"type":"LineString","coordinates":[[2.2581393E0,4.87257206E1],[2.2582250818254357E0,4.872579309702946E1]]}}]})", - extract_and_route("test/station-border.osm.pbf", from, to)); + osr::test::extract_route_to_feature( + "test/station-border.osm.pbf", from, to, + osr::foot::parameters{}, osr::search_profile::kFoot, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, bus_private_gate) { @@ -85,14 +74,17 @@ TEST(routing, bus_private_gate) { auto const to = osr::location{49.1020397, 8.4332380, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":14,"distance":76},"geometry":{"type":"LineString","coordinates":[[8.438033588333512E0,4.9113532427453805E1],[8.4377581E0,4.91128665E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":849584375,"cost":4,"distance":24},"geometry":{"type":"LineString","coordinates":[[8.4377581E0,4.91128665E1],[8.4376667E0,4.91126578E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":137079241,"cost":8,"distance":46},"geometry":{"type":"LineString","coordinates":[[8.4376667E0,4.91126578E1],[8.4375677E0,4.91124791E1],[8.4374778E0,4.91122613E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":849584376,"cost":5,"distance":28},"geometry":{"type":"LineString","coordinates":[[8.4374778E0,4.91122613E1],[8.437396E0,4.91121011E1],[8.4373648E0,4.91120195E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":851838931,"cost":61,"distance":5},"geometry":{"type":"LineString","coordinates":[[8.4373648E0,4.91120195E1],[8.4373498E0,4.91119765E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":849584369,"cost":68,"distance":6},"geometry":{"type":"LineString","coordinates":[[8.4373498E0,4.91119765E1],[8.4373272E0,4.91119262E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":310571714,"cost":8,"distance":17},"geometry":{"type":"LineString","coordinates":[[8.4373272E0,4.91119262E1],[8.4372649E0,4.91117757E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":310571711,"cost":52,"distance":108},"geometry":{"type":"LineString","coordinates":[[8.4372649E0,4.91117757E1],[8.4369716E0,4.91110229E1],[8.4369164E0,4.91108317E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":28,"distance":57},"geometry":{"type":"LineString","coordinates":[[8.4369164E0,4.91108317E1],[8.4367049E0,4.91103396E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":4,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.4367049E0,4.91103396E1],[8.4366731E0,4.91102697E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":4,"distance":10},"geometry":{"type":"LineString","coordinates":[[8.4366731E0,4.91102697E1],[8.4366345E0,4.91101859E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":44,"distance":89},"geometry":{"type":"LineString","coordinates":[[8.4366345E0,4.91101859E1],[8.4363175E0,4.91094145E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":4,"distance":5},"geometry":{"type":"LineString","coordinates":[[8.4363175E0,4.91094145E1],[8.4363014E0,4.91093749E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":4,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.4363014E0,4.91093749E1],[8.4362712E0,4.91093008E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":20,"distance":45},"geometry":{"type":"LineString","coordinates":[[8.4362712E0,4.91093008E1],[8.4361122E0,4.91089107E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":40,"distance":83},"geometry":{"type":"LineString","coordinates":[[8.4361122E0,4.91089107E1],[8.4358119E0,4.9108187E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":3498959,"cost":36,"distance":79},"geometry":{"type":"LineString","coordinates":[[8.4358119E0,4.9108187E1],[8.4355482E0,4.91075004E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":851838936,"cost":112,"distance":232},"geometry":{"type":"LineString","coordinates":[[8.4355482E0,4.91075004E1],[8.4354366E0,4.91072387E1],[8.4347732E0,4.91056838E1],[8.4346938E0,4.91054924E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":155843105,"cost":36,"distance":75},"geometry":{"type":"LineString","coordinates":[[8.4346938E0,4.91054924E1],[8.4344235E0,4.91048453E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":155843105,"cost":36,"distance":74},"geometry":{"type":"LineString","coordinates":[[8.4344235E0,4.91048453E1],[8.4341525E0,4.91042005E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":155843105,"cost":52,"distance":107},"geometry":{"type":"LineString","coordinates":[[8.4341525E0,4.91042005E1],[8.4337533E0,4.91032736E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":155843105,"cost":4,"distance":12},"geometry":{"type":"LineString","coordinates":[[8.4337533E0,4.91032736E1],[8.4337108E0,4.91031653E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":155843105,"cost":40,"distance":83},"geometry":{"type":"LineString","coordinates":[[8.4337108E0,4.91031653E1],[8.4333945E0,4.91024469E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":24,"distance":46},"geometry":{"type":"LineString","coordinates":[[8.4333945E0,4.91024469E1],[8.433240399498114E0,4.910203931113488E1]]}}]})", - extract_and_route("test/karlsruhe-kit-nord.osm.pbf", from, to, - osr::bus::parameters{}, osr::search_profile::kBus)); + osr::test::extract_route_to_feature( + "test/karlsruhe-kit-nord.osm.pbf", from, to, osr::bus::parameters{}, + osr::search_profile::kBus, osr::direction::kForward, + osr::routing_algorithm::kDijkstra)); // car profile should not be allowed to pass through the private gate -> // expect no route - EXPECT_ANY_THROW(extract_and_route("test/karlsruhe-kit-nord.osm.pbf", from, - to, osr::car::parameters{}, - osr::search_profile::kCar)); + EXPECT_ANY_THROW(osr::test::extract_route_to_feature( + "test/karlsruhe-kit-nord.osm.pbf", from, to, osr::car::parameters{}, + osr::search_profile::kCar, osr::direction::kForward, + osr::routing_algorithm::kDijkstra)); } TEST(routing, ferry_route) { @@ -100,8 +92,10 @@ TEST(routing, ferry_route) { auto const to = osr::location{53.540747, 9.974202, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":0,"distance":3},"geometry":{"type":"LineString","coordinates":[[9.9715663E0,5.35447076E1],[9.9715663E0,5.35447076E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":25445781,"cost":40,"distance":403},"geometry":{"type":"LineString","coordinates":[[9.9715663E0,5.35447076E1],[9.9714744E0,5.35445564E1],[9.971377E0,5.3544327E1],[9.9713103E0,5.35440655E1],[9.971313E0,5.35437668E1],[9.9713504E0,5.35434353E1],[9.9714368E0,5.35431238E1],[9.9716401E0,5.35427136E1],[9.9717758E0,5.35425032E1],[9.9720512E0,5.35422903E1],[9.9724062E0,5.3542125E1],[9.9733279E0,5.35417842E1],[9.973563E0,5.3541663E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1372460184,"cost":11,"distance":115},"geometry":{"type":"LineString","coordinates":[[9.973563E0,5.3541663E1],[9.9739284E0,5.35413931E1],[9.9741339E0,5.35412257E1],[9.9742222E0,5.35410764E1],[9.9742233E0,5.35409368E1],[9.9741898E0,5.35407539E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":0,"distance":1},"geometry":{"type":"LineString","coordinates":[[9.9741898E0,5.35407539E1],[9.974193272653226E0,5.3540755204448416E1]]}}]})", - extract_and_route("test/hamburg-hafen-ferry.osm.pbf", from, to, - osr::ferry::parameters{}, osr::search_profile::kFerry)); + osr::test::extract_route_to_feature( + "test/hamburg-hafen-ferry.osm.pbf", from, to, + osr::ferry::parameters{}, osr::search_profile::kFerry, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, tram_junction) { @@ -112,9 +106,10 @@ TEST(routing, tram_junction) { auto const to = osr::location{49.002328, 8.395582, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":14,"distance":14},"geometry":{"type":"LineString","coordinates":[[8.394302960176601E0,4.900197169592092E1],[8.3943169E0,4.90020954E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":404384687,"cost":5,"distance":5},"geometry":{"type":"LineString","coordinates":[[8.3943169E0,4.90020954E1],[8.3943189E0,4.90021148E1],[8.3943214E0,4.90021421E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":145048133,"cost":14,"distance":12},"geometry":{"type":"LineString","coordinates":[[8.3943214E0,4.90021421E1],[8.3943347E0,4.9002237E1],[8.3943377E0,4.90022509E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":145048133,"cost":20,"distance":14},"geometry":{"type":"LineString","coordinates":[[8.3943377E0,4.90022509E1],[8.3943474E0,4.90022723E1],[8.3943902E0,4.90023232E1],[8.3944341E0,4.90023612E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":145048133,"cost":17,"distance":7},"geometry":{"type":"LineString","coordinates":[[8.3944341E0,4.90023612E1],[8.3944564E0,4.9002373E1],[8.3944881E0,4.90023898E1],[8.3945052E0,4.90023989E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":145048133,"cost":15,"distance":6},"geometry":{"type":"LineString","coordinates":[[8.3945052E0,4.90023989E1],[8.3945345E0,4.90024088E1],[8.3945768E0,4.90024214E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":145048133,"cost":23,"distance":13},"geometry":{"type":"LineString","coordinates":[[8.3945768E0,4.90024214E1],[8.3946412E0,4.90024284E1],[8.3946882E0,4.90024286E1],[8.3947562E0,4.90024251E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":150939191,"cost":26,"distance":21},"geometry":{"type":"LineString","coordinates":[[8.3947562E0,4.90024251E1],[8.3950429E0,4.90023862E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":150939191,"cost":21,"distance":21},"geometry":{"type":"LineString","coordinates":[[8.3950429E0,4.90023862E1],[8.3953208E0,4.90023485E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":150939191,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.3953208E0,4.90023485E1],[8.3953654E0,4.90023425E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":17,"distance":17},"geometry":{"type":"LineString","coordinates":[[8.3953654E0,4.90023425E1],[8.3955258E0,4.90023205E1],[8.395577741913971E0,4.900231379781756E1]]}}]})", - extract_and_route("test/tram-junction.osm.pbf", from, to, - osr::railway::parameters{}, - osr::search_profile::kRailway)); + osr::test::extract_route_to_feature( + "test/tram-junction.osm.pbf", from, to, osr::railway::parameters{}, + osr::search_profile::kRailway, osr::direction::kForward, + osr::routing_algorithm::kDijkstra)); } TEST(routing, incline_oneway_combination) { @@ -123,8 +118,10 @@ TEST(routing, incline_oneway_combination) { auto const to = osr::location{54.467231, 18.509283, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":2,"distance":34},"geometry":{"type":"LineString","coordinates":[[1.850720928395522E1,5.446895820930245E1],[1.85072976E1,5.44689494E1],[1.85077098E1,5.44688994E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":84736865,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[1.85077098E1,5.44688994E1],[1.85079268E1,5.44688676E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1466304642,"cost":1,"distance":18},"geometry":{"type":"LineString","coordinates":[[1.85079268E1,5.44688676E1],[1.85081972E1,5.4468828E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":129959871,"cost":3,"distance":38},"geometry":{"type":"LineString","coordinates":[[1.85081972E1,5.4468828E1],[1.85087622E1,5.44687431E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":516453827,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[1.85087622E1,5.44687431E1],[1.85087959E1,5.44687377E1],[1.85089192E1,5.44687181E1],[1.85089688E1,5.44687102E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":400398318,"cost":11,"distance":9},"geometry":{"type":"LineString","coordinates":[[1.85089688E1,5.44687102E1],[1.85089596E1,5.44686839E1],[1.8508959E1,5.4468627E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":328595159,"cost":5,"distance":65},"geometry":{"type":"LineString","coordinates":[[1.8508959E1,5.4468627E1],[1.8508973E1,5.44685687E1],[1.8509001E1,5.44684687E1],[1.85090447E1,5.44683137E1],[1.85091248E1,5.44680547E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":328595159,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[1.85091248E1,5.44680547E1],[1.85091577E1,5.4467934E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":6,"distance":79},"geometry":{"type":"LineString","coordinates":[[1.85091577E1,5.4467934E1],[1.85092136E1,5.44677472E1],[1.85092609E1,5.44675879E1],[1.85092683E1,5.44675629E1],[1.85092941E1,5.44674731E1],[1.85093025E1,5.4467404E1],[1.85093033E1,5.4467355E1],[1.85092973E1,5.44673081E1],[1.850927727479884E1,5.4467231505553535E1]]}}]})", - extract_and_route("test/gdynia-kariny.osm.pbf", from, to, - osr::bus::parameters{}, osr::search_profile::kBus)); + osr::test::extract_route_to_feature( + "test/gdynia-kariny.osm.pbf", from, to, osr::bus::parameters{}, + osr::search_profile::kBus, osr::direction::kForward, + osr::routing_algorithm::kDijkstra)); } TEST(routing, darmstadt_willy_luisenplatz_highway_busway) { @@ -133,9 +130,10 @@ TEST(routing, darmstadt_willy_luisenplatz_highway_busway) { auto const to = osr::location{49.876042, 8.650119, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":2,"distance":22},"geometry":{"type":"LineString","coordinates":[[8.651250568496865E0,4.98730858444102E1],[8.6513009E0,4.98731323E1],[8.6513615E0,4.9873198E1],[8.651378E0,4.98732369E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551428,"cost":0,"distance":6},"geometry":{"type":"LineString","coordinates":[[8.651378E0,4.98732369E1],[8.6514002E0,4.98732894E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551427,"cost":0,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.6514002E0,4.98732894E1],[8.6514025E0,4.98733198E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551427,"cost":1,"distance":7},"geometry":{"type":"LineString","coordinates":[[8.6514025E0,4.98733198E1],[8.6514047E0,4.9873348E1],[8.6513988E0,4.98733841E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551427,"cost":0,"distance":4},"geometry":{"type":"LineString","coordinates":[[8.6513988E0,4.98733841E1],[8.65139E0,4.98734211E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551424,"cost":5,"distance":70},"geometry":{"type":"LineString","coordinates":[[8.65139E0,4.98734211E1],[8.6513679E0,4.9873488E1],[8.6512738E0,4.98737519E1],[8.6511757E0,4.98740344E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551424,"cost":1,"distance":7},"geometry":{"type":"LineString","coordinates":[[8.6511757E0,4.98740344E1],[8.6511536E0,4.98740994E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201687302,"cost":4,"distance":52},"geometry":{"type":"LineString","coordinates":[[8.6511536E0,4.98740994E1],[8.6510817E0,4.98743116E1],[8.651003E0,4.98745578E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551432,"cost":8,"distance":113},"geometry":{"type":"LineString","coordinates":[[8.651003E0,4.98745578E1],[8.6509721E0,4.98746543E1],[8.650876E0,4.98749577E1],[8.6507742E0,4.98752626E1],[8.6506871E0,4.98755414E1],[8.6506832E0,4.98755571E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551432,"cost":2,"distance":28},"geometry":{"type":"LineString","coordinates":[[8.6506832E0,4.98755571E1],[8.6506755E0,4.98755876E1],[8.6506056E0,4.98758015E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1207456799,"cost":0,"distance":4},"geometry":{"type":"LineString","coordinates":[[8.6506056E0,4.98758015E1],[8.6505953E0,4.98758329E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1207456800,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.6505953E0,4.98758329E1],[8.6505628E0,4.98759322E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551421,"cost":0,"distance":4},"geometry":{"type":"LineString","coordinates":[[8.6505628E0,4.98759322E1],[8.6505329E0,4.98759607E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551421,"cost":0,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.6505329E0,4.98759607E1],[8.6505177E0,4.98759759E1],[8.6505132E0,4.98759797E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551421,"cost":0,"distance":6},"geometry":{"type":"LineString","coordinates":[[8.6505132E0,4.98759797E1],[8.6504785E0,4.98760087E1],[8.6504544E0,4.98760219E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551421,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.6504544E0,4.98760219E1],[8.6504304E0,4.98760352E1],[8.6503651E0,4.98760535E1],[8.6503173E0,4.98760631E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551421,"cost":1,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.6503173E0,4.98760631E1],[8.6502623E0,4.98760632E1],[8.6501998E0,4.98760604E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":0,"distance":6},"geometry":{"type":"LineString","coordinates":[[8.6501998E0,4.98760604E1],[8.650117720690279E0,4.98760447944274E1]]}}]})", - extract_and_route( + osr::test::extract_route_to_feature( "test/darmstadt-willy-luisenplatz-highway-busway.osm.pbf", from, to, - osr::bus::parameters{}, osr::search_profile::kBus)); + osr::bus::parameters{}, osr::search_profile::kBus, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, darmstadt_hbf_busses_not_on_rails) { @@ -144,8 +142,10 @@ TEST(routing, darmstadt_hbf_busses_not_on_rails) { auto const to = osr::location{49.870393, 8.633510, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.631508562388836E0,4.9872686182346236E1],[8.6315313E0,4.9872604E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":230340028,"cost":1,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.6315313E0,4.9872604E1],[8.6315505E0,4.98725343E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":230340028,"cost":1,"distance":18},"geometry":{"type":"LineString","coordinates":[[8.6315505E0,4.98725343E1],[8.6315682E0,4.98724307E1],[8.6315773E0,4.98723775E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":973475460,"cost":0,"distance":5},"geometry":{"type":"LineString","coordinates":[[8.6315773E0,4.98723775E1],[8.6315847E0,4.98723341E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":973475460,"cost":0,"distance":4},"geometry":{"type":"LineString","coordinates":[[8.6315847E0,4.98723341E1],[8.6315902E0,4.98723017E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":973478659,"cost":2,"distance":21},"geometry":{"type":"LineString","coordinates":[[8.6315902E0,4.98723017E1],[8.6316178E0,4.98721392E1],[8.6316226E0,4.98721109E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":230340027,"cost":2,"distance":28},"geometry":{"type":"LineString","coordinates":[[8.6316226E0,4.98721109E1],[8.6316407E0,4.98720468E1],[8.6316658E0,4.98719811E1],[8.6317024E0,4.98719202E1],[8.6317402E0,4.98718677E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":992498844,"cost":1,"distance":18},"geometry":{"type":"LineString","coordinates":[[8.6317402E0,4.98718677E1],[8.6318654E0,4.98717225E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":973477991,"cost":2,"distance":22},"geometry":{"type":"LineString","coordinates":[[8.6318654E0,4.98717225E1],[8.6320127E0,4.98715538E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":973485144,"cost":2,"distance":21},"geometry":{"type":"LineString","coordinates":[[8.6320127E0,4.98715538E1],[8.6321536E0,4.98713891E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1113008801,"cost":1,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6321536E0,4.98713891E1],[8.6322163E0,4.98713158E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":973477394,"cost":1,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6322163E0,4.98713158E1],[8.6322781E0,4.98712435E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":230340026,"cost":5,"distance":72},"geometry":{"type":"LineString","coordinates":[[8.6322781E0,4.98712435E1],[8.6323387E0,4.98711721E1],[8.6327576E0,4.98706785E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":281583585,"cost":0,"distance":4},"geometry":{"type":"LineString","coordinates":[[8.6327576E0,4.98706785E1],[8.632783E0,4.98706484E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":281583585,"cost":2,"distance":22},"geometry":{"type":"LineString","coordinates":[[8.632783E0,4.98706484E1],[8.6329395E0,4.9870473E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551444,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.6329395E0,4.9870473E1],[8.632991E0,4.98704372E1],[8.6330466E0,4.98704073E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551443,"cost":0,"distance":2},"geometry":{"type":"LineString","coordinates":[[8.6330466E0,4.98704073E1],[8.633068E0,4.98703983E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551449,"cost":1,"distance":10},"geometry":{"type":"LineString","coordinates":[[8.633068E0,4.98703983E1],[8.633114E0,4.98703832E1],[8.6331557E0,4.98703735E1],[8.633201E0,4.98703665E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551447,"cost":1,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.633201E0,4.98703665E1],[8.6333066E0,4.98703645E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551445,"cost":0,"distance":5},"geometry":{"type":"LineString","coordinates":[[8.6333066E0,4.98703645E1],[8.6333799E0,4.98703738E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":1,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6333799E0,4.98703738E1],[8.633510410792013E0,4.9870391759984216E1]]}}]})", - extract_and_route("test/da_hbf_2.osm.pbf", from, to, - osr::bus::parameters{}, osr::search_profile::kBus)); + osr::test::extract_route_to_feature( + "test/da_hbf_2.osm.pbf", from, to, osr::bus::parameters{}, + osr::search_profile::kBus, osr::direction::kForward, + osr::routing_algorithm::kDijkstra)); } TEST(routing, a17_access_yes_barrier) { @@ -154,12 +154,16 @@ TEST(routing, a17_access_yes_barrier) { auto const to = osr::location{51.01311341142, 13.717227005180, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":3,"distance":43},"geometry":{"type":"LineString","coordinates":[[1.3726364319752435E1,5.1008833043882724E1],[1.37258517E1,5.10090094E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":233629713,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[1.37258517E1,5.10090094E1],[1.37256789E1,5.10090685E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":505372075,"cost":2,"distance":41},"geometry":{"type":"LineString","coordinates":[[1.37256789E1,5.10090685E1],[1.3725181E1,5.10092599E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":318372932,"cost":8,"distance":136},"geometry":{"type":"LineString","coordinates":[[1.3725181E1,5.10092599E1],[1.37248222E1,5.10093998E1],[1.37241306E1,5.10097037E1],[1.37235956E1,5.10099611E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":10682579,"cost":10,"distance":159},"geometry":{"type":"LineString","coordinates":[[1.37235956E1,5.10099611E1],[1.37231133E1,5.10102182E1],[1.37227302E1,5.10104277E1],[1.3722152E1,5.10107639E1],[1.37219044E1,5.10109094E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":318372935,"cost":13,"distance":219},"geometry":{"type":"LineString","coordinates":[[1.37219044E1,5.10109094E1],[1.3721322E1,5.10112584E1],[1.37201667E1,5.1011907E1],[1.37195582E1,5.10122109E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":10682549,"cost":4,"distance":67},"geometry":{"type":"LineString","coordinates":[[1.37195582E1,5.10122109E1],[1.37191778E1,5.10123852E1],[1.37189568E1,5.10124808E1],[1.37187802E1,5.10125572E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":8,"distance":127},"geometry":{"type":"LineString","coordinates":[[1.37187802E1,5.10125572E1],[1.3718396E1,5.10127141E1],[1.37180063E1,5.1012866E1],[1.3717246938023868E1,5.101313563118154E1]]}}]})", - extract_and_route("test/a17-barrier-access-yes.osm.pbf", from, to, - osr::bus::parameters{}, osr::search_profile::kBus)); + osr::test::extract_route_to_feature( + "test/a17-barrier-access-yes.osm.pbf", from, to, + osr::bus::parameters{}, osr::search_profile::kBus, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":3,"distance":43},"geometry":{"type":"LineString","coordinates":[[1.3726364319752435E1,5.1008833043882724E1],[1.37258517E1,5.10090094E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":233629713,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[1.37258517E1,5.10090094E1],[1.37256789E1,5.10090685E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":505372075,"cost":2,"distance":41},"geometry":{"type":"LineString","coordinates":[[1.37256789E1,5.10090685E1],[1.3725181E1,5.10092599E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":318372932,"cost":8,"distance":136},"geometry":{"type":"LineString","coordinates":[[1.3725181E1,5.10092599E1],[1.37248222E1,5.10093998E1],[1.37241306E1,5.10097037E1],[1.37235956E1,5.10099611E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":10682579,"cost":10,"distance":159},"geometry":{"type":"LineString","coordinates":[[1.37235956E1,5.10099611E1],[1.37231133E1,5.10102182E1],[1.37227302E1,5.10104277E1],[1.3722152E1,5.10107639E1],[1.37219044E1,5.10109094E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":318372935,"cost":13,"distance":219},"geometry":{"type":"LineString","coordinates":[[1.37219044E1,5.10109094E1],[1.3721322E1,5.10112584E1],[1.37201667E1,5.1011907E1],[1.37195582E1,5.10122109E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":10682549,"cost":4,"distance":67},"geometry":{"type":"LineString","coordinates":[[1.37195582E1,5.10122109E1],[1.37191778E1,5.10123852E1],[1.37189568E1,5.10124808E1],[1.37187802E1,5.10125572E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":8,"distance":127},"geometry":{"type":"LineString","coordinates":[[1.37187802E1,5.10125572E1],[1.3718396E1,5.10127141E1],[1.37180063E1,5.1012866E1],[1.3717246938023868E1,5.101313563118154E1]]}}]})", - extract_and_route("test/a17-barrier-access-yes.osm.pbf", from, to, - osr::car::parameters{}, osr::search_profile::kCar)); + osr::test::extract_route_to_feature( + "test/a17-barrier-access-yes.osm.pbf", from, to, + osr::car::parameters{}, osr::search_profile::kCar, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, rome_bus_highway_service) { @@ -168,8 +172,10 @@ TEST(routing, rome_bus_highway_service) { auto const to = osr::location{41.893381, 12.509254, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[1.2499583294345202E1,4.1898267708640205E1],[1.24996984E1,4.18981975E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":178469121,"cost":0,"distance":1},"geometry":{"type":"LineString","coordinates":[[1.24996984E1,4.18981975E1],[1.24997079E1,4.1898192E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":779566386,"cost":1,"distance":13},"geometry":{"type":"LineString","coordinates":[[1.24997079E1,4.1898192E1],[1.24997354E1,4.18981718E1],[1.24997634E1,4.18981526E1],[1.24997908E1,4.18981316E1],[1.24998169E1,4.18981104E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":108571049,"cost":1,"distance":17},"geometry":{"type":"LineString","coordinates":[[1.24998169E1,4.18981104E1],[1.24998609E1,4.18980917E1],[1.24998802E1,4.18980828E1],[1.24998985E1,4.18980731E1],[1.2499988E1,4.18980283E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":108571049,"cost":2,"distance":27},"geometry":{"type":"LineString","coordinates":[[1.2499988E1,4.18980283E1],[1.25000658E1,4.18979802E1],[1.25001825E1,4.18979098E1],[1.25002358E1,4.18978772E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":108571049,"cost":7,"distance":97},"geometry":{"type":"LineString","coordinates":[[1.25002358E1,4.18978772E1],[1.25004759E1,4.18977302E1],[1.250114E1,4.18973194E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":108571049,"cost":8,"distance":118},"geometry":{"type":"LineString","coordinates":[[1.250114E1,4.18973194E1],[1.25022394E1,4.18966473E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":108571049,"cost":0,"distance":5},"geometry":{"type":"LineString","coordinates":[[1.25022394E1,4.18966473E1],[1.25022909E1,4.18966163E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":108571049,"cost":8,"distance":110},"geometry":{"type":"LineString","coordinates":[[1.25022909E1,4.18966163E1],[1.2503157E1,4.1896087E1],[1.25033143E1,4.18959856E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":108571049,"cost":0,"distance":3},"geometry":{"type":"LineString","coordinates":[[1.25033143E1,4.18959856E1],[1.2503338E1,4.18959696E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":169787522,"cost":6,"distance":90},"geometry":{"type":"LineString","coordinates":[[1.2503338E1,4.18959696E1],[1.25035415E1,4.18958431E1],[1.25037819E1,4.18957071E1],[1.25039436E1,4.18956151E1],[1.2504202E1,4.18954714E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":169787522,"cost":2,"distance":34},"geometry":{"type":"LineString","coordinates":[[1.2504202E1,4.18954714E1],[1.25045271E1,4.18952903E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":169787522,"cost":2,"distance":33},"geometry":{"type":"LineString","coordinates":[[1.25045271E1,4.18952903E1],[1.25048483E1,4.18951093E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":169787522,"cost":2,"distance":25},"geometry":{"type":"LineString","coordinates":[[1.25048483E1,4.18951093E1],[1.25050911E1,4.18949781E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":169787522,"cost":2,"distance":25},"geometry":{"type":"LineString","coordinates":[[1.25050911E1,4.18949781E1],[1.25053286E1,4.18948431E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":169787522,"cost":6,"distance":87},"geometry":{"type":"LineString","coordinates":[[1.25053286E1,4.18948431E1],[1.25060572E1,4.18944366E1],[1.25061337E1,4.18943935E1],[1.25061656E1,4.18943746E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":169787522,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[1.25061656E1,4.18943746E1],[1.25062563E1,4.18943343E1],[1.25062818E1,4.1894324E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":131476998,"cost":0,"distance":6},"geometry":{"type":"LineString","coordinates":[[1.25062818E1,4.1894324E1],[1.25063202E1,4.1894308E1],[1.25063444E1,4.18943007E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":131476998,"cost":0,"distance":6},"geometry":{"type":"LineString","coordinates":[[1.25063444E1,4.18943007E1],[1.25063711E1,4.18942925E1],[1.25064095E1,4.18942807E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":131476998,"cost":7,"distance":101},"geometry":{"type":"LineString","coordinates":[[1.25064095E1,4.18942807E1],[1.25064528E1,4.1894266E1],[1.25066818E1,4.18941938E1],[1.25072008E1,4.18940218E1],[1.25075235E1,4.18939205E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":131476998,"cost":8,"distance":105},"geometry":{"type":"LineString","coordinates":[[1.25075235E1,4.18939205E1],[1.25086902E1,4.18935413E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":131476998,"cost":2,"distance":22},"geometry":{"type":"LineString","coordinates":[[1.25086902E1,4.18935413E1],[1.25089339E1,4.18934636E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":2,"distance":30},"geometry":{"type":"LineString","coordinates":[[1.25089339E1,4.18934636E1],[1.2509243277334367E1,4.1893362769664456E1]]}}]})", - extract_and_route("test/rome-piazza-vittorio-emanuele.osm.pbf", from, to, - osr::bus::parameters{}, osr::search_profile::kBus)); + osr::test::extract_route_to_feature( + "test/rome-piazza-vittorio-emanuele.osm.pbf", from, to, + osr::bus::parameters{}, osr::search_profile::kBus, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, switzerland_motorway_shortcut) { @@ -178,8 +184,10 @@ TEST(routing, switzerland_motorway_shortcut) { auto const to = osr::location{47.31075422405, 9.575005944968, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":10,"distance":284},"geometry":{"type":"LineString","coordinates":[[9.561901310876577E0,4.730307704630513E1],[9.5622401E0,4.73032964E1],[9.5631905E0,4.73039245E1],[9.5638819E0,4.73043897E1],[9.564557E0,4.73048612E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":345464997,"cost":3,"distance":89},"geometry":{"type":"LineString","coordinates":[[9.564557E0,4.73048612E1],[9.5649083E0,4.73051131E1],[9.5653714E0,4.73054411E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":345464989,"cost":25,"distance":706},"geometry":{"type":"LineString","coordinates":[[9.5653714E0,4.73054411E1],[9.5662414E0,4.73060697E1],[9.5670476E0,4.73066468E1],[9.5678211E0,4.73071787E1],[9.5683852E0,4.73075441E1],[9.5689533E0,4.7307885E1],[9.5697104E0,4.73083001E1],[9.5704498E0,4.73086789E1],[9.5713228E0,4.73090933E1],[9.5719851E0,4.73093937E1],[9.5724251E0,4.73095851E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":345464996,"cost":5,"distance":130},"geometry":{"type":"LineString","coordinates":[[9.5724251E0,4.73095851E1],[9.573867E0,4.73102321E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":4,"distance":104},"geometry":{"type":"LineString","coordinates":[[9.573867E0,4.73102321E1],[9.575011315268094E0,4.7310748756107586E1]]}}]})", - extract_and_route("test/switzerland-motorway-shortcut.osm.pbf", from, to, - osr::car::parameters{}, osr::search_profile::kCar)); + osr::test::extract_route_to_feature( + "test/switzerland-motorway-shortcut.osm.pbf", from, to, + osr::car::parameters{}, osr::search_profile::kCar, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } TEST(routing, ballwil_shortcut) { @@ -189,8 +197,10 @@ TEST(routing, ballwil_shortcut) { auto const to = osr::location{47.15347392750, 8.316863681682, osr::kNoLevel}; EXPECT_EQ( R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":12,"distance":104},"geometry":{"type":"LineString","coordinates":[[8.315623672478951E0,4.715570128880367E1],[8.315628E0,4.71556617E1],[8.3156385E0,4.71556178E1],[8.3156641E0,4.71555631E1],[8.3157083E0,4.71554997E1],[8.3158538E0,4.71552882E1],[8.3160479E0,4.71549502E1],[8.3161126E0,4.71548364E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":30743688,"cost":10,"distance":80},"geometry":{"type":"LineString","coordinates":[[8.3161126E0,4.71548364E1],[8.316494E0,4.71541641E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":30743688,"cost":6,"distance":51},"geometry":{"type":"LineString","coordinates":[[8.316494E0,4.71541641E1],[8.3167335E0,4.71537386E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":4,"distance":32},"geometry":{"type":"LineString","coordinates":[[8.3167335E0,4.71537386E1],[8.316879613965764E0,4.715347805943454E1]]}}]})", - extract_and_route("test/ballwill-shortcut.osm.pbf", from, to, - osr::car::parameters{}, osr::search_profile::kCar)); + osr::test::extract_route_to_feature( + "test/ballwill-shortcut.osm.pbf", from, to, osr::car::parameters{}, + osr::search_profile::kCar, osr::direction::kForward, + osr::routing_algorithm::kDijkstra)); } TEST(routing, bus_platform) { @@ -198,6 +208,9 @@ TEST(routing, bus_platform) { auto const from = osr::location{49.8755750, 8.6472192, osr::kNoLevel}; auto const to = osr::location{49.8761539, 8.6503118, osr::kNoLevel}; EXPECT_EQ( - R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":0,"distance":1},"geometry":{"type":"LineString","coordinates":[[8.647215893993957E0,4.987558480274741E1],[8.6472223E0,4.98755857E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":0,"distance":6},"geometry":{"type":"LineString","coordinates":[[8.6472223E0,4.98755857E1],[8.6473042E0,4.98755972E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.6473042E0,4.98755972E1],[8.6473987E0,4.98756104E1],[8.6474599E0,4.98756205E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":0,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.6474599E0,4.98756205E1],[8.647511E0,4.98756289E1],[8.6475641E0,4.98756376E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":1,"distance":17},"geometry":{"type":"LineString","coordinates":[[8.6475641E0,4.98756376E1],[8.6477018E0,4.98756603E1],[8.6477364E0,4.98756669E1],[8.6477912E0,4.98756773E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":1,"distance":15},"geometry":{"type":"LineString","coordinates":[[8.6477912E0,4.98756773E1],[8.6479897E0,4.98757129E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":1,"distance":15},"geometry":{"type":"LineString","coordinates":[[8.6479897E0,4.98757129E1],[8.6480341E0,4.9875721E1],[8.64819E0,4.98757429E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":1,"distance":12},"geometry":{"type":"LineString","coordinates":[[8.64819E0,4.98757429E1],[8.6483493E0,4.98757643E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":0,"distance":5},"geometry":{"type":"LineString","coordinates":[[8.6483493E0,4.98757643E1],[8.6484161E0,4.9875773E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[8.6484161E0,4.9875773E1],[8.648613E0,4.9875798E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":1,"distance":20},"geometry":{"type":"LineString","coordinates":[[8.648613E0,4.9875798E1],[8.6488875E0,4.9875833E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":0,"distance":4},"geometry":{"type":"LineString","coordinates":[[8.6488875E0,4.9875833E1],[8.648948E0,4.98758407E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":1,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.648948E0,4.98758407E1],[8.6490562E0,4.98758544E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551422,"cost":3,"distance":41},"geometry":{"type":"LineString","coordinates":[[8.6490562E0,4.98758544E1],[8.6495122E0,4.98759191E1],[8.6496122E0,4.9875934E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551422,"cost":1,"distance":10},"geometry":{"type":"LineString","coordinates":[[8.6496122E0,4.9875934E1],[8.6497483E0,4.9875948E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551417,"cost":1,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6497483E0,4.9875948E1],[8.6498691E0,4.9875976E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551417,"cost":1,"distance":17},"geometry":{"type":"LineString","coordinates":[[8.6498691E0,4.9875976E1],[8.6500904E0,4.98760396E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551418,"cost":1,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6500904E0,4.98760396E1],[8.6502086E0,4.98760764E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551418,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.6502086E0,4.98760764E1],[8.6502881E0,4.9876129E1],[8.6503107E0,4.98761538E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":0,"distance":0},"geometry":{"type":"LineString","coordinates":[[8.6503107E0,4.98761538E1],[8.650311050022971E0,4.98761541839459E1]]}}]})", extract_and_route( - "test/darmstadt-bismarckstr.osm.pbf", from, to, osr::bus::parameters{}, osr::search_profile::kBus)); + R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":0,"distance":1},"geometry":{"type":"LineString","coordinates":[[8.647215893993957E0,4.987558480274741E1],[8.6472223E0,4.98755857E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":0,"distance":6},"geometry":{"type":"LineString","coordinates":[[8.6472223E0,4.98755857E1],[8.6473042E0,4.98755972E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.6473042E0,4.98755972E1],[8.6473987E0,4.98756104E1],[8.6474599E0,4.98756205E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":0,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.6474599E0,4.98756205E1],[8.647511E0,4.98756289E1],[8.6475641E0,4.98756376E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551411,"cost":1,"distance":17},"geometry":{"type":"LineString","coordinates":[[8.6475641E0,4.98756376E1],[8.6477018E0,4.98756603E1],[8.6477364E0,4.98756669E1],[8.6477912E0,4.98756773E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":1,"distance":15},"geometry":{"type":"LineString","coordinates":[[8.6477912E0,4.98756773E1],[8.6479897E0,4.98757129E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":1,"distance":15},"geometry":{"type":"LineString","coordinates":[[8.6479897E0,4.98757129E1],[8.6480341E0,4.9875721E1],[8.64819E0,4.98757429E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":1,"distance":12},"geometry":{"type":"LineString","coordinates":[[8.64819E0,4.98757429E1],[8.6483493E0,4.98757643E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551413,"cost":0,"distance":5},"geometry":{"type":"LineString","coordinates":[[8.6483493E0,4.98757643E1],[8.6484161E0,4.9875773E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":1,"distance":14},"geometry":{"type":"LineString","coordinates":[[8.6484161E0,4.9875773E1],[8.648613E0,4.9875798E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":1,"distance":20},"geometry":{"type":"LineString","coordinates":[[8.648613E0,4.9875798E1],[8.6488875E0,4.9875833E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":0,"distance":4},"geometry":{"type":"LineString","coordinates":[[8.6488875E0,4.9875833E1],[8.648948E0,4.98758407E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551412,"cost":1,"distance":8},"geometry":{"type":"LineString","coordinates":[[8.648948E0,4.98758407E1],[8.6490562E0,4.98758544E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551422,"cost":3,"distance":41},"geometry":{"type":"LineString","coordinates":[[8.6490562E0,4.98758544E1],[8.6495122E0,4.98759191E1],[8.6496122E0,4.9875934E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551422,"cost":1,"distance":10},"geometry":{"type":"LineString","coordinates":[[8.6496122E0,4.9875934E1],[8.6497483E0,4.9875948E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551417,"cost":1,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6497483E0,4.9875948E1],[8.6498691E0,4.9875976E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551417,"cost":1,"distance":17},"geometry":{"type":"LineString","coordinates":[[8.6498691E0,4.9875976E1],[8.6500904E0,4.98760396E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551418,"cost":1,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6500904E0,4.98760396E1],[8.6502086E0,4.98760764E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551418,"cost":1,"distance":11},"geometry":{"type":"LineString","coordinates":[[8.6502086E0,4.98760764E1],[8.6502881E0,4.9876129E1],[8.6503107E0,4.98761538E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":0,"distance":0},"geometry":{"type":"LineString","coordinates":[[8.6503107E0,4.98761538E1],[8.650311050022971E0,4.98761541839459E1]]}}]})", + osr::test::extract_route_to_feature( + "test/darmstadt-bismarckstr.osm.pbf", from, to, + osr::bus::parameters{}, osr::search_profile::kBus, + osr::direction::kForward, osr::routing_algorithm::kDijkstra)); } diff --git a/test/sliding_window_test.cc b/test/sliding_window_test.cc new file mode 100644 index 00000000..f0fc38e0 --- /dev/null +++ b/test/sliding_window_test.cc @@ -0,0 +1,147 @@ +#include "gtest/gtest.h" +#include +#include +#include "osr/util/sliding_window.h" + +#include "utl/enumerate.h" + +TEST(sliding_window, basic1) { + std::vector v(5); + std::iota(v.begin(), v.end(), 0); + + // Radius 1: width 3. + // Windows centered at 0, 1, 2, 3, 4. + // [null, 0, 1] + // [0, 1, 2] + // [1, 2, 3] + // [2, 3, 4] + // [3, 4, null] + + auto count = 0; + + const auto window_range = osr::sliding_window<1>(v); + for (const auto& [i, w] : utl::enumerate(window_range)) { + if (i == 0U) { + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(w[-1], v.end()); + EXPECT_EQ(*w.focus(), 0); + EXPECT_EQ(*w[1], 1); + EXPECT_EQ(w[2], v.end()); + } else if (i == 1U) { + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(*w[-1], 0); + EXPECT_EQ(*w.focus(), 1); + EXPECT_EQ(*w[1], 2); + EXPECT_EQ(w[2], v.end()); + } else if (i == 2U) { + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(*w[-1], 1); + EXPECT_EQ(*w.focus(), 2); + EXPECT_EQ(*w[1], 3); + EXPECT_EQ(w[2], v.end()); + } else if (i == 3U) { + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(*w[-1], 2); + EXPECT_EQ(*w.focus(), 3); + EXPECT_EQ(*w[1], 4); + EXPECT_EQ(w[2], v.end()); + } else if (i == 4U) { + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(*w[-1], 3); + EXPECT_EQ(*w.focus(), 4); + EXPECT_EQ(w[1], v.end()); + EXPECT_EQ(w[2], v.end()); + } + count++; + } + EXPECT_EQ(count, 5); +} + +TEST(sliding_window, empty) { + std::vector v; + + const auto window_range = osr::sliding_window<1>(v); + EXPECT_EQ(window_range.begin(), window_range.end()); +} + +TEST(sliding_window, singleton) { + std::vector v = { 1 }; + const auto window_range = osr::sliding_window<1>(v); + auto iter = window_range.begin(); + + EXPECT_NE(iter, window_range.end()); + const auto w = *iter; + + // Radius 1: width 3. + // Expected [null, 1, null] + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(w[-1], v.end()); + EXPECT_EQ(*w.focus(), 1); + EXPECT_EQ(w[1], v.end()); + EXPECT_EQ(w[2], v.end()); + + ++iter; + EXPECT_EQ(iter, window_range.end()); +} + +TEST(sliding_window, basic2) { + std::vector v(5); + std::iota(v.begin(), v.end(), 0); + std::reverse(v.begin(), v.end()); + // Radius 2: width 5. + // Windows centered at 4, 3, 2, 1, 0. + // [null, null, 4, 3, 2] + // [null, 4, 3, 2, 1] + // [4, 3, 2, 1, 0] + // [3, 2, 1, 0, null] + // [2, 1, 0, null, null] + + auto count = 0; + + const auto window_range = osr::sliding_window<2>(v); + for (const auto& [i, w] : utl::enumerate(window_range)) { + if (i == 0U) { + EXPECT_EQ(w[-3], v.end()); + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(w[-1], v.end()); + EXPECT_EQ(*w.focus(), 4); + EXPECT_EQ(*w[1], 3); + EXPECT_EQ(*w[2], 2); + EXPECT_EQ(w[3], v.end()); + } else if (i == 1U) { + EXPECT_EQ(w[-3], v.end()); + EXPECT_EQ(w[-2], v.end()); + EXPECT_EQ(*w[-1], 4); + EXPECT_EQ(*w.focus(), 3); + EXPECT_EQ(*w[1], 2); + EXPECT_EQ(*w[2], 1); + EXPECT_EQ(w[3], v.end()); + } else if (i == 2U) { + EXPECT_EQ(w[-3], v.end()); + EXPECT_EQ(*w[-2], 4); + EXPECT_EQ(*w[-1], 3); + EXPECT_EQ(*w.focus(), 2); + EXPECT_EQ(*w[1], 1); + EXPECT_EQ(*w[2], 0); + EXPECT_EQ(w[3], v.end()); + } else if (i == 3U) { + EXPECT_EQ(w[-3], v.end()); + EXPECT_EQ(*w[-2], 3); + EXPECT_EQ(*w[-1], 2); + EXPECT_EQ(*w.focus(), 1); + EXPECT_EQ(*w[1], 0); + EXPECT_EQ(w[2], v.end()); + EXPECT_EQ(w[3], v.end()); + } else if (i == 4U) { + EXPECT_EQ(w[-3], v.end()); + EXPECT_EQ(*w[-2], 2); + EXPECT_EQ(*w[-1], 1); + EXPECT_EQ(*w.focus(), 0); + EXPECT_EQ(w[1], v.end()); + EXPECT_EQ(w[2], v.end()); + EXPECT_EQ(w[3], v.end()); + } + count++; + } + EXPECT_EQ(count, 5); +} diff --git a/test/spessartring-fiedlerweg-darmstadt-pbf.osm.pbf b/test/spessartring-fiedlerweg-darmstadt-pbf.osm.pbf new file mode 100644 index 00000000..152ed6f2 Binary files /dev/null and b/test/spessartring-fiedlerweg-darmstadt-pbf.osm.pbf differ diff --git a/web/index.html b/web/index.html index 134b6c5f..3b775b93 100644 --- a/web/index.html +++ b/web/index.html @@ -12,8 +12,8 @@ -G -
- - - - -
- Test +
+ G + +
+ + + + +
+ Test +
+
+
+ + + -
\ No newline at end of file