From 54ceb0542017ec92b9f388a53a7395004b4c357f Mon Sep 17 00:00:00 2001 From: "Daniel J. Hofmann" Date: Thu, 6 Apr 2017 14:28:43 +0200 Subject: [PATCH] Implements Alternatives for MLD --- CHANGELOG.md | 12 +- docs/http.md | 6 +- docs/profiles.md | 20 +- include/engine/algorithm.hpp | 3 + include/engine/api/route_parameters.hpp | 17 +- include/engine/engine.hpp | 12 +- include/engine/engine_config.hpp | 1 + include/engine/internal_route_result.hpp | 12 + include/engine/plugins/viaroute.hpp | 3 +- include/engine/routing_algorithms.hpp | 27 +- .../routing_algorithms/alternative_path.hpp | 18 +- .../routing_algorithms/routing_base.hpp | 38 +- .../routing_algorithms/routing_base_mld.hpp | 132 ++- include/extractor/profile_properties.hpp | 2 +- include/nodejs/node_osrm_support.hpp | 17 +- .../server/api/route_parameters_grammar.hpp | 8 +- include/util/static_assert.hpp | 26 + profiles/car.lua | 14 +- src/engine/engine_config.cpp | 3 +- src/engine/plugins/viaroute.cpp | 26 +- ...ative_path.cpp => alternative_path_ch.cpp} | 186 ++-- .../alternative_path_mld.cpp | 936 ++++++++++++++++++ .../direct_shortest_path.cpp | 33 - .../routing_algorithms/routing_base.cpp | 10 + src/nodejs/node_osrm.cpp | 5 +- src/tools/routed.cpp | 22 +- test/nodejs/route.js | 10 +- unit_tests/server/parameters_parser.cpp | 43 + 28 files changed, 1404 insertions(+), 238 deletions(-) create mode 100644 include/util/static_assert.hpp rename src/engine/routing_algorithms/{alternative_path.cpp => alternative_path_ch.cpp} (86%) create mode 100644 src/engine/routing_algorithms/alternative_path_mld.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 42db08626..90b476de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,19 @@ - Algorithm: - Multi-Level Dijkstra: - Plugins supported: `table` + - Adds alternative routes support (see [#4047](https://github.com/Project-OSRM/osrm-backend/pull/4047) and [3905](https://github.com/Project-OSRM/osrm-backend/issues/3905)): provides reasonably looking alternative routes (many, if possible) with reasonable query times. - API: + - Exposes `alternatives=Number` parameter overload in addition to the boolean flag. - Support for exits numbers and names. New member `exits` in `RouteStep`, based on `junction:ref` on ways - `RouteStep` now has new parameter `classes` that can be set in the profile on each way. - Profiles: - - `result.exits` allows you to set a way's exit numbers and names, see [`junction:ref`](http://wiki.openstreetmap.org/wiki/Proposed_features/junction_details) - - `ExtractionWay` now as new property `forward_classes` and `backward_classes` that can set in the `way_function`. - The maximum number of classes is 8. + - `result.exits` allows you to set a way's exit numbers and names, see [`junction:ref`](http://wiki.openstreetmap.org/wiki/Proposed_features/junction_details) + - `ExtractionWay` now as new property `forward_classes` and `backward_classes` that can set in the `way_function`. + The maximum number of classes is 8. + - Node.js Bindings: + - Exposes `alternatives=Number` parameter overload in addition to the boolean flag + - Tools: + - Exposes engine limit on number of alternatives to generate `--max-alternatives` in `osrm-routed` (3 by default) # 5.8.0 - Changes from 5.7 diff --git a/docs/http.md b/docs/http.md index 77eeb46ad..344f0edca 100644 --- a/docs/http.md +++ b/docs/http.md @@ -165,21 +165,21 @@ curl 'http://router.project-osrm.org/nearest/v1/driving/13.388860,52.517037?numb Finds the fastest route between coordinates in the supplied order. ```endpoint -GET /route/v1/{profile}/{coordinates}?alternatives={true|false}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false} +GET /route/v1/{profile}/{coordinates}?alternatives={true|false|number}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false} ``` In addition to the [general options](#general-options) the following options are supported for this service: |Option |Values |Description | |------------|---------------------------------------------|-------------------------------------------------------------------------------| -|alternatives|`true`, `false` (default) |Search for alternative routes and return as well.\* | +|alternatives|`true`, `false` (default), or Number |Search for alternative routes. Passing a number `alternatives=n` searches for up to `n` alternative routes.\* | |steps |`true`, `false` (default) |Returned route steps for each route leg | |annotations |`true`, `false` (default), `nodes`, `distance`, `duration`, `datasources`, `weight`, `speed` |Returns additional metadata for each coordinate along the route geometry. | |geometries |`polyline` (default), `polyline6`, `geojson` |Returned route geometry format (influences overview and per step) | |overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| |continue\_straight |`default` (default), `true`, `false` |Forces the route to keep going straight at waypoints constraining uturns there even if it would be faster. Default value depends on the profile. | -\* Please note that even if an alternative route is requested, a result cannot be guaranteed. +\* Please note that even if alternative routes are requested, a result cannot be guaranteed. **Response** diff --git a/docs/profiles.md b/docs/profiles.md index 4f544b700..a2c32034a 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -27,16 +27,16 @@ As you scroll down the file you'll see local variables, and then local functions The following global properties can be set in your profile: -Attribute | Type | Notes -------------------------------|----------|---------------------------------------------------------------------------- -weight_name | String | Name used in output for the routing weight property (default `'duration'`) -weight_precision | Unsigned | Decimal precision of edge weights (default `1`) -left_hand_driving | Boolean | Are vehicles assumed to drive on the left? (used in guidance, default `false`) -use_turn_restrictions | Boolean | Are turn instructions followed? (default `false`) -continue_straight_at_waypoint | Boolean | Must the route continue straight on at a via point, or are U-turns allowed? (default `true`) -max_speed_for_map_matching | Float | Maximum vehicle speed to be assumed in matching (in m/s) -max_turn_weight | Float | Maximum turn penalty weight -force_split_edges | Boolean | True value forces a split of forward and backward edges of extracted ways and guarantees that `segment_function` will be called for all segments (default `false`) +Attribute | Type | Notes +-------------------------------------|----------|---------------------------------------------------------------------------- +weight_name | String | Name used in output for the routing weight property (default `'duration'`) +weight_precision | Unsigned | Decimal precision of edge weights (default `1`) +left_hand_driving | Boolean | Are vehicles assumed to drive on the left? (used in guidance, default `false`) +use_turn_restrictions | Boolean | Are turn instructions followed? (default `false`) +continue_straight_at_waypoint | Boolean | Must the route continue straight on at a via point, or are U-turns allowed? (default `true`) +max_speed_for_map_matching | Float | Maximum vehicle speed to be assumed in matching (in m/s) +max_turn_weight | Float | Maximum turn penalty weight +force_split_edges | Boolean | True value forces a split of forward and backward edges of extracted ways and guarantees that `segment_function` will be called for all segments (default `false`) ## way_function diff --git a/include/engine/algorithm.hpp b/include/engine/algorithm.hpp index b7a2f47e6..6c3e9afda 100644 --- a/include/engine/algorithm.hpp +++ b/include/engine/algorithm.hpp @@ -93,6 +93,9 @@ template <> struct HasGetTileTurns final : std::true_type }; // Algorithms supported by Multi-Level Dijkstra +template <> struct HasAlternativePathSearch final : std::true_type +{ +}; template <> struct HasDirectShortestPathSearch final : std::true_type { }; diff --git a/include/engine/api/route_parameters.hpp b/include/engine/api/route_parameters.hpp index ef9719940..9b2003124 100644 --- a/include/engine/api/route_parameters.hpp +++ b/include/engine/api/route_parameters.hpp @@ -89,8 +89,9 @@ struct RouteParameters : public BaseParameters const boost::optional continue_straight_, Args... args_) : BaseParameters{std::forward(args_)...}, steps{steps_}, alternatives{alternatives_}, - annotations{false}, annotations_type{AnnotationsType::None}, geometries{geometries_}, - overview{overview_}, continue_straight{continue_straight_} + number_of_alternatives{alternatives_ ? 1u : 0u}, annotations{false}, + annotations_type{AnnotationsType::None}, geometries{geometries_}, overview{overview_}, + continue_straight{continue_straight_} // Once we perfectly-forward `args` (see #2990) this constructor can delegate to the one below. { } @@ -105,7 +106,7 @@ struct RouteParameters : public BaseParameters const boost::optional continue_straight_, Args... args_) : BaseParameters{std::forward(args_)...}, steps{steps_}, alternatives{alternatives_}, - annotations{annotations_}, + number_of_alternatives{alternatives_ ? 1u : 0u}, annotations{annotations_}, annotations_type{annotations_ ? AnnotationsType::All : AnnotationsType::None}, geometries{geometries_}, overview{overview_}, continue_straight{continue_straight_} { @@ -121,6 +122,7 @@ struct RouteParameters : public BaseParameters const boost::optional continue_straight_, Args... args_) : BaseParameters{std::forward(args_)...}, steps{steps_}, alternatives{alternatives_}, + number_of_alternatives{alternatives_ ? 1u : 0u}, annotations{annotations_ == AnnotationsType::None ? false : true}, annotations_type{annotations_}, geometries{geometries_}, overview{overview_}, continue_straight{continue_straight_} @@ -128,14 +130,21 @@ struct RouteParameters : public BaseParameters } bool steps = false; + // TODO: in v6 we should remove the boolean and only keep the number parameter; for compat. bool alternatives = false; + unsigned number_of_alternatives = 0; bool annotations = false; AnnotationsType annotations_type = AnnotationsType::None; GeometriesType geometries = GeometriesType::Polyline; OverviewType overview = OverviewType::Simplified; boost::optional continue_straight; - bool IsValid() const { return coordinates.size() >= 2 && BaseParameters::IsValid(); } + bool IsValid() const + { + const auto coordinates_ok = coordinates.size() >= 2; + const auto base_params_ok = BaseParameters::IsValid(); + return coordinates_ok && base_params_ok; + } }; inline bool operator&(RouteParameters::AnnotationsType lhs, RouteParameters::AnnotationsType rhs) diff --git a/include/engine/engine.hpp b/include/engine/engine.hpp index d3e4907ed..cdb4b598e 100644 --- a/include/engine/engine.hpp +++ b/include/engine/engine.hpp @@ -53,12 +53,12 @@ template class Engine final : public EngineInterface { public: explicit Engine(const EngineConfig &config) - : route_plugin(config.max_locations_viaroute), // - table_plugin(config.max_locations_distance_table), // - nearest_plugin(config.max_results_nearest), // - trip_plugin(config.max_locations_trip), // - match_plugin(config.max_locations_map_matching), // - tile_plugin() // + : route_plugin(config.max_locations_viaroute, config.max_alternatives), // + table_plugin(config.max_locations_distance_table), // + nearest_plugin(config.max_results_nearest), // + trip_plugin(config.max_locations_trip), // + match_plugin(config.max_locations_map_matching), // + tile_plugin() // { if (config.use_shared_memory) diff --git a/include/engine/engine_config.hpp b/include/engine/engine_config.hpp index e2e8d8ae8..2459b7ee5 100644 --- a/include/engine/engine_config.hpp +++ b/include/engine/engine_config.hpp @@ -87,6 +87,7 @@ struct EngineConfig final int max_locations_distance_table = -1; int max_locations_map_matching = -1; int max_results_nearest = -1; + int max_alternatives = 3; // set an arbitrary upper bound; can be adjusted by user bool use_shared_memory = true; Algorithm algorithm = Algorithm::CH; }; diff --git a/include/engine/internal_route_result.hpp b/include/engine/internal_route_result.hpp index 17303956a..395a9a1f9 100644 --- a/include/engine/internal_route_result.hpp +++ b/include/engine/internal_route_result.hpp @@ -61,6 +61,18 @@ struct InternalRouteResult { return (leg != unpacked_path_segments.size() - 1); } + + // Note: includes duration for turns, except for at start and end node. + EdgeWeight duration() const + { + EdgeWeight ret{0}; + + for (const auto &leg : unpacked_path_segments) + for (const auto &segment : leg) + ret += segment.duration_until_turn; + + return ret; + } }; struct InternalManyRoutesResult diff --git a/include/engine/plugins/viaroute.hpp b/include/engine/plugins/viaroute.hpp index ab194b485..1e1234dea 100644 --- a/include/engine/plugins/viaroute.hpp +++ b/include/engine/plugins/viaroute.hpp @@ -27,9 +27,10 @@ class ViaRoutePlugin final : public BasePlugin { private: const int max_locations_viaroute; + const int max_alternatives; public: - explicit ViaRoutePlugin(int max_locations_viaroute); + explicit ViaRoutePlugin(int max_locations_viaroute, int max_alternatives); Status HandleRequest(const datafacade::ContiguousInternalMemoryDataFacadeBase &facade, const RoutingAlgorithmsInterface &algorithms, diff --git a/include/engine/routing_algorithms.hpp b/include/engine/routing_algorithms.hpp index e964028cf..bd1a44546 100644 --- a/include/engine/routing_algorithms.hpp +++ b/include/engine/routing_algorithms.hpp @@ -20,7 +20,8 @@ class RoutingAlgorithmsInterface { public: virtual InternalManyRoutesResult - AlternativePathSearch(const PhantomNodes &phantom_node_pair) const = 0; + AlternativePathSearch(const PhantomNodes &phantom_node_pair, + unsigned number_of_alternatives) const = 0; virtual InternalRouteResult ShortestPathSearch(const std::vector &phantom_node_pair, @@ -66,7 +67,8 @@ template class RoutingAlgorithms final : public RoutingAlgo virtual ~RoutingAlgorithms() = default; InternalManyRoutesResult - AlternativePathSearch(const PhantomNodes &phantom_node_pair) const final override; + AlternativePathSearch(const PhantomNodes &phantom_node_pair, + unsigned number_of_alternatives) const final override; InternalRouteResult ShortestPathSearch( const std::vector &phantom_node_pair, @@ -130,9 +132,11 @@ template class RoutingAlgorithms final : public RoutingAlgo template InternalManyRoutesResult -RoutingAlgorithms::AlternativePathSearch(const PhantomNodes &phantom_node_pair) const +RoutingAlgorithms::AlternativePathSearch(const PhantomNodes &phantom_node_pair, + unsigned number_of_alternatives) const { - return routing_algorithms::ch::alternativePathSearch(heaps, facade, phantom_node_pair); + return routing_algorithms::alternativePathSearch( + heaps, facade, phantom_node_pair, number_of_alternatives); } template @@ -189,7 +193,8 @@ inline std::vector RoutingAlgorithms::G // CoreCH overrides template <> InternalManyRoutesResult inline RoutingAlgorithms< - routing_algorithms::corech::Algorithm>::AlternativePathSearch(const PhantomNodes &) const + routing_algorithms::corech::Algorithm>::AlternativePathSearch(const PhantomNodes &, + unsigned) const { throw util::exception("AlternativePathSearch is disabled due to performance reasons"); } @@ -203,15 +208,7 @@ RoutingAlgorithms::ManyToManySearch( { throw util::exception("ManyToManySearch is disabled due to performance reasons"); } - -// MLD overrides for not implemented -template <> -InternalManyRoutesResult inline RoutingAlgorithms< - routing_algorithms::mld::Algorithm>::AlternativePathSearch(const PhantomNodes &) const -{ - throw util::exception("AlternativePathSearch is not implemented"); -} -} -} +} // ns engine +} // ns osrm #endif diff --git a/include/engine/routing_algorithms/alternative_path.hpp b/include/engine/routing_algorithms/alternative_path.hpp index 016c46621..dff8d25c7 100644 --- a/include/engine/routing_algorithms/alternative_path.hpp +++ b/include/engine/routing_algorithms/alternative_path.hpp @@ -15,13 +15,19 @@ namespace engine { namespace routing_algorithms { -namespace ch -{ + InternalManyRoutesResult -alternativePathSearch(SearchEngineData &search_engine_data, - const datafacade::ContiguousInternalMemoryDataFacade &facade, - const PhantomNodes &phantom_node_pair); -} // namespace ch +alternativePathSearch(SearchEngineData &search_engine_data, + const datafacade::ContiguousInternalMemoryDataFacade &facade, + const PhantomNodes &phantom_node_pair, + unsigned number_of_alternatives); + +InternalManyRoutesResult +alternativePathSearch(SearchEngineData &search_engine_data, + const datafacade::ContiguousInternalMemoryDataFacade &facade, + const PhantomNodes &phantom_node_pair, + unsigned number_of_alternatives); + } // namespace routing_algorithms } // namespace engine } // namespace osrm diff --git a/include/engine/routing_algorithms/routing_base.hpp b/include/engine/routing_algorithms/routing_base.hpp index 75315a4c3..17fde0205 100644 --- a/include/engine/routing_algorithms/routing_base.hpp +++ b/include/engine/routing_algorithms/routing_base.hpp @@ -6,6 +6,7 @@ #include "engine/algorithm.hpp" #include "engine/datafacade/contiguous_internalmem_datafacade.hpp" #include "engine/internal_route_result.hpp" +#include "engine/phantom_node.hpp" #include "engine/search_engine_data.hpp" #include "util/coordinate_calculation.hpp" @@ -38,9 +39,11 @@ static constexpr bool REVERSE_DIRECTION = false; static constexpr bool DO_NOT_FORCE_LOOPS = false; bool needsLoopForward(const PhantomNode &source_phantom, const PhantomNode &target_phantom); - bool needsLoopBackwards(const PhantomNode &source_phantom, const PhantomNode &target_phantom); +bool needsLoopForward(const PhantomNodes &phantoms); +bool needsLoopBackwards(const PhantomNodes &phantoms); + template void insertNodesInHeaps(Heap &forward_heap, Heap &reverse_heap, const PhantomNodes &nodes) { @@ -369,6 +372,39 @@ double getPathDistance(const datafacade::ContiguousInternalMemoryDataFacade +InternalRouteResult +extractRoute(const datafacade::ContiguousInternalMemoryDataFacade &facade, + const EdgeWeight weight, + const PhantomNodes &phantom_nodes, + const std::vector &unpacked_nodes, + const std::vector &unpacked_edges) +{ + InternalRouteResult raw_route_data; + raw_route_data.segment_end_coordinates = {phantom_nodes}; + + // No path found for both target nodes? + if (INVALID_EDGE_WEIGHT == weight) + { + return raw_route_data; + } + + raw_route_data.shortest_path_weight = weight; + raw_route_data.unpacked_path_segments.resize(1); + raw_route_data.source_traversed_in_reverse.push_back( + (unpacked_nodes.front() != phantom_nodes.source_phantom.forward_segment_id.id)); + raw_route_data.target_traversed_in_reverse.push_back( + (unpacked_nodes.back() != phantom_nodes.target_phantom.forward_segment_id.id)); + + annotatePath(facade, + phantom_nodes, + unpacked_nodes, + unpacked_edges, + raw_route_data.unpacked_path_segments.front()); + + return raw_route_data; +} + } // namespace routing_algorithms } // namespace engine } // namespace osrm diff --git a/include/engine/routing_algorithms/routing_base_mld.hpp b/include/engine/routing_algorithms/routing_base_mld.hpp index 340924f38..22192f0a7 100644 --- a/include/engine/routing_algorithms/routing_base_mld.hpp +++ b/include/engine/routing_algorithms/routing_base_mld.hpp @@ -10,6 +10,12 @@ #include +#include +#include +#include +#include +#include + namespace osrm { namespace engine @@ -24,7 +30,7 @@ namespace // Unrestricted search (Args is const PhantomNodes &): // * use partition.GetQueryLevel to find the node query level based on source and target phantoms // * allow to traverse all cells -inline LevelID getNodeQureyLevel(const partition::MultiLevelPartitionView &partition, +inline LevelID getNodeQueryLevel(const partition::MultiLevelPartitionView &partition, NodeID node, const PhantomNodes &phantom_nodes) { @@ -49,7 +55,7 @@ inline bool checkParentCellRestriction(CellID, const PhantomNodes &) { return tr // * use the fixed level for queries // * check if the node cell is the same as the specified parent onr inline LevelID -getNodeQureyLevel(const partition::MultiLevelPartitionView &, NodeID, LevelID level, CellID) +getNodeQueryLevel(const partition::MultiLevelPartitionView &, NodeID, LevelID level, CellID) { return level; } @@ -60,6 +66,70 @@ inline bool checkParentCellRestriction(CellID cell, LevelID, CellID parent) } } +// Heaps only record for each node its predecessor ("parent") on the shortest path. +// For re-constructing the actual path we need to trace back all parent "pointers". +// In contrast to the CH code MLD needs to know the edges (with clique arc property). + +using PackedEdge = std::tuple; +using PackedPath = std::vector; + +template +inline void retrievePackedPathFromSingleHeap(const SearchEngineData::QueryHeap &heap, + const NodeID middle, + OutIter out) +{ + NodeID current = middle; + NodeID parent = heap.GetData(current).parent; + + while (current != parent) + { + const auto &data = heap.GetData(current); + + if (DIRECTION == FORWARD_DIRECTION) + { + *out = std::make_tuple(parent, current, data.from_clique_arc); + ++out; + } + else if (DIRECTION == REVERSE_DIRECTION) + { + *out = std::make_tuple(current, parent, data.from_clique_arc); + ++out; + } + + current = parent; + parent = heap.GetData(parent).parent; + } +} + +template +inline PackedPath +retrievePackedPathFromSingleHeap(const SearchEngineData::QueryHeap &heap, + const NodeID middle) +{ + PackedPath packed_path; + retrievePackedPathFromSingleHeap(heap, middle, std::back_inserter(packed_path)); + return packed_path; +} + +// Trace path from middle to start in the forward search space (in reverse) +// and from middle to end in the reverse search space. Middle connects paths. + +inline PackedPath +retrievePackedPathFromHeap(const SearchEngineData::QueryHeap &forward_heap, + const SearchEngineData::QueryHeap &reverse_heap, + const NodeID middle) +{ + // Retrieve start -> middle. Is in reverse order since tracing back starts from middle. + auto packed_path = retrievePackedPathFromSingleHeap(forward_heap, middle); + std::reverse(begin(packed_path), end(packed_path)); + + // Retrieve middle -> end. Is already in correct order, tracing starts from middle. + auto into = std::back_inserter(packed_path); + retrievePackedPathFromSingleHeap(reverse_heap, middle, into); + + return packed_path; +} + template void routingStep(const datafacade::ContiguousInternalMemoryDataFacade &facade, SearchEngineData::QueryHeap &forward_heap, @@ -96,7 +166,7 @@ void routingStep(const datafacade::ContiguousInternalMemoryDataFacade } } - const auto level = getNodeQureyLevel(partition, node, args...); + const auto level = getNodeQueryLevel(partition, node, args...); if (level >= 1 && !forward_heap.GetData(node).from_clique_arc) { @@ -112,6 +182,7 @@ void routingStep(const datafacade::ContiguousInternalMemoryDataFacade if (shortcut_weight != INVALID_EDGE_WEIGHT && node != to) { const EdgeWeight to_weight = weight + shortcut_weight; + BOOST_ASSERT(to_weight >= weight); if (!forward_heap.WasInserted(to)) { forward_heap.Insert(to, to_weight, {node, true}); @@ -137,6 +208,7 @@ void routingStep(const datafacade::ContiguousInternalMemoryDataFacade if (shortcut_weight != INVALID_EDGE_WEIGHT && node != to) { const EdgeWeight to_weight = weight + shortcut_weight; + BOOST_ASSERT(to_weight >= weight); if (!forward_heap.WasInserted(to)) { forward_heap.Insert(to, to_weight, {node, true}); @@ -179,16 +251,24 @@ void routingStep(const datafacade::ContiguousInternalMemoryDataFacade } } +// With (s, middle, t) we trace back the paths middle -> s and middle -> t. +// This gives us a packed path (node ids) from the base graph around s and t, +// and overlay node ids otherwise. We then have to unpack the overlay clique +// edges by recursively descending unpacking the path down to the base graph. + +using UnpackedNodes = std::vector; +using UnpackedEdges = std::vector; +using UnpackedPath = std::tuple; + template -std::tuple, std::vector> -search(SearchEngineData &engine_working_data, - const datafacade::ContiguousInternalMemoryDataFacade &facade, - SearchEngineData::QueryHeap &forward_heap, - SearchEngineData::QueryHeap &reverse_heap, - const bool force_loop_forward, - const bool force_loop_reverse, - EdgeWeight weight_upper_bound, - Args... args) +UnpackedPath search(SearchEngineData &engine_working_data, + const datafacade::ContiguousInternalMemoryDataFacade &facade, + SearchEngineData::QueryHeap &forward_heap, + SearchEngineData::QueryHeap &reverse_heap, + const bool force_loop_forward, + const bool force_loop_reverse, + EdgeWeight weight_upper_bound, + Args... args) { if (forward_heap.Empty() || reverse_heap.Empty()) { @@ -242,27 +322,12 @@ search(SearchEngineData &engine_working_data, return std::make_tuple(INVALID_EDGE_WEIGHT, std::vector(), std::vector()); } - // Get packed path as edges {from node ID, to node ID, edge ID} - std::vector> packed_path; - NodeID current_node = middle, parent_node = forward_heap.GetData(middle).parent; - while (parent_node != current_node) - { - const auto &data = forward_heap.GetData(current_node); - packed_path.push_back(std::make_tuple(parent_node, current_node, data.from_clique_arc)); - current_node = parent_node; - parent_node = forward_heap.GetData(parent_node).parent; - } - std::reverse(std::begin(packed_path), std::end(packed_path)); - const NodeID source_node = current_node; + // Get packed path as edges {from node ID, to node ID, from_clique_arc} + auto packed_path = retrievePackedPathFromHeap(forward_heap, reverse_heap, middle); - current_node = middle, parent_node = reverse_heap.GetData(middle).parent; - while (parent_node != current_node) - { - const auto &data = reverse_heap.GetData(current_node); - packed_path.push_back(std::make_tuple(current_node, parent_node, data.from_clique_arc)); - current_node = parent_node; - parent_node = reverse_heap.GetData(parent_node).parent; - } + // Beware the edge case when start, middle, end are all the same. + // In this case we return a single node, no edges. We also don't unpack. + const NodeID source_node = !packed_path.empty() ? std::get<0>(packed_path.front()) : middle; // Unpack path std::vector unpacked_nodes; @@ -271,6 +336,7 @@ search(SearchEngineData &engine_working_data, unpacked_edges.reserve(packed_path.size()); unpacked_nodes.push_back(source_node); + for (auto const &packed_edge : packed_path) { NodeID source, target; @@ -283,7 +349,7 @@ search(SearchEngineData &engine_working_data, } else { // an overlay graph edge - LevelID level = getNodeQureyLevel(partition, source, args...); + LevelID level = getNodeQueryLevel(partition, source, args...); CellID parent_cell_id = partition.GetCell(level, source); BOOST_ASSERT(parent_cell_id == partition.GetCell(level, target)); diff --git a/include/extractor/profile_properties.hpp b/include/extractor/profile_properties.hpp index cb528c92a..fefec4749 100644 --- a/include/extractor/profile_properties.hpp +++ b/include/extractor/profile_properties.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace osrm { @@ -109,7 +110,6 @@ struct ProfileProperties std::array class_names; unsigned weight_precision = 1; bool force_split_edges = false; - bool call_tagless_node_function = true; }; } diff --git a/include/nodejs/node_osrm_support.hpp b/include/nodejs/node_osrm_support.hpp index dd67958ec..da38af424 100644 --- a/include/nodejs/node_osrm_support.hpp +++ b/include/nodejs/node_osrm_support.hpp @@ -703,7 +703,7 @@ argumentsToRouteParameter(const Nan::FunctionCallbackInfo &args, if (!value->IsBoolean() && !value->IsNull()) { - Nan::ThrowError("'continue_straight' parama must be boolean or null"); + Nan::ThrowError("'continue_straight' param must be boolean or null"); return route_parameters_ptr(); } if (value->IsBoolean()) @@ -718,12 +718,21 @@ argumentsToRouteParameter(const Nan::FunctionCallbackInfo &args, if (value.IsEmpty()) return route_parameters_ptr(); - if (!value->IsBoolean()) + if (value->IsBoolean()) { - Nan::ThrowError("'alternatives' parama must be boolean"); + params->alternatives = value->BooleanValue(); + params->number_of_alternatives = 1u; + } + else if (value->IsNumber()) + { + params->alternatives = value->BooleanValue(); + params->number_of_alternatives = static_cast(value->NumberValue()); + } + else + { + Nan::ThrowError("'alternatives' param must be boolean or number"); return route_parameters_ptr(); } - params->alternatives = value->BooleanValue(); } bool parsedSuccessfully = parseCommonParameters(obj, params); diff --git a/include/server/api/route_parameters_grammar.hpp b/include/server/api/route_parameters_grammar.hpp index 3ad51d73f..2572532c9 100644 --- a/include/server/api/route_parameters_grammar.hpp +++ b/include/server/api/route_parameters_grammar.hpp @@ -30,7 +30,13 @@ struct RouteParametersGrammar : public BaseParametersGrammar - qi::bool_[ph::bind(&engine::api::RouteParameters::alternatives, qi::_r1) = qi::_1]) | + (qi::uint_[ph::bind(&engine::api::RouteParameters::number_of_alternatives, qi::_r1) = + qi::_1, + ph::bind(&engine::api::RouteParameters::alternatives, qi::_r1) = + qi::_1 > 0] | + qi::bool_[ph::bind(&engine::api::RouteParameters::number_of_alternatives, qi::_r1) = + qi::_1, + ph::bind(&engine::api::RouteParameters::alternatives, qi::_r1) = qi::_1])) | (qi::lit("continue_straight=") > (qi::lit("default") | qi::bool_[ph::bind(&engine::api::RouteParameters::continue_straight, qi::_r1) = diff --git a/include/util/static_assert.hpp b/include/util/static_assert.hpp new file mode 100644 index 000000000..25484a57f --- /dev/null +++ b/include/util/static_assert.hpp @@ -0,0 +1,26 @@ +#ifndef OSRM_STATIC_ASSERT_HPP +#define OSRM_STATIC_ASSERT_HPP + +#include + +namespace osrm +{ +namespace util +{ + +template inline void static_assert_iter_value() +{ + using IterValueType = typename std::iterator_traits::value_type; + static_assert(std::is_same::value, ""); +} + +template inline void static_assert_iter_category() +{ + using IterCategoryType = typename std::iterator_traits::iterator_category; + static_assert(std::is_base_of::value, ""); +} + +} // ns util +} // ns osrm + +#endif // OSRM_STATIC_ASSERT_HPP diff --git a/profiles/car.lua b/profiles/car.lua index b0271a93b..c86503dac 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -9,16 +9,16 @@ local Handlers = require("lib/handlers") local next = next -- bind to local for speed -- set profile properties -properties.max_speed_for_map_matching = 180/3.6 -- 180kmph -> m/s -properties.use_turn_restrictions = true -properties.continue_straight_at_waypoint = true -properties.left_hand_driving = false +properties.max_speed_for_map_matching = 180/3.6 -- 180kmph -> m/s +properties.use_turn_restrictions = true +properties.continue_straight_at_waypoint = true +properties.left_hand_driving = false -- For routing based on duration, but weighted for preferring certain roads -properties.weight_name = 'routability' +properties.weight_name = 'routability' -- For shortest duration without penalties for accessibility ---properties.weight_name = 'duration' +--properties.weight_name = 'duration' -- For shortest distance without penalties for accessibility ---properties.weight_name = 'distance' +--properties.weight_name = 'distance' -- Set to true if you need to call the node_function for every node. -- Generally can be left as false to avoid unnecessary Lua calls diff --git a/src/engine/engine_config.cpp b/src/engine/engine_config.cpp index 357aeff4e..1874046a7 100644 --- a/src/engine/engine_config.cpp +++ b/src/engine/engine_config.cpp @@ -25,7 +25,8 @@ bool EngineConfig::IsValid() const unlimited_or_more_than(max_locations_map_matching, 2) && unlimited_or_more_than(max_locations_trip, 2) && unlimited_or_more_than(max_locations_viaroute, 2) && - unlimited_or_more_than(max_results_nearest, 0); + unlimited_or_more_than(max_results_nearest, 0) && + max_alternatives >= 0; return ((use_shared_memory && all_path_are_empty) || storage_config.IsValid()) && limits_valid; } diff --git a/src/engine/plugins/viaroute.cpp b/src/engine/plugins/viaroute.cpp index 98b97c51c..c80c0411b 100644 --- a/src/engine/plugins/viaroute.cpp +++ b/src/engine/plugins/viaroute.cpp @@ -21,8 +21,8 @@ namespace engine namespace plugins { -ViaRoutePlugin::ViaRoutePlugin(int max_locations_viaroute) - : max_locations_viaroute(max_locations_viaroute) +ViaRoutePlugin::ViaRoutePlugin(int max_locations_viaroute, int max_alternatives) + : max_locations_viaroute(max_locations_viaroute), max_alternatives(max_alternatives) { } @@ -60,6 +60,16 @@ ViaRoutePlugin::HandleRequest(const datafacade::ContiguousInternalMemoryDataFaca json_result); } + // Takes care of alternatives=n and alternatives=true + if ((route_parameters.number_of_alternatives > static_cast(max_alternatives)) || + (route_parameters.alternatives && max_alternatives == 0)) + { + return Error("TooBig", + "Requested number of alternatives is higher than current maximum (" + + std::to_string(max_alternatives) + ")", + json_result); + } + if (!CheckAllCoordinates(route_parameters.coordinates)) { return Error("InvalidValue", "Invalid coordinate value.", json_result); @@ -88,13 +98,19 @@ ViaRoutePlugin::HandleRequest(const datafacade::ContiguousInternalMemoryDataFaca InternalManyRoutesResult routes; + // TODO: in v6 we should remove the boolean and only keep the number parameter. + // For now just force them to be in sync. and keep backwards compatibility. + const auto wants_alternatives = + (max_alternatives > 0) && + (route_parameters.alternatives || route_parameters.number_of_alternatives > 0); + const auto number_of_alternatives = std::max(1u, route_parameters.number_of_alternatives); + // Alternatives do not support vias, only direct s,t queries supported // See the implementation notes and high-level outline. // https://github.com/Project-OSRM/osrm-backend/issues/3905 - if (1 == start_end_nodes.size() && algorithms.HasAlternativePathSearch() && - route_parameters.alternatives) + if (1 == start_end_nodes.size() && algorithms.HasAlternativePathSearch() && wants_alternatives) { - routes = algorithms.AlternativePathSearch(start_end_nodes.front()); + routes = algorithms.AlternativePathSearch(start_end_nodes.front(), number_of_alternatives); } else if (1 == start_end_nodes.size() && algorithms.HasDirectShortestPathSearch()) { diff --git a/src/engine/routing_algorithms/alternative_path.cpp b/src/engine/routing_algorithms/alternative_path_ch.cpp similarity index 86% rename from src/engine/routing_algorithms/alternative_path.cpp rename to src/engine/routing_algorithms/alternative_path_ch.cpp index f04737e96..a738aa415 100644 --- a/src/engine/routing_algorithms/alternative_path.cpp +++ b/src/engine/routing_algorithms/alternative_path_ch.cpp @@ -8,9 +8,7 @@ #include #include #include -#include #include - #include namespace osrm @@ -19,8 +17,10 @@ namespace engine { namespace routing_algorithms { -namespace ch -{ + +// Unqualified calls below are from the ch namespace. +// This alternative implementation works only for ch. +using namespace ch; namespace { @@ -90,7 +90,7 @@ void alternativeRoutingStep(const datafacade::ContiguousInternalMemoryDataFacade else { // check whether there is a loop present at the node - const auto loop_weight = ch::getLoopWeight(facade, node); + const auto loop_weight = getLoopWeight(facade, node); const EdgeWeight new_weight_with_loop = new_weight + loop_weight; if (loop_weight != INVALID_EDGE_WEIGHT && new_weight_with_loop <= *upper_bound_to_shortest_path_weight) @@ -140,11 +140,11 @@ void retrievePackedAlternatePath(const QueryHeap &forward_heap1, { // fetch packed path [s,v) std::vector packed_v_t_path; - ch::retrievePackedPathFromHeap(forward_heap1, reverse_heap2, s_v_middle, packed_path); + retrievePackedPathFromHeap(forward_heap1, reverse_heap2, s_v_middle, packed_path); packed_path.pop_back(); // remove middle node. It's in both half-paths // fetch patched path [v,t] - ch::retrievePackedPathFromHeap(forward_heap2, reverse_heap1, v_t_middle, packed_v_t_path); + retrievePackedPathFromHeap(forward_heap2, reverse_heap1, v_t_middle, packed_v_t_path); packed_path.insert(packed_path.end(), packed_v_t_path.begin(), packed_v_t_path.end()); } @@ -181,14 +181,14 @@ void computeWeightAndSharingOfViaPath( // compute path by reusing forward search from s while (!new_reverse_heap.Empty()) { - ch::routingStep(facade, - new_reverse_heap, - existing_forward_heap, - s_v_middle, - upper_bound_s_v_path_weight, - min_edge_offset, - DO_NOT_FORCE_LOOPS, - DO_NOT_FORCE_LOOPS); + routingStep(facade, + new_reverse_heap, + existing_forward_heap, + s_v_middle, + upper_bound_s_v_path_weight, + min_edge_offset, + DO_NOT_FORCE_LOOPS, + DO_NOT_FORCE_LOOPS); } // compute path by reusing backward search from node t NodeID v_t_middle = SPECIAL_NODEID; @@ -196,14 +196,14 @@ void computeWeightAndSharingOfViaPath( new_forward_heap.Insert(via_node, 0, via_node); while (!new_forward_heap.Empty()) { - ch::routingStep(facade, - new_forward_heap, - existing_reverse_heap, - v_t_middle, - upper_bound_of_v_t_path_weight, - min_edge_offset, - DO_NOT_FORCE_LOOPS, - DO_NOT_FORCE_LOOPS); + routingStep(facade, + new_forward_heap, + existing_reverse_heap, + v_t_middle, + upper_bound_of_v_t_path_weight, + min_edge_offset, + DO_NOT_FORCE_LOOPS, + DO_NOT_FORCE_LOOPS); } *real_weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight; @@ -213,9 +213,9 @@ void computeWeightAndSharingOfViaPath( } // retrieve packed paths - ch::retrievePackedPathFromHeap( + retrievePackedPathFromHeap( existing_forward_heap, new_reverse_heap, s_v_middle, packed_s_v_path); - ch::retrievePackedPathFromHeap( + retrievePackedPathFromHeap( new_forward_heap, existing_reverse_heap, v_t_middle, packed_v_t_path); // partial unpacking, compute sharing @@ -235,14 +235,14 @@ void computeWeightAndSharingOfViaPath( { if (packed_s_v_path[current_node] == packed_shortest_path[current_node]) { - ch::unpackEdge(facade, - packed_s_v_path[current_node], - packed_s_v_path[current_node + 1], - partially_unpacked_via_path); - ch::unpackEdge(facade, - packed_shortest_path[current_node], - packed_shortest_path[current_node + 1], - partially_unpacked_shortest_path); + unpackEdge(facade, + packed_s_v_path[current_node], + packed_s_v_path[current_node + 1], + partially_unpacked_via_path); + unpackEdge(facade, + packed_shortest_path[current_node], + packed_shortest_path[current_node + 1], + partially_unpacked_shortest_path); break; } } @@ -281,14 +281,14 @@ void computeWeightAndSharingOfViaPath( { if (packed_v_t_path[via_path_index] == packed_shortest_path[shortest_path_index]) { - ch::unpackEdge(facade, - packed_v_t_path[via_path_index - 1], - packed_v_t_path[via_path_index], - partially_unpacked_via_path); - ch::unpackEdge(facade, - packed_shortest_path[shortest_path_index - 1], - packed_shortest_path[shortest_path_index], - partially_unpacked_shortest_path); + unpackEdge(facade, + packed_v_t_path[via_path_index - 1], + packed_v_t_path[via_path_index], + partially_unpacked_via_path); + unpackEdge(facade, + packed_shortest_path[shortest_path_index - 1], + packed_shortest_path[shortest_path_index], + partially_unpacked_shortest_path); break; } } @@ -343,14 +343,14 @@ bool viaNodeCandidatePassesTTest( new_reverse_heap.Insert(candidate.node, 0, candidate.node); while (new_reverse_heap.Size() > 0) { - ch::routingStep(facade, - new_reverse_heap, - existing_forward_heap, - *s_v_middle, - upper_bound_s_v_path_weight, - min_edge_offset, - DO_NOT_FORCE_LOOPS, - DO_NOT_FORCE_LOOPS); + routingStep(facade, + new_reverse_heap, + existing_forward_heap, + *s_v_middle, + upper_bound_s_v_path_weight, + min_edge_offset, + DO_NOT_FORCE_LOOPS, + DO_NOT_FORCE_LOOPS); } if (INVALID_EDGE_WEIGHT == upper_bound_s_v_path_weight) @@ -364,14 +364,14 @@ bool viaNodeCandidatePassesTTest( new_forward_heap.Insert(candidate.node, 0, candidate.node); while (new_forward_heap.Size() > 0) { - ch::routingStep(facade, - new_forward_heap, - existing_reverse_heap, - *v_t_middle, - upper_bound_of_v_t_path_weight, - min_edge_offset, - DO_NOT_FORCE_LOOPS, - DO_NOT_FORCE_LOOPS); + routingStep(facade, + new_forward_heap, + existing_reverse_heap, + *v_t_middle, + upper_bound_of_v_t_path_weight, + min_edge_offset, + DO_NOT_FORCE_LOOPS, + DO_NOT_FORCE_LOOPS); } if (INVALID_EDGE_WEIGHT == upper_bound_of_v_t_path_weight) @@ -382,10 +382,10 @@ bool viaNodeCandidatePassesTTest( *weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight; // retrieve packed paths - ch::retrievePackedPathFromHeap( + retrievePackedPathFromHeap( existing_forward_heap, new_reverse_heap, *s_v_middle, packed_s_v_path); - ch::retrievePackedPathFromHeap( + retrievePackedPathFromHeap( new_forward_heap, existing_reverse_heap, *v_t_middle, packed_v_t_path); NodeID s_P = *s_v_middle, t_P = *v_t_middle; @@ -537,35 +537,36 @@ bool viaNodeCandidatePassesTTest( { if (!forward_heap3.Empty()) { - ch::routingStep(facade, - forward_heap3, - reverse_heap3, - middle, - upper_bound, - min_edge_offset, - DO_NOT_FORCE_LOOPS, - DO_NOT_FORCE_LOOPS); + routingStep(facade, + forward_heap3, + reverse_heap3, + middle, + upper_bound, + min_edge_offset, + DO_NOT_FORCE_LOOPS, + DO_NOT_FORCE_LOOPS); } if (!reverse_heap3.Empty()) { - ch::routingStep(facade, - reverse_heap3, - forward_heap3, - middle, - upper_bound, - min_edge_offset, - DO_NOT_FORCE_LOOPS, - DO_NOT_FORCE_LOOPS); + routingStep(facade, + reverse_heap3, + forward_heap3, + middle, + upper_bound, + min_edge_offset, + DO_NOT_FORCE_LOOPS, + DO_NOT_FORCE_LOOPS); } } return (upper_bound <= t_test_path_weight); } -} +} // anon. namespace InternalManyRoutesResult alternativePathSearch(SearchEngineData &engine_working_data, const datafacade::ContiguousInternalMemoryDataFacade &facade, - const PhantomNodes &phantom_node_pair) + const PhantomNodes &phantom_node_pair, + unsigned /*number_of_alternatives*/) { InternalRouteResult primary_route; InternalRouteResult secondary_route; @@ -651,8 +652,8 @@ alternativePathSearch(SearchEngineData &engine_working_data, else { - ch::retrievePackedPathFromSingleHeap(forward_heap1, middle_node, packed_forward_path); - ch::retrievePackedPathFromSingleHeap(reverse_heap1, middle_node, packed_reverse_path); + retrievePackedPathFromSingleHeap(forward_heap1, middle_node, packed_forward_path); + retrievePackedPathFromSingleHeap(reverse_heap1, middle_node, packed_reverse_path); } // this set is is used as an indicator if a node is on the shortest path @@ -805,14 +806,14 @@ alternativePathSearch(SearchEngineData &engine_working_data, primary_route.target_traversed_in_reverse.push_back(( packed_shortest_path.back() != phantom_node_pair.target_phantom.forward_segment_id.id)); - ch::unpackPath(facade, - // -- packed input - packed_shortest_path.begin(), - packed_shortest_path.end(), - // -- start of route - phantom_node_pair, - // -- unpacked output - primary_route.unpacked_path_segments.front()); + unpackPath(facade, + // -- packed input + packed_shortest_path.begin(), + packed_shortest_path.end(), + // -- start of route + phantom_node_pair, + // -- unpacked output + primary_route.unpacked_path_segments.front()); primary_route.shortest_path_weight = upper_bound_to_shortest_path_weight; } @@ -837,11 +838,11 @@ alternativePathSearch(SearchEngineData &engine_working_data, phantom_node_pair.target_phantom.forward_segment_id.id)); // unpack the alternate path - ch::unpackPath(facade, - packed_alternate_path.begin(), - packed_alternate_path.end(), - phantom_node_pair, - secondary_route.unpacked_path_segments.front()); + unpackPath(facade, + packed_alternate_path.begin(), + packed_alternate_path.end(), + phantom_node_pair, + secondary_route.unpacked_path_segments.front()); secondary_route.shortest_path_weight = weight_of_via_path; } @@ -853,7 +854,6 @@ alternativePathSearch(SearchEngineData &engine_working_data, return InternalManyRoutesResult{{std::move(primary_route), std::move(secondary_route)}}; } -} // namespace ch } // namespace routing_algorithms } // namespace engine } // namespace osrm} diff --git a/src/engine/routing_algorithms/alternative_path_mld.cpp b/src/engine/routing_algorithms/alternative_path_mld.cpp new file mode 100644 index 000000000..83f4a8e9b --- /dev/null +++ b/src/engine/routing_algorithms/alternative_path_mld.cpp @@ -0,0 +1,936 @@ +#include "engine/routing_algorithms/alternative_path.hpp" +#include "engine/routing_algorithms/routing_base_mld.hpp" + +#include "util/static_assert.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace osrm +{ +namespace engine +{ +namespace routing_algorithms +{ + +// Unqualified calls below are from the mld namespace. +// This alternative implementation works only for mld. +using namespace mld; + +using Heap = SearchEngineData::QueryHeap; +using Partition = partition::MultiLevelPartitionView; +using Facade = datafacade::ContiguousInternalMemoryDataFacade; + +// Implementation details +namespace +{ + +// Alternative paths candidate via nodes are taken from overlapping search spaces. +// Overlapping by a third guarantees us taking candidate nodes "from the middle". +const constexpr auto kSearchSpaceOverlapFactor = 1.33; +// Unpack n-times more candidate paths to run high-quality checks on. +// Unpacking paths yields higher chance to find good alternatives but is also expensive. +const constexpr auto kAlternativesToUnpackFactor = 2.0; +// Alternative paths length requirement (stretch). +// At most 25% longer then the shortest path. +const constexpr auto kAtMostLongerBy = 0.25; +// Alternative paths similarity requirement (sharing). +// At least 15% different than the shortest path. +const constexpr auto kAtLeastDifferentBy = 0.85; +// Alternative paths are still reasonable around the via node candidate (local optimality). +// At least optimal around 10% sub-paths around the via node candidate. +const /*constexpr*/ auto kAtLeastOptimalAroundViaBy = 0.10; +// gcc 7.1 ICE ^ + +// Represents a via middle node where forward (from s) and backward (from t) +// search spaces overlap and the weight a path (made up of s,via and via,t) has. +struct WeightedViaNode +{ + NodeID node; + EdgeWeight weight; +}; + +// Represents a complete packed path (made up of s,via and via,t) +// its total weight and the via node used to construct the path. +struct WeightedViaNodePackedPath +{ + WeightedViaNode via; + PackedPath path; + std::vector path_weights; +}; + +// Represents a high-detail unpacked path (s, .., via, .., t) +// its total weight and the via node used to construct the path. +struct WeightedViaNodeUnpackedPath +{ + WeightedViaNode via; + UnpackedNodes nodes; + UnpackedEdges edges; +}; + +// Filters candidates which are on not unique. +// Returns an iterator to the uniquified range's new end. +// Note: mutates the range in-place invalidating iterators. +template RandIt filterViaCandidatesByUniqueNodeIds(RandIt first, RandIt last) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + std::sort(first, last, [](auto lhs, auto rhs) { return lhs.node < rhs.node; }); + return std::unique(first, last, [](auto lhs, auto rhs) { return lhs.node == rhs.node; }); +} + +// Filters candidates which are on un-important roads. +// Returns an iterator to the filtered range's new end. +template +RandIt filterViaCandidatesByRoadImportance(RandIt first, RandIt last, const Facade &facade) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + // Todo: the idea here is to filter out alternatives where the via candidate is not on a + // high-priority road. We should experiment if this is really needed or if the boundary + // nodes the mld search space gives us already provides us with reasonable via candidates. + // + // Implementation: we already have `RoadClassification` from guidance. We need to serialize + // it to disk and then in the facades read it in again providing `IsImportantRoad(NodeID)`. + // Note: serialize out bit vector keyed by node id with 0/1 <=> unimportant/important. + (void)first; + (void)last; + (void)facade; + + return last; +} + +// Scale the maximum allowed weight increase based on its magnitude: +// - Shortest path 10 minutes, alternative 13 minutes => Factor of 0.30 ok +// - Shortest path 10 hours, alternative 13 hours => Factor of 0.30 unreasonable +double scaledAtMostLongerByFactorBasedOnDuration(EdgeWeight duration) +{ + BOOST_ASSERT(duration != INVALID_EDGE_WEIGHT); + + // We only have generic weights here and no durations without unpacking. + // We also have restricted way penalties which are huge and will screw scaling here. + // + // Users can pass us generic weights not based on durations; we can't do anything about + // it here other than either generating too many or no alternatives in these cases. + // + // We scale the weights with a step function based on some rough guestimates, so that + // they match tens of minutes, in the low hours, tens of hours, etc. + + // Todo: instead of a piecewise constant function should this be a continuous function? + // At the moment there are "hard" jump edge cases when crossing the thresholds. + + auto scaledAtMostLongerBy = kAtMostLongerBy; + + const constexpr auto minutes = 60.; + const constexpr auto hours = 60. * minutes; + + if (duration < EdgeWeight(10 * minutes)) + scaledAtMostLongerBy *= 1.20; + else if (duration < EdgeWeight(30 * minutes)) + scaledAtMostLongerBy *= 1.00; + else if (duration < EdgeWeight(1 * hours)) + scaledAtMostLongerBy *= 0.90; + else if (duration < EdgeWeight(3 * hours)) + scaledAtMostLongerBy *= 0.70; + else if (duration > EdgeWeight(10 * hours)) + scaledAtMostLongerBy *= 0.50; + + return scaledAtMostLongerBy; +} + +// Filters candidates with much higher weight than the primary route. Mutates range in-place. +// Returns an iterator to the filtered range's new end. +template +RandIt +filterViaCandidatesByStretch(RandIt first, RandIt last, EdgeWeight weight, double weight_multiplier) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + // Assumes weight roughly corresponds to duration-ish. If this is not the case e.g. + // because users are setting weight to be distance in the profiles, then we might + // either generate more candidates than we have to or not enough. But is okay. + const auto scaled_at_most_longer_by = + scaledAtMostLongerByFactorBasedOnDuration(weight / weight_multiplier); + const auto stretch_weight_limit = (1. + scaled_at_most_longer_by) * weight; + + const auto over_weight_limit = [=](const auto via) { + return via.weight > stretch_weight_limit; + }; + + return std::remove_if(first, last, over_weight_limit); +} + +// Filters candidates that are on the path. Mutates range in-place. +// Returns an iterator to the filtered range's new end. +template +RandIt +filterViaCandidatesByViaNotOnPath(const WeightedViaNodePackedPath &path, RandIt first, RandIt last) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + if (path.path.empty()) + return last; + + std::unordered_set nodes; + nodes.reserve(path.path.size() + 1); + + nodes.insert(std::get<0>(path.path.front())); + for (const auto &edge : path.path) + nodes.insert(std::get<1>(edge)); + + const auto via_on_path = [&](const auto via) { return nodes.count(via.node) > 0; }; + + return std::remove_if(first, last, via_on_path); +} + +// Filters packed paths with similar cells between each other. Mutates range in-place. +// Returns an iterator to the filtered range's new end. +template +RandIt filterPackedPathsByCellSharing(RandIt first, RandIt last, const Partition &partition) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + // Todo: we could scale cell sharing with edge weights. Also basing sharing on + // cells could be problematic e.g. think of parallel ways in grid cities. + + const auto size = static_cast(last - first); + + if (size < 2) + return last; + + const auto number_of_levels = partition.GetNumberOfLevels(); + (void)number_of_levels; + BOOST_ASSERT(number_of_levels >= 1); + + // Todo: sharing could be a linear combination based on level and sharing on each level. + // Experimental evaluation shows using the lowest level works surprisingly well already. + const auto level = 1; + const auto get_cell = [&](auto node) { return partition.GetCell(level, node); }; + + const auto shortest_path = *first; + + if (shortest_path.path.empty()) + return last; + + std::unordered_set cells; + cells.reserve(size * (shortest_path.path.size() + 1) * (1. + kAtMostLongerBy)); + + cells.insert(get_cell(std::get<0>(shortest_path.path.front()))); + for (const auto &edge : shortest_path.path) + cells.insert(get_cell(std::get<1>(edge))); + + const auto over_sharing_limit = [&](const auto &packed) { + const auto not_seen = [&](const PackedEdge edge) { + const auto source_cell = get_cell(std::get<0>(edge)); + const auto target_cell = get_cell(std::get<1>(edge)); + return cells.count(source_cell) < 1 && cells.count(target_cell) < 1; + }; + + const auto different = std::count_if(begin(packed.path), end(packed.path), not_seen); + + const auto difference = different / static_cast(packed.path.size() + 1); + BOOST_ASSERT(difference >= 0.); + BOOST_ASSERT(difference <= 1.); + + const auto sharing = 1. - difference; + + if (sharing > kAtLeastDifferentBy) + { + return true; + } + else + { + cells.insert(get_cell(std::get<0>(packed.path.front()))); + for (const auto &edge : packed.path) + cells.insert(get_cell(std::get<1>(edge))); + + return false; + } + }; + + return std::remove_if(first + 1, last, over_sharing_limit); +} + +// Filters packed paths based on local optimality. Mutates range in-place. +// Returns an iterator to the filtered range's new end. +template +RandIt filterPackedPathsByLocalOptimality(const WeightedViaNodePackedPath &path, + const Heap &forward_heap, + const Heap &reverse_heap, + RandIt first, + RandIt last) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + if (path.path.empty()) + return last; + + // Check sub-path optimality on alternative path crossing the via node candidate. + // + // s - - - v - - - t our packed path made up of (from, to) edges + // |--|--| sub-paths "left" and "right" of v based on threshold + // f v l nodes in [v,f] and in [v, l] have to match predecessor in heaps + // + // Todo: this approach is efficient but works on packed paths only. Do we need to do a + // thorough check on the unpacked paths instead? Or do we even need to introduce two + // new thread-local heaps for the mld SearchEngineData and do proper s-t routing here? + + BOOST_ASSERT(path.via.weight != INVALID_EDGE_WEIGHT); + + // node == parent_in_main_heap(parent_in_side_heap(v)) -> plateaux at `node` + const auto has_plateaux_at_node = [&](const NodeID node, const Heap &fst, const Heap &snd) { + BOOST_ASSERT(fst.WasInserted(node)); + auto const parent = fst.GetData(node).parent; + return snd.WasInserted(parent) && snd.GetData(parent).parent == node; + }; + + // A plateaux is defined as a segment in which the search tree from s and the search + // tree from t overlap. An edge is part of such a plateaux around `v` if: + // v == parent_in_reverse_search(parent_in_forward_search(v)). + // Here we calculate the last node on the plateaux in either direction. + const auto plateaux_end = [&](NodeID node, const Heap &fst, const Heap &snd) { + BOOST_ASSERT(node != SPECIAL_NODEID); + BOOST_ASSERT(fst.WasInserted(node)); + BOOST_ASSERT(snd.WasInserted(node)); + + // Check plateaux edges towards the target. Terminates at the source / target + // at the latest, since parent(target)==target for the reverse heap and + // parent(target) != target in the forward heap (and vice versa). + while (has_plateaux_at_node(node, fst, snd)) + node = fst.GetData(node).parent; + + return node; + }; + + const auto is_not_locally_optimal = [&](const auto &packed) { + BOOST_ASSERT(packed.via.node != path.via.node); + BOOST_ASSERT(packed.via.weight != INVALID_EDGE_WEIGHT); + BOOST_ASSERT(packed.via.node != SPECIAL_NODEID); + BOOST_ASSERT(!packed.path.empty()); + + const NodeID via = packed.via.node; + + // Plateaux iff via == parent_in_reverse_search(parent_in_forward_search(via)) + // + // Forward search starts from s, reverse search starts from t: + // - parent_in_forward_search(via) = a + // - parent_in_reverse_search(a) = b != via and therefore not local optimal + // + // via + // .' '. + // s - a - - - b - t + // + // Care needs to be taken for the edge case where the via node is on the border + // of the search spaces and therefore parent pointers may not be valid in heaps. + // In these cases we know we can't have local optimality around the via already. + + const auto first_on_plateaux = plateaux_end(via, forward_heap, reverse_heap); + const auto last_on_plateaux = plateaux_end(via, reverse_heap, forward_heap); + + // fop - - via - - lop + // .' '. + // s - a - - - - - - - - - - - - b - t + // + // Lenth of plateaux is given by the weight vetween fop and via as well as via and lop. + const auto plateaux_length = + forward_heap.GetKey(last_on_plateaux) - forward_heap.GetKey(first_on_plateaux); + + // Find a/b as the first location where packed and path differ + const auto a = std::get<0>(*std::mismatch(packed.path.begin(), // + packed.path.end(), + path.path.begin(), + path.path.end()) + .first); + const auto b = std::get<1>(*std::mismatch(packed.path.rbegin(), // + packed.path.rend(), + path.path.rbegin(), + path.path.rend()) + .first); + + BOOST_ASSERT(forward_heap.WasInserted(a)); + BOOST_ASSERT(reverse_heap.WasInserted(b)); + const auto detour_length = forward_heap.GetKey(via) - forward_heap.GetKey(a) + + reverse_heap.GetKey(via) - reverse_heap.GetKey(b); + + return plateaux_length < kAtLeastOptimalAroundViaBy * detour_length; + }; + + return std::remove_if(first, last, is_not_locally_optimal); +} + +// Filters unpacked paths compared to all other paths. Mutates range in-place. +// Returns an iterator to the filtered range's new end. +template RandIt filterUnpackedPathsBySharing(RandIt first, RandIt last) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + const auto size = static_cast(last - first); + + if (size < 2) + return last; + + const auto &shortest_path = *first; + + if (shortest_path.edges.empty()) + return last; + + std::unordered_set edges; + edges.reserve(size * shortest_path.edges.size() * (1. + kAtMostLongerBy)); + + edges.insert(begin(shortest_path.edges), begin(shortest_path.edges)); + + const auto over_sharing_limit = [&](const auto &unpacked) { + const auto not_seen = [&](const EdgeID edge) { return edges.count(edge) < 1; }; + const auto different = std::count_if(begin(unpacked.edges), end(unpacked.edges), not_seen); + + const auto difference = different / static_cast(unpacked.edges.size()); + BOOST_ASSERT(difference >= 0.); + BOOST_ASSERT(difference <= 1.); + + const auto sharing = 1. - difference; + + if (sharing > kAtLeastDifferentBy) + { + return true; + } + else + { + edges.insert(begin(unpacked.edges), end(unpacked.edges)); + return false; + } + }; + + return std::remove_if(first + 1, last, over_sharing_limit); +} + +// Filters annotated routes by stretch based on duration. Mutates range in-place. +// Returns an iterator to the filtered range's new end. +template +RandIt +filterAnnotatedRoutesByStretch(RandIt first, RandIt last, const InternalRouteResult &shortest_route) +{ + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + BOOST_ASSERT(shortest_route.is_valid()); + + const auto shortest_route_duration = shortest_route.duration(); + const auto scaled_at_most_longer_by = + scaledAtMostLongerByFactorBasedOnDuration(shortest_route_duration); + const auto stretch_duration_limit = (1. + scaled_at_most_longer_by) * shortest_route_duration; + + const auto over_duration_limit = [=](const auto &route) { + return route.duration() > stretch_duration_limit; + }; + + return std::remove_if(first, last, over_duration_limit); +} + +// Unpacks a range of WeightedViaNodePackedPaths into a range of WeightedViaNodeUnpackedPaths. +// Note: destroys search engine heaps for recursive unpacking. Extract heap data you need before. +template +void unpackPackedPaths(InputIt first, + InputIt last, + OutIt out, + SearchEngineData &search_engine_data, + const Facade &facade, + const PhantomNodes &phantom_node_pair) +{ + util::static_assert_iter_category(); + util::static_assert_iter_category(); + util::static_assert_iter_value(); + + const bool force_loop_forward = needsLoopForward(phantom_node_pair); + const bool force_loop_backward = needsLoopBackwards(phantom_node_pair); + + const Partition &partition = facade.GetMultiLevelPartition(); + + Heap &forward_heap = *search_engine_data.forward_heap_1; + Heap &reverse_heap = *search_engine_data.reverse_heap_1; + + for (auto it = first; it != last; ++it, ++out) + { + const auto packed_path_weight = it->via.weight; + const auto packed_path_via = it->via.node; + + const auto &packed_path = it->path; + + // + // Todo: dup. code with mld::search except for level entry: we run a slight mld::search + // adaption here and then dispatch to mld::search for recursively descending down. + // + + std::vector unpacked_nodes; + std::vector unpacked_edges; + unpacked_nodes.reserve(packed_path.size()); + unpacked_edges.reserve(packed_path.size()); + + // Beware the edge case when start, via, end are all the same. + // In this case we return a single node, no edges. We also don't unpack. + if (packed_path.empty()) + { + const auto source_node = packed_path_via; + unpacked_nodes.push_back(source_node); + } + else + { + const auto source_node = std::get<0>(packed_path.front()); + unpacked_nodes.push_back(source_node); + } + + for (auto const &packed_edge : packed_path) + { + NodeID source, target; + bool overlay_edge; + std::tie(source, target, overlay_edge) = packed_edge; + if (!overlay_edge) + { // a base graph edge + unpacked_nodes.push_back(target); + unpacked_edges.push_back(facade.FindEdge(source, target)); + } + else + { // an overlay graph edge + LevelID level = getNodeQueryLevel(partition, source, phantom_node_pair); // XXX + CellID parent_cell_id = partition.GetCell(level, source); + BOOST_ASSERT(parent_cell_id == partition.GetCell(level, target)); + + LevelID sublevel = level - 1; + + // Here heaps can be reused, let's go deeper! + forward_heap.Clear(); + reverse_heap.Clear(); + forward_heap.Insert(source, 0, {source}); + reverse_heap.Insert(target, 0, {target}); + + // TODO: when structured bindings will be allowed change to + // auto [subpath_weight, subpath_source, subpath_target, subpath] = ... + EdgeWeight subpath_weight; + std::vector subpath_nodes; + std::vector subpath_edges; + std::tie(subpath_weight, subpath_nodes, subpath_edges) = search(search_engine_data, + facade, + forward_heap, + reverse_heap, + force_loop_forward, + force_loop_backward, + INVALID_EDGE_WEIGHT, + sublevel, + parent_cell_id); + BOOST_ASSERT(!subpath_edges.empty()); + BOOST_ASSERT(subpath_nodes.size() > 1); + BOOST_ASSERT(subpath_nodes.front() == source); + BOOST_ASSERT(subpath_nodes.back() == target); + unpacked_nodes.insert( + unpacked_nodes.end(), std::next(subpath_nodes.begin()), subpath_nodes.end()); + unpacked_edges.insert( + unpacked_edges.end(), subpath_edges.begin(), subpath_edges.end()); + } + } + + WeightedViaNodeUnpackedPath unpacked_path{ + WeightedViaNode{packed_path_via, packed_path_weight}, + std::move(unpacked_nodes), + std::move(unpacked_edges)}; + + out = std::move(unpacked_path); + } +} + +// Generates via candidate nodes from the overlap of the two search spaces from s and t. +// Returns via node candidates in no particular order; they're not guaranteed to be unique. +// Note: heaps are modified in-place, after the function returns they're valid and can be used. +inline std::vector +makeCandidateVias(SearchEngineData &search_engine_data, + const Facade &facade, + const PhantomNodes &phantom_node_pair) +{ + Heap &forward_heap = *search_engine_data.forward_heap_1; + Heap &reverse_heap = *search_engine_data.reverse_heap_1; + + insertNodesInHeaps(forward_heap, reverse_heap, phantom_node_pair); + + // The single via node in the shortest paths s,via and via,t sub-paths and + // the weight for the shortest path s,t we return and compare alternatives to. + EdgeWeight shortest_path_weight = INVALID_EDGE_WEIGHT; + + // The current via node during search spaces overlap stepping and an artificial + // weight (overlap factor * shortest path weight) we use as a termination criteria. + NodeID overlap_via = SPECIAL_NODEID; + EdgeWeight overlap_weight = INVALID_EDGE_WEIGHT; + + // All via nodes in the overlapping search space (except the shortest path via node). + // Will be filtered and ranked and then used for s,via and via,t alternative paths. + std::vector candidate_vias; + + // The logic below is a bit weird - here's why: we want to re-use the MLD routingStep for + // stepping our search space from s and from t. We don't know how far to overlap until we have + // the shortest path. Once we have the shortest path we can use its weight to terminate when + // we're over factor * weight. We have to set the weight for routingStep to INVALID_EDGE_WEIGHT + // so that stepping will continue even after we reached the shortest path upper bound. + + const bool force_loop_forward = needsLoopForward(phantom_node_pair); + const bool force_loop_backward = needsLoopBackwards(phantom_node_pair); + + EdgeWeight forward_heap_min = forward_heap.MinKey(); + EdgeWeight reverse_heap_min = reverse_heap.MinKey(); + + while (forward_heap.Size() + reverse_heap.Size() > 0) + { + if (shortest_path_weight != INVALID_EDGE_WEIGHT) + overlap_weight = shortest_path_weight * kSearchSpaceOverlapFactor; + + // Termination criteria - when we have a shortest path this will guarantee for our overlap. + const bool keep_going = forward_heap_min + reverse_heap_min < overlap_weight; + + if (!keep_going) + break; + + // Force forward step to not break early when we reached the middle, continue for overlap. + // Note: only invalidate the via node, the weight upper bound is still correct! + overlap_via = SPECIAL_NODEID; + + if (!forward_heap.Empty()) + { + routingStep(facade, + forward_heap, + reverse_heap, + overlap_via, + overlap_weight, + force_loop_forward, + force_loop_backward, + phantom_node_pair); + + if (!forward_heap.Empty()) + forward_heap_min = forward_heap.MinKey(); + + if (overlap_weight != INVALID_EDGE_WEIGHT && overlap_via != SPECIAL_NODEID) + { + candidate_vias.push_back(WeightedViaNode{overlap_via, overlap_weight}); + } + } + + // Adjusting upper bound for forward search + shortest_path_weight = std::min(shortest_path_weight, overlap_weight); + // Force reverse step to not break early when we reached the middle, continue for overlap. + // Note: only invalidate the via node, the weight upper bound is still correct! + overlap_via = SPECIAL_NODEID; + + if (!reverse_heap.Empty()) + { + routingStep(facade, + reverse_heap, + forward_heap, + overlap_via, + overlap_weight, + force_loop_forward, + force_loop_backward, + phantom_node_pair); + + if (!reverse_heap.Empty()) + reverse_heap_min = reverse_heap.MinKey(); + + if (overlap_weight != INVALID_EDGE_WEIGHT && overlap_via != SPECIAL_NODEID) + { + candidate_vias.push_back(WeightedViaNode{overlap_via, overlap_weight}); + } + } + + // Adjusting upper bound for reverse search + shortest_path_weight = std::min(shortest_path_weight, overlap_weight); + } + + return candidate_vias; +} + +// Generates packed path edge weights based on a packed path, its total weight and the heaps. +inline std::vector retrievePackedPathWeightsFromHeap(const Heap &forward_heap, + const Heap &reverse_heap, + const PackedPath &packed_path, + const WeightedViaNode via) +{ + if (packed_path.empty()) + return {}; + + std::vector path_weights; + path_weights.reserve(packed_path.size()); + + // We need to retrieve edge weights either from the forward heap or the reverse heap. + // The heap depends on if we are on the s,via sub-path or on the via,t sub-path. + // + // There is an edge-case where the (from,to) has to==via. In this case the edge weight + // can only be retrieved by subtracting the heap weights from the total path weight. + // + // Note: heap.WasInserted(node) only guarantees that the search has seen the node, + // but does not guarantee for the node to be settled in the heap! + + // The first edge could already be the edge-case with to==via as explained above. + bool after_via = std::get<0>(packed_path.front()) == via.node; + + for (auto it = begin(packed_path), last = end(packed_path); it != last; ++it) + { + const auto from = std::get<0>(*it); + const auto to = std::get<1>(*it); + + BOOST_ASSERT(forward_heap.WasInserted(from) || reverse_heap.WasInserted(from)); + BOOST_ASSERT(forward_heap.WasInserted(to) || reverse_heap.WasInserted(to)); + + if (to != via.node && !after_via) + { + BOOST_ASSERT(forward_heap.WasInserted(from) && forward_heap.WasInserted(to)); + const auto weight_from = forward_heap.GetKey(from); + const auto weight_to = forward_heap.GetKey(to); + BOOST_ASSERT(weight_to >= weight_from); + path_weights.push_back(weight_to - weight_from); + } + + if (to != via.node && after_via) + { + BOOST_ASSERT(reverse_heap.WasInserted(from) && reverse_heap.WasInserted(to)); + const auto weight_from = reverse_heap.GetKey(from); + const auto weight_to = reverse_heap.GetKey(to); + BOOST_ASSERT(weight_from >= weight_to); + path_weights.push_back(weight_from - weight_to); + } + + if (to == via.node) + { + BOOST_ASSERT(forward_heap.WasInserted(from)); + BOOST_ASSERT(reverse_heap.WasInserted(to)); + const auto weight_from = forward_heap.GetKey(from); + const auto weight_to = reverse_heap.GetKey(to); + BOOST_ASSERT(via.weight >= (weight_from + weight_to)); + path_weights.push_back(via.weight - (weight_from + weight_to)); + + after_via = true; + } + } + + BOOST_ASSERT(path_weights.size() == packed_path.size()); + + // We inserted phantom nodes into the heaps, which means our start and target node + // already have weights in the heaps even without edges. The search we did to generate + // the total weight already includes these phantom node weights. Here we have to + // manually account for them by means of simply retrieving the inserted weights. + const auto assert_weights = [&] { + const auto s = std::get<0>(packed_path.front()); + const auto t = std::get<1>(packed_path.back()); + BOOST_ASSERT(forward_heap.WasInserted(s)); + BOOST_ASSERT(reverse_heap.WasInserted(t)); + + const auto edge_weights = + std::accumulate(begin(path_weights), end(path_weights), EdgeWeight{0}); + const auto phantom_node_weights = forward_heap.GetKey(s) + reverse_heap.GetKey(t); + (void)edge_weights; + (void)phantom_node_weights; + BOOST_ASSERT(via.weight == edge_weights + phantom_node_weights); + }; + (void)assert_weights; + BOOST_ASSERT((assert_weights(), true)); + + return path_weights; +} + +} // anon. ns + +// Alternative Routes for MLD. +// +// Start search from s and continue "for a while" when middle was found. Save vertices. +// Start search from t and continue "for a while" when middle was found. Save vertices. +// Intersect both vertex sets: these are the candidate vertices. +// For all candidate vertices c a (potentially arbitrarily bad) alternative route is (s, c, t). +// Apply heuristic to evaluate alternative route based on stretch, overlap, how reasonable it is. +// +// For MLD specifically we can pull off some tricks to make evaluating alternatives fast: +// Only consider (s, c, t) with c border vertex: re-use MLD search steps. +// Add meta data to border vertices: consider (s, c, t) only when c is e.g. on a highway. +// Prune based on vertex cell id +// +// https://github.com/Project-OSRM/osrm-backend/issues/3905 +InternalManyRoutesResult alternativePathSearch(SearchEngineData &search_engine_data, + const Facade &facade, + const PhantomNodes &phantom_node_pair, + unsigned number_of_alternatives) +{ + const auto max_number_of_alternatives = number_of_alternatives; + const auto max_number_of_alternatives_to_unpack = + kAlternativesToUnpackFactor * max_number_of_alternatives; + BOOST_ASSERT(max_number_of_alternatives > 0); + BOOST_ASSERT(max_number_of_alternatives_to_unpack >= max_number_of_alternatives); + + const Partition &partition = facade.GetMultiLevelPartition(); + + // Prepare heaps for usage below. The searches will modify them in-place. + search_engine_data.InitializeOrClearFirstThreadLocalStorage(facade.GetNumberOfNodes()); + + Heap &forward_heap = *search_engine_data.forward_heap_1; + Heap &reverse_heap = *search_engine_data.reverse_heap_1; + + // Do forward and backward search, save search space overlap as via candidates. + auto candidate_vias = makeCandidateVias(search_engine_data, facade, phantom_node_pair); + + const auto by_weight = [](const auto &lhs, const auto &rhs) { return lhs.weight < rhs.weight; }; + auto shortest_path_via_it = + std::min_element(begin(candidate_vias), end(candidate_vias), by_weight); + + const auto has_shortest_path = shortest_path_via_it != end(candidate_vias) && + shortest_path_via_it->node != SPECIAL_NODEID && + shortest_path_via_it->weight != INVALID_EDGE_WEIGHT; + + if (!has_shortest_path) + return InternalManyRoutesResult{}; + + NodeID shortest_path_via = shortest_path_via_it->node; + EdgeWeight shortest_path_weight = shortest_path_via_it->weight; + + // Filters via candidate nodes with heuristics + + // Note: filter pipeline below only makes range smaller; no need to erase items + // from the vector when we can mutate in-place and for filtering adjust iterators. + auto it = end(candidate_vias); + + it = filterViaCandidatesByUniqueNodeIds(begin(candidate_vias), it); + it = filterViaCandidatesByRoadImportance(begin(candidate_vias), it, facade); + it = filterViaCandidatesByStretch( + begin(candidate_vias), it, shortest_path_weight, facade.GetWeightMultiplier()); + + // Pre-rank by weight; sharing filtering below then discards by similarity. + std::sort(begin(candidate_vias), it, [](const auto lhs, const auto rhs) { + return lhs.weight < rhs.weight; + }); + + // Filtered and ranked candidate range + const auto candidate_vias_first = begin(candidate_vias); + const auto candidate_vias_last = it; + const auto number_of_candidate_vias = candidate_vias_last - candidate_vias_first; + + // Reconstruct packed paths from the heaps. + // The recursive path unpacking below destructs heaps. + // We need to save all packed paths from the heaps upfront. + + const auto extract_packed_path_from_heaps = [&](WeightedViaNode via) { + auto packed_path = retrievePackedPathFromHeap(forward_heap, reverse_heap, via.node); + auto path_weights = + retrievePackedPathWeightsFromHeap(forward_heap, reverse_heap, packed_path, via); + + return WeightedViaNodePackedPath{std::move(via), // + std::move(packed_path), // + std::move(path_weights)}; // + }; + + std::vector weighted_packed_paths; + weighted_packed_paths.reserve(1 + number_of_candidate_vias); + + // Store shortest path + WeightedViaNode shortest_path_weighted_via{shortest_path_via, shortest_path_weight}; + weighted_packed_paths.push_back(extract_packed_path_from_heaps(shortest_path_weighted_via)); + + const auto last_filtered = filterViaCandidatesByViaNotOnPath( + weighted_packed_paths[0], candidate_vias_first + 1, candidate_vias_last); + + // Store all alternative packed paths (if there are any). + auto into = std::back_inserter(weighted_packed_paths); + std::transform(begin(candidate_vias) + 1, last_filtered, into, extract_packed_path_from_heaps); + + // Filter packed paths with heuristics + + auto alternative_paths_last = end(weighted_packed_paths); + + alternative_paths_last = filterPackedPathsByLocalOptimality(weighted_packed_paths[0], + forward_heap, // paths for s, via + reverse_heap, // paths for via, t + begin(weighted_packed_paths) + 1, + alternative_paths_last); + + alternative_paths_last = filterPackedPathsByCellSharing(begin(weighted_packed_paths), // + end(weighted_packed_paths), // + partition); // + + BOOST_ASSERT(weighted_packed_paths.size() >= 1); + + const auto number_of_filtered_alternative_paths = std::min( + static_cast(max_number_of_alternatives_to_unpack), + static_cast(alternative_paths_last - (begin(weighted_packed_paths) + 1))); + + const auto paths_first = begin(weighted_packed_paths); + const auto paths_last = begin(weighted_packed_paths) + 1 + number_of_filtered_alternative_paths; + const auto number_of_packed_paths = paths_last - paths_first; + + std::vector unpacked_paths; + unpacked_paths.reserve(number_of_packed_paths); + + // Note: re-uses (read: destroys) heaps; we don't need them from here on anyway. + unpackPackedPaths(paths_first, + paths_last, + std::back_inserter(unpacked_paths), + search_engine_data, + facade, + phantom_node_pair); + + // + // Filter and rank a second time. This time instead of being fast and doing + // heuristics on the packed path only we now have the detailed unpacked path. + // + + auto unpacked_paths_last = end(unpacked_paths); + + unpacked_paths_last = filterUnpackedPathsBySharing(begin(unpacked_paths), end(unpacked_paths)); + + const auto unpacked_paths_first = begin(unpacked_paths); + const auto number_of_unpacked_paths = + std::min(static_cast(max_number_of_alternatives) + 1, + static_cast(unpacked_paths_last - unpacked_paths_first)); + BOOST_ASSERT(number_of_unpacked_paths >= 1); + unpacked_paths_last = unpacked_paths_first + number_of_unpacked_paths; + + // + // Annotate the unpacked path and transform to proper internal route result. + // + + std::vector routes; + routes.reserve(number_of_unpacked_paths); + + const auto unpacked_path_to_route = [&](const WeightedViaNodeUnpackedPath &path) { + return extractRoute(facade, path.via.weight, phantom_node_pair, path.nodes, path.edges); + }; + + std::transform(unpacked_paths_first, + unpacked_paths_last, + std::back_inserter(routes), + unpacked_path_to_route); + + BOOST_ASSERT(routes.size() >= 1); + + // Only now that we annotated the routes do we have their actual duration. + + const auto routes_first = begin(routes); + auto routes_last = end(routes); + + if (routes.size() > 1) + { + routes_last = filterAnnotatedRoutesByStretch(routes_first + 1, routes_last, *routes_first); + routes.erase(routes_last, end(routes)); + } + + BOOST_ASSERT(routes.size() >= 1); + return InternalManyRoutesResult{std::move(routes)}; +} + +} // namespace routing_algorithms +} // namespace engine +} // namespace osrm diff --git a/src/engine/routing_algorithms/direct_shortest_path.cpp b/src/engine/routing_algorithms/direct_shortest_path.cpp index 671ea1308..9927cffec 100644 --- a/src/engine/routing_algorithms/direct_shortest_path.cpp +++ b/src/engine/routing_algorithms/direct_shortest_path.cpp @@ -11,39 +11,6 @@ namespace engine namespace routing_algorithms { -template -InternalRouteResult -extractRoute(const datafacade::ContiguousInternalMemoryDataFacade &facade, - const EdgeWeight weight, - const PhantomNodes &phantom_nodes, - const std::vector &unpacked_nodes, - const std::vector &unpacked_edges) -{ - InternalRouteResult raw_route_data; - raw_route_data.segment_end_coordinates = {phantom_nodes}; - - // No path found for both target nodes? - if (INVALID_EDGE_WEIGHT == weight) - { - return raw_route_data; - } - - raw_route_data.shortest_path_weight = weight; - raw_route_data.unpacked_path_segments.resize(1); - raw_route_data.source_traversed_in_reverse.push_back( - (unpacked_nodes.front() != phantom_nodes.source_phantom.forward_segment_id.id)); - raw_route_data.target_traversed_in_reverse.push_back( - (unpacked_nodes.back() != phantom_nodes.target_phantom.forward_segment_id.id)); - - annotatePath(facade, - phantom_nodes, - unpacked_nodes, - unpacked_edges, - raw_route_data.unpacked_path_segments.front()); - - return raw_route_data; -} - /// This is a striped down version of the general shortest path algorithm. /// The general algorithm always computes two queries for each leg. This is only /// necessary in case of vias, where the directions of the start node is constrainted diff --git a/src/engine/routing_algorithms/routing_base.cpp b/src/engine/routing_algorithms/routing_base.cpp index 4a1140e9e..fb01472bb 100644 --- a/src/engine/routing_algorithms/routing_base.cpp +++ b/src/engine/routing_algorithms/routing_base.cpp @@ -23,6 +23,16 @@ bool needsLoopBackwards(const PhantomNode &source_phantom, const PhantomNode &ta target_phantom.GetReverseWeightPlusOffset(); } +bool needsLoopForward(const PhantomNodes &phantoms) +{ + return needsLoopForward(phantoms.source_phantom, phantoms.target_phantom); +} + +bool needsLoopBackwards(const PhantomNodes &phantoms) +{ + return needsLoopBackwards(phantoms.source_phantom, phantoms.target_phantom); +} + } // namespace routing_algorithms } // namespace engine } // namespace osrm diff --git a/src/nodejs/node_osrm.cpp b/src/nodejs/node_osrm.cpp index 9406fa4da..8605fea5f 100644 --- a/src/nodejs/node_osrm.cpp +++ b/src/nodejs/node_osrm.cpp @@ -185,8 +185,9 @@ inline void async(const Nan::FunctionCallbackInfo &info, * Can be `null` or an array of `[{value},{range}]` with `integer 0 .. 360,integer 0 .. 180`. * @param {Array} [options.radiuses] Limits the coordinate snapping to streets in the given radius in meters. Can be `null` (unlimited, default) or `double >= 0`. * @param {Array} [options.hints] Hints for the coordinate snapping. Array of base64 encoded strings. - * @param {Boolean} [options.alternatives=false] Search for alternative routes and return as well. - * *Please note that even if an alternative route is requested, a result cannot be guaranteed.* + * @param {Boolean} [options.alternatives=false] Search for alternative routes. + * @param {Number} [options.alternatives=0] Search for up to this many alternative routes. + * *Please note that even if alternative routes are requested, a result cannot be guaranteed.* * @param {Boolean} [options.steps=false] Return route steps for each route leg. * @param {Array|Boolean} [options.annotations=false] An array with strings of `duration`, `nodes`, `distance`, `weight`, `datasources`, `speed` or boolean for enabling/disabling all. * @param {String} [options.geometries=polyline] Returned route geometry format (influences overview and per step). Can also be `geojson`. diff --git a/src/tools/routed.cpp b/src/tools/routed.cpp index 3623de95d..92b01db1b 100644 --- a/src/tools/routed.cpp +++ b/src/tools/routed.cpp @@ -9,6 +9,7 @@ #include "osrm/osrm.hpp" #include "osrm/storage_config.hpp" +#include #include #include #include @@ -51,13 +52,15 @@ const static unsigned INIT_OK_START_ENGINE = 0; const static unsigned INIT_OK_DO_NOT_START_ENGINE = 1; const static unsigned INIT_FAILED = -1; -EngineConfig::Algorithm stringToAlgorithm(const std::string &algorithm) +static EngineConfig::Algorithm stringToAlgorithm(std::string algorithm) { - if (algorithm == "CH") + boost::to_lower(algorithm); + + if (algorithm == "ch") return EngineConfig::Algorithm::CH; - if (algorithm == "CoreCH") + if (algorithm == "corech") return EngineConfig::Algorithm::CoreCH; - if (algorithm == "MLD") + if (algorithm == "mld") return EngineConfig::Algorithm::MLD; throw util::RuntimeError(algorithm, ErrorCode::UnknownAlgorithm, SOURCE_REF); } @@ -76,7 +79,8 @@ inline unsigned generateServerProgramOptions(const int argc, int &max_locations_viaroute, int &max_locations_distance_table, int &max_locations_map_matching, - int &max_results_nearest) + int &max_results_nearest, + int &max_alternatives) { using boost::program_options::value; using boost::filesystem::path; @@ -119,7 +123,10 @@ inline unsigned generateServerProgramOptions(const int argc, "Max. locations supported in map matching query") // ("max-nearest-size", value(&max_results_nearest)->default_value(100), - "Max. results supported in nearest query"); + "Max. results supported in nearest query") // + ("max-alternatives", + value(&max_alternatives)->default_value(3), + "Max. number of alternatives supported in the MLD route query"); // hidden options, will be allowed on command line, but will not be shown to the user boost::program_options::options_description hidden_options("Hidden options"); @@ -210,7 +217,8 @@ int main(int argc, const char *argv[]) try config.max_locations_viaroute, config.max_locations_distance_table, config.max_locations_map_matching, - config.max_results_nearest); + config.max_results_nearest, + config.max_alternatives); if (init_result == INIT_OK_DO_NOT_START_ENGINE) { return EXIT_SUCCESS; diff --git a/test/nodejs/route.js b/test/nodejs/route.js index cc5e4bafe..37d3459cb 100644 --- a/test/nodejs/route.js +++ b/test/nodejs/route.js @@ -55,7 +55,7 @@ test('route: throws with too few or invalid args', function(assert) { }); test('route: provides no alternatives by default, but when requested it may (not guaranteed)', function(assert) { - assert.plan(6); + assert.plan(9); var osrm = new OSRM(monaco_path); var options = {coordinates: two_test_coordinates}; @@ -70,6 +70,12 @@ test('route: provides no alternatives by default, but when requested it may (not assert.ok(route.routes); assert.ok(route.routes.length >= 1); }); + options.alternatives = 3; + osrm.route(options, function(err, route) { + assert.ifError(err); + assert.ok(route.routes); + assert.ok(route.routes.length >= 1); + }); }); test('route: throws with bad params', function(assert) { @@ -542,4 +548,4 @@ test('route: throws on bad approaches', function(assert) { approaches: [10, 15] }, function(err, route) {}) }, /Approach must be a string: \[curb, unrestricted\] or null/); -}); \ No newline at end of file +}); diff --git a/unit_tests/server/parameters_parser.cpp b/unit_tests/server/parameters_parser.cpp index 96473fc0c..39f026082 100644 --- a/unit_tests/server/parameters_parser.cpp +++ b/unit_tests/server/parameters_parser.cpp @@ -59,6 +59,8 @@ BOOST_AUTO_TEST_CASE(invalid_route_urls) 32L); BOOST_CHECK_EQUAL( testInvalidOptions("1,2;3,4?overview=false&alternatives=foo"), 36UL); + BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4?overview=false&alternatives=-1"), + 36UL); BOOST_CHECK_EQUAL(testInvalidOptions(""), 0); BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3.4.unsupported"), 7); BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4.json?nooptions"), 13); @@ -118,6 +120,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) RouteParameters reference_2{}; reference_2.alternatives = true; + reference_2.number_of_alternatives = 1; reference_2.steps = true; reference_2.annotations = true; reference_2.coordinates = coords_1; @@ -127,6 +130,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK(result_2); BOOST_CHECK_EQUAL(reference_2.steps, result_2->steps); BOOST_CHECK_EQUAL(reference_2.alternatives, result_2->alternatives); + BOOST_CHECK_EQUAL(reference_2.number_of_alternatives, result_2->number_of_alternatives); BOOST_CHECK_EQUAL(reference_2.geometries, result_2->geometries); BOOST_CHECK_EQUAL(reference_2.annotations, result_2->annotations); BOOST_CHECK_EQUAL(reference_2.overview, result_2->overview); @@ -151,6 +155,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK(result_3); BOOST_CHECK_EQUAL(reference_3.steps, result_3->steps); BOOST_CHECK_EQUAL(reference_3.alternatives, result_3->alternatives); + BOOST_CHECK_EQUAL(reference_3.number_of_alternatives, result_3->number_of_alternatives); BOOST_CHECK_EQUAL(reference_3.geometries, result_3->geometries); BOOST_CHECK_EQUAL(reference_3.annotations, result_3->annotations); BOOST_CHECK_EQUAL(reference_3.overview, result_3->overview); @@ -428,6 +433,44 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) CHECK_EQUAL_RANGE(reference_18.approaches, result_18->approaches); CHECK_EQUAL_RANGE(reference_18.coordinates, result_18->coordinates); CHECK_EQUAL_RANGE(reference_18.hints, result_18->hints); + + RouteParameters reference_19{}; + reference_19.alternatives = true; + reference_19.number_of_alternatives = 3; + reference_19.coordinates = coords_1; + auto result_19 = parseParameters("1,2;3,4?alternatives=3"); + BOOST_CHECK(result_19); + BOOST_CHECK_EQUAL(reference_19.steps, result_19->steps); + BOOST_CHECK_EQUAL(reference_19.alternatives, result_19->alternatives); + BOOST_CHECK_EQUAL(reference_19.number_of_alternatives, result_19->number_of_alternatives); + BOOST_CHECK_EQUAL(reference_19.geometries, result_19->geometries); + BOOST_CHECK_EQUAL(reference_19.annotations, result_19->annotations); + BOOST_CHECK_EQUAL(reference_19.overview, result_19->overview); + BOOST_CHECK_EQUAL(reference_19.continue_straight, result_19->continue_straight); + CHECK_EQUAL_RANGE(reference_19.bearings, result_19->bearings); + CHECK_EQUAL_RANGE(reference_19.radiuses, result_19->radiuses); + CHECK_EQUAL_RANGE(reference_19.approaches, result_19->approaches); + CHECK_EQUAL_RANGE(reference_19.coordinates, result_19->coordinates); + CHECK_EQUAL_RANGE(reference_19.hints, result_19->hints); + + RouteParameters reference_20{}; + reference_20.alternatives = false; + reference_20.number_of_alternatives = 0; + reference_20.coordinates = coords_1; + auto result_20 = parseParameters("1,2;3,4?alternatives=0"); + BOOST_CHECK(result_20); + BOOST_CHECK_EQUAL(reference_20.steps, result_20->steps); + BOOST_CHECK_EQUAL(reference_20.alternatives, result_20->alternatives); + BOOST_CHECK_EQUAL(reference_20.number_of_alternatives, result_20->number_of_alternatives); + BOOST_CHECK_EQUAL(reference_20.geometries, result_20->geometries); + BOOST_CHECK_EQUAL(reference_20.annotations, result_20->annotations); + BOOST_CHECK_EQUAL(reference_20.overview, result_20->overview); + BOOST_CHECK_EQUAL(reference_20.continue_straight, result_20->continue_straight); + CHECK_EQUAL_RANGE(reference_20.bearings, result_20->bearings); + CHECK_EQUAL_RANGE(reference_20.radiuses, result_20->radiuses); + CHECK_EQUAL_RANGE(reference_20.approaches, result_20->approaches); + CHECK_EQUAL_RANGE(reference_20.coordinates, result_20->coordinates); + CHECK_EQUAL_RANGE(reference_20.hints, result_20->hints); } BOOST_AUTO_TEST_CASE(valid_table_urls)