Implements Alternatives for MLD
This commit is contained in:
parent
fef0344be0
commit
54ceb05420
12
CHANGELOG.md
12
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
|
||||
|
@ -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**
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -93,6 +93,9 @@ template <> struct HasGetTileTurns<corech::Algorithm> final : std::true_type
|
||||
};
|
||||
|
||||
// Algorithms supported by Multi-Level Dijkstra
|
||||
template <> struct HasAlternativePathSearch<mld::Algorithm> final : std::true_type
|
||||
{
|
||||
};
|
||||
template <> struct HasDirectShortestPathSearch<mld::Algorithm> final : std::true_type
|
||||
{
|
||||
};
|
||||
|
@ -89,8 +89,9 @@ struct RouteParameters : public BaseParameters
|
||||
const boost::optional<bool> continue_straight_,
|
||||
Args... args_)
|
||||
: BaseParameters{std::forward<Args>(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<bool> continue_straight_,
|
||||
Args... args_)
|
||||
: BaseParameters{std::forward<Args>(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<bool> continue_straight_,
|
||||
Args... args_)
|
||||
: BaseParameters{std::forward<Args>(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<bool> 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)
|
||||
|
@ -53,12 +53,12 @@ template <typename Algorithm> 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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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<PhantomNodes> &phantom_node_pair,
|
||||
@ -66,7 +67,8 @@ template <typename Algorithm> 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<PhantomNodes> &phantom_node_pair,
|
||||
@ -130,9 +132,11 @@ template <typename Algorithm> class RoutingAlgorithms final : public RoutingAlgo
|
||||
|
||||
template <typename Algorithm>
|
||||
InternalManyRoutesResult
|
||||
RoutingAlgorithms<Algorithm>::AlternativePathSearch(const PhantomNodes &phantom_node_pair) const
|
||||
RoutingAlgorithms<Algorithm>::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 <typename Algorithm>
|
||||
@ -189,7 +193,8 @@ inline std::vector<routing_algorithms::TurnData> RoutingAlgorithms<Algorithm>::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<routing_algorithms::corech::Algorithm>::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
|
||||
|
@ -15,13 +15,19 @@ namespace engine
|
||||
{
|
||||
namespace routing_algorithms
|
||||
{
|
||||
namespace ch
|
||||
{
|
||||
|
||||
InternalManyRoutesResult
|
||||
alternativePathSearch(SearchEngineData<Algorithm> &search_engine_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
||||
const PhantomNodes &phantom_node_pair);
|
||||
} // namespace ch
|
||||
alternativePathSearch(SearchEngineData<ch::Algorithm> &search_engine_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<ch::Algorithm> &facade,
|
||||
const PhantomNodes &phantom_node_pair,
|
||||
unsigned number_of_alternatives);
|
||||
|
||||
InternalManyRoutesResult
|
||||
alternativePathSearch(SearchEngineData<mld::Algorithm> &search_engine_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<mld::Algorithm> &facade,
|
||||
const PhantomNodes &phantom_node_pair,
|
||||
unsigned number_of_alternatives);
|
||||
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
} // namespace osrm
|
||||
|
@ -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 <typename Heap>
|
||||
void insertNodesInHeaps(Heap &forward_heap, Heap &reverse_heap, const PhantomNodes &nodes)
|
||||
{
|
||||
@ -369,6 +372,39 @@ double getPathDistance(const datafacade::ContiguousInternalMemoryDataFacade<Algo
|
||||
return distance;
|
||||
}
|
||||
|
||||
template <typename AlgorithmT>
|
||||
InternalRouteResult
|
||||
extractRoute(const datafacade::ContiguousInternalMemoryDataFacade<AlgorithmT> &facade,
|
||||
const EdgeWeight weight,
|
||||
const PhantomNodes &phantom_nodes,
|
||||
const std::vector<NodeID> &unpacked_nodes,
|
||||
const std::vector<EdgeID> &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
|
||||
|
@ -10,6 +10,12 @@
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
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</*from*/ NodeID, /*to*/ NodeID, /*from_clique_arc*/ bool>;
|
||||
using PackedPath = std::vector<PackedEdge>;
|
||||
|
||||
template <bool DIRECTION, typename OutIter>
|
||||
inline void retrievePackedPathFromSingleHeap(const SearchEngineData<Algorithm>::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 <bool DIRECTION>
|
||||
inline PackedPath
|
||||
retrievePackedPathFromSingleHeap(const SearchEngineData<Algorithm>::QueryHeap &heap,
|
||||
const NodeID middle)
|
||||
{
|
||||
PackedPath packed_path;
|
||||
retrievePackedPathFromSingleHeap<DIRECTION>(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<Algorithm>::QueryHeap &forward_heap,
|
||||
const SearchEngineData<Algorithm>::QueryHeap &reverse_heap,
|
||||
const NodeID middle)
|
||||
{
|
||||
// Retrieve start -> middle. Is in reverse order since tracing back starts from middle.
|
||||
auto packed_path = retrievePackedPathFromSingleHeap<FORWARD_DIRECTION>(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_DIRECTION>(reverse_heap, middle, into);
|
||||
|
||||
return packed_path;
|
||||
}
|
||||
|
||||
template <bool DIRECTION, typename... Args>
|
||||
void routingStep(const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
||||
SearchEngineData<Algorithm>::QueryHeap &forward_heap,
|
||||
@ -96,7 +166,7 @@ void routingStep(const datafacade::ContiguousInternalMemoryDataFacade<Algorithm>
|
||||
}
|
||||
}
|
||||
|
||||
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<Algorithm>
|
||||
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<Algorithm>
|
||||
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<Algorithm>
|
||||
}
|
||||
}
|
||||
|
||||
// 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<NodeID>;
|
||||
using UnpackedEdges = std::vector<EdgeID>;
|
||||
using UnpackedPath = std::tuple<EdgeWeight, UnpackedNodes, UnpackedEdges>;
|
||||
|
||||
template <typename... Args>
|
||||
std::tuple<EdgeWeight, std::vector<NodeID>, std::vector<EdgeID>>
|
||||
search(SearchEngineData<Algorithm> &engine_working_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
||||
SearchEngineData<Algorithm>::QueryHeap &forward_heap,
|
||||
SearchEngineData<Algorithm>::QueryHeap &reverse_heap,
|
||||
const bool force_loop_forward,
|
||||
const bool force_loop_reverse,
|
||||
EdgeWeight weight_upper_bound,
|
||||
Args... args)
|
||||
UnpackedPath search(SearchEngineData<Algorithm> &engine_working_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
||||
SearchEngineData<Algorithm>::QueryHeap &forward_heap,
|
||||
SearchEngineData<Algorithm>::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<Algorithm> &engine_working_data,
|
||||
return std::make_tuple(INVALID_EDGE_WEIGHT, std::vector<NodeID>(), std::vector<EdgeID>());
|
||||
}
|
||||
|
||||
// Get packed path as edges {from node ID, to node ID, edge ID}
|
||||
std::vector<std::tuple<NodeID, NodeID, bool>> 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<NodeID> unpacked_nodes;
|
||||
@ -271,6 +336,7 @@ search(SearchEngineData<Algorithm> &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<Algorithm> &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));
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <algorithm>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/numeric/conversion/cast.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
@ -109,7 +110,6 @@ struct ProfileProperties
|
||||
std::array<char[MAX_CLASS_NAME_LENGTH + 1], MAX_CLASS_INDEX + 1> class_names;
|
||||
unsigned weight_precision = 1;
|
||||
bool force_split_edges = false;
|
||||
|
||||
bool call_tagless_node_function = true;
|
||||
};
|
||||
}
|
||||
|
@ -703,7 +703,7 @@ argumentsToRouteParameter(const Nan::FunctionCallbackInfo<v8::Value> &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<v8::Value> &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<unsigned>(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);
|
||||
|
@ -30,7 +30,13 @@ struct RouteParametersGrammar : public BaseParametersGrammar<Iterator, Signature
|
||||
{
|
||||
route_rule =
|
||||
(qi::lit("alternatives=") >
|
||||
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) =
|
||||
|
26
include/util/static_assert.hpp
Normal file
26
include/util/static_assert.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef OSRM_STATIC_ASSERT_HPP
|
||||
#define OSRM_STATIC_ASSERT_HPP
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
|
||||
template <typename It, typename Value> inline void static_assert_iter_value()
|
||||
{
|
||||
using IterValueType = typename std::iterator_traits<It>::value_type;
|
||||
static_assert(std::is_same<IterValueType, Value>::value, "");
|
||||
}
|
||||
|
||||
template <typename It, typename Category> inline void static_assert_iter_category()
|
||||
{
|
||||
using IterCategoryType = typename std::iterator_traits<It>::iterator_category;
|
||||
static_assert(std::is_base_of<Category, IterCategoryType>::value, "");
|
||||
}
|
||||
|
||||
} // ns util
|
||||
} // ns osrm
|
||||
|
||||
#endif // OSRM_STATIC_ASSERT_HPP
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<unsigned>(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())
|
||||
{
|
||||
|
@ -8,9 +8,7 @@
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<false>(facade, node);
|
||||
const auto loop_weight = getLoopWeight<false>(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<NodeID> 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 <s,..,v> by reusing forward search from s
|
||||
while (!new_reverse_heap.Empty())
|
||||
{
|
||||
ch::routingStep<REVERSE_DIRECTION>(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<REVERSE_DIRECTION>(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 <v,..,t> 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<FORWARD_DIRECTION>(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<FORWARD_DIRECTION>(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<REVERSE_DIRECTION>(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<REVERSE_DIRECTION>(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<FORWARD_DIRECTION>(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<FORWARD_DIRECTION>(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<FORWARD_DIRECTION>(facade,
|
||||
forward_heap3,
|
||||
reverse_heap3,
|
||||
middle,
|
||||
upper_bound,
|
||||
min_edge_offset,
|
||||
DO_NOT_FORCE_LOOPS,
|
||||
DO_NOT_FORCE_LOOPS);
|
||||
routingStep<FORWARD_DIRECTION>(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<REVERSE_DIRECTION>(facade,
|
||||
reverse_heap3,
|
||||
forward_heap3,
|
||||
middle,
|
||||
upper_bound,
|
||||
min_edge_offset,
|
||||
DO_NOT_FORCE_LOOPS,
|
||||
DO_NOT_FORCE_LOOPS);
|
||||
routingStep<REVERSE_DIRECTION>(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<Algorithm> &engine_working_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &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<Algorithm> &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<Algorithm> &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<Algorithm> &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<Algorithm> &engine_working_data,
|
||||
return InternalManyRoutesResult{{std::move(primary_route), std::move(secondary_route)}};
|
||||
}
|
||||
|
||||
} // namespace ch
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
} // namespace osrm}
|
936
src/engine/routing_algorithms/alternative_path_mld.cpp
Normal file
936
src/engine/routing_algorithms/alternative_path_mld.cpp
Normal file
@ -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 <boost/assert.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/function_output_iterator.hpp>
|
||||
|
||||
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<Algorithm>::QueryHeap;
|
||||
using Partition = partition::MultiLevelPartitionView;
|
||||
using Facade = datafacade::ContiguousInternalMemoryDataFacade<Algorithm>;
|
||||
|
||||
// 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<EdgeWeight> 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 <typename RandIt> RandIt filterViaCandidatesByUniqueNodeIds(RandIt first, RandIt last)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, WeightedViaNode>();
|
||||
|
||||
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 <typename RandIt>
|
||||
RandIt filterViaCandidatesByRoadImportance(RandIt first, RandIt last, const Facade &facade)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, WeightedViaNode>();
|
||||
|
||||
// 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 <typename RandIt>
|
||||
RandIt
|
||||
filterViaCandidatesByStretch(RandIt first, RandIt last, EdgeWeight weight, double weight_multiplier)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, WeightedViaNode>();
|
||||
|
||||
// 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 <typename RandIt>
|
||||
RandIt
|
||||
filterViaCandidatesByViaNotOnPath(const WeightedViaNodePackedPath &path, RandIt first, RandIt last)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, WeightedViaNode>();
|
||||
|
||||
if (path.path.empty())
|
||||
return last;
|
||||
|
||||
std::unordered_set<NodeID> 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 <typename RandIt>
|
||||
RandIt filterPackedPathsByCellSharing(RandIt first, RandIt last, const Partition &partition)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, WeightedViaNodePackedPath>();
|
||||
|
||||
// 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<std::size_t>(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<CellID> 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<double>(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 <typename RandIt>
|
||||
RandIt filterPackedPathsByLocalOptimality(const WeightedViaNodePackedPath &path,
|
||||
const Heap &forward_heap,
|
||||
const Heap &reverse_heap,
|
||||
RandIt first,
|
||||
RandIt last)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, WeightedViaNodePackedPath>();
|
||||
|
||||
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 <typename RandIt> RandIt filterUnpackedPathsBySharing(RandIt first, RandIt last)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, WeightedViaNodeUnpackedPath>();
|
||||
|
||||
const auto size = static_cast<std::size_t>(last - first);
|
||||
|
||||
if (size < 2)
|
||||
return last;
|
||||
|
||||
const auto &shortest_path = *first;
|
||||
|
||||
if (shortest_path.edges.empty())
|
||||
return last;
|
||||
|
||||
std::unordered_set<EdgeID> 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<double>(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 <typename RandIt>
|
||||
RandIt
|
||||
filterAnnotatedRoutesByStretch(RandIt first, RandIt last, const InternalRouteResult &shortest_route)
|
||||
{
|
||||
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
|
||||
util::static_assert_iter_value<RandIt, InternalRouteResult>();
|
||||
|
||||
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 <typename InputIt, typename OutIt>
|
||||
void unpackPackedPaths(InputIt first,
|
||||
InputIt last,
|
||||
OutIt out,
|
||||
SearchEngineData<Algorithm> &search_engine_data,
|
||||
const Facade &facade,
|
||||
const PhantomNodes &phantom_node_pair)
|
||||
{
|
||||
util::static_assert_iter_category<InputIt, std::input_iterator_tag>();
|
||||
util::static_assert_iter_category<OutIt, std::output_iterator_tag>();
|
||||
util::static_assert_iter_value<InputIt, WeightedViaNodePackedPath>();
|
||||
|
||||
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<NodeID> unpacked_nodes;
|
||||
std::vector<EdgeID> 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<NodeID> subpath_nodes;
|
||||
std::vector<EdgeID> 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<WeightedViaNode>
|
||||
makeCandidateVias(SearchEngineData<Algorithm> &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<WeightedViaNode> 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<FORWARD_DIRECTION>(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<REVERSE_DIRECTION>(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<EdgeWeight> retrievePackedPathWeightsFromHeap(const Heap &forward_heap,
|
||||
const Heap &reverse_heap,
|
||||
const PackedPath &packed_path,
|
||||
const WeightedViaNode via)
|
||||
{
|
||||
if (packed_path.empty())
|
||||
return {};
|
||||
|
||||
std::vector<EdgeWeight> 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<Algorithm> &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<WeightedViaNodePackedPath> 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<std::size_t>(max_number_of_alternatives_to_unpack),
|
||||
static_cast<std::size_t>(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<WeightedViaNodeUnpackedPath> 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<std::size_t>(max_number_of_alternatives) + 1,
|
||||
static_cast<std::size_t>(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<InternalRouteResult> 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
|
@ -11,39 +11,6 @@ namespace engine
|
||||
namespace routing_algorithms
|
||||
{
|
||||
|
||||
template <typename AlgorithmT>
|
||||
InternalRouteResult
|
||||
extractRoute(const datafacade::ContiguousInternalMemoryDataFacade<AlgorithmT> &facade,
|
||||
const EdgeWeight weight,
|
||||
const PhantomNodes &phantom_nodes,
|
||||
const std::vector<NodeID> &unpacked_nodes,
|
||||
const std::vector<EdgeID> &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
|
||||
|
@ -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
|
||||
|
@ -185,8 +185,9 @@ inline void async(const Nan::FunctionCallbackInfo<v8::Value> &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`.
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "osrm/osrm.hpp"
|
||||
#include "osrm/storage_config.hpp"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/any.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
@ -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<int>(&max_results_nearest)->default_value(100),
|
||||
"Max. results supported in nearest query");
|
||||
"Max. results supported in nearest query") //
|
||||
("max-alternatives",
|
||||
value<int>(&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;
|
||||
|
@ -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/);
|
||||
});
|
||||
});
|
||||
|
@ -59,6 +59,8 @@ BOOST_AUTO_TEST_CASE(invalid_route_urls)
|
||||
32L);
|
||||
BOOST_CHECK_EQUAL(
|
||||
testInvalidOptions<RouteParameters>("1,2;3,4?overview=false&alternatives=foo"), 36UL);
|
||||
BOOST_CHECK_EQUAL(testInvalidOptions<RouteParameters>("1,2;3,4?overview=false&alternatives=-1"),
|
||||
36UL);
|
||||
BOOST_CHECK_EQUAL(testInvalidOptions<RouteParameters>(""), 0);
|
||||
BOOST_CHECK_EQUAL(testInvalidOptions<RouteParameters>("1,2;3.4.unsupported"), 7);
|
||||
BOOST_CHECK_EQUAL(testInvalidOptions<RouteParameters>("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<RouteParameters>("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<RouteParameters>("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)
|
||||
|
Loading…
Reference in New Issue
Block a user