Implements Alternatives for MLD

This commit is contained in:
Daniel J. Hofmann 2017-04-06 14:28:43 +02:00 committed by Patrick Niklaus
parent fef0344be0
commit 54ceb05420
28 changed files with 1404 additions and 238 deletions

View File

@ -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

View File

@ -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**

View File

@ -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

View File

@ -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
{
};

View File

@ -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)

View File

@ -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)

View File

@ -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;
};

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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));

View File

@ -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;
};
}

View File

@ -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);

View File

@ -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) =

View 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

View File

@ -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

View File

@ -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;
}

View File

@ -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())
{

View File

@ -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}

View 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

View File

@ -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

View File

@ -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

View File

@ -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`.

View File

@ -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;

View File

@ -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/);
});
});

View File

@ -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)