Implements Alternatives for MLD
This commit is contained in:
parent
fef0344be0
commit
54ceb05420
@ -3,13 +3,19 @@
|
|||||||
- Algorithm:
|
- Algorithm:
|
||||||
- Multi-Level Dijkstra:
|
- Multi-Level Dijkstra:
|
||||||
- Plugins supported: `table`
|
- 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:
|
- 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
|
- 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.
|
- `RouteStep` now has new parameter `classes` that can be set in the profile on each way.
|
||||||
- Profiles:
|
- 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)
|
- `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`.
|
- `ExtractionWay` now as new property `forward_classes` and `backward_classes` that can set in the `way_function`.
|
||||||
The maximum number of classes is 8.
|
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
|
# 5.8.0
|
||||||
- Changes from 5.7
|
- 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.
|
Finds the fastest route between coordinates in the supplied order.
|
||||||
|
|
||||||
```endpoint
|
```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:
|
In addition to the [general options](#general-options) the following options are supported for this service:
|
||||||
|
|
||||||
|Option |Values |Description |
|
|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 |
|
|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. |
|
|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) |
|
|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.|
|
|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. |
|
|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**
|
**Response**
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ 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:
|
The following global properties can be set in your profile:
|
||||||
|
|
||||||
Attribute | Type | Notes
|
Attribute | Type | Notes
|
||||||
------------------------------|----------|----------------------------------------------------------------------------
|
-------------------------------------|----------|----------------------------------------------------------------------------
|
||||||
weight_name | String | Name used in output for the routing weight property (default `'duration'`)
|
weight_name | String | Name used in output for the routing weight property (default `'duration'`)
|
||||||
weight_precision | Unsigned | Decimal precision of edge weights (default `1`)
|
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`)
|
left_hand_driving | Boolean | Are vehicles assumed to drive on the left? (used in guidance, default `false`)
|
||||||
|
@ -93,6 +93,9 @@ template <> struct HasGetTileTurns<corech::Algorithm> final : std::true_type
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Algorithms supported by Multi-Level Dijkstra
|
// Algorithms supported by Multi-Level Dijkstra
|
||||||
|
template <> struct HasAlternativePathSearch<mld::Algorithm> final : std::true_type
|
||||||
|
{
|
||||||
|
};
|
||||||
template <> struct HasDirectShortestPathSearch<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_,
|
const boost::optional<bool> continue_straight_,
|
||||||
Args... args_)
|
Args... args_)
|
||||||
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
|
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
|
||||||
annotations{false}, annotations_type{AnnotationsType::None}, geometries{geometries_},
|
number_of_alternatives{alternatives_ ? 1u : 0u}, annotations{false},
|
||||||
overview{overview_}, continue_straight{continue_straight_}
|
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.
|
// 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_,
|
const boost::optional<bool> continue_straight_,
|
||||||
Args... args_)
|
Args... args_)
|
||||||
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
|
: 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},
|
annotations_type{annotations_ ? AnnotationsType::All : AnnotationsType::None},
|
||||||
geometries{geometries_}, overview{overview_}, continue_straight{continue_straight_}
|
geometries{geometries_}, overview{overview_}, continue_straight{continue_straight_}
|
||||||
{
|
{
|
||||||
@ -121,6 +122,7 @@ struct RouteParameters : public BaseParameters
|
|||||||
const boost::optional<bool> continue_straight_,
|
const boost::optional<bool> continue_straight_,
|
||||||
Args... args_)
|
Args... args_)
|
||||||
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
|
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
|
||||||
|
number_of_alternatives{alternatives_ ? 1u : 0u},
|
||||||
annotations{annotations_ == AnnotationsType::None ? false : true},
|
annotations{annotations_ == AnnotationsType::None ? false : true},
|
||||||
annotations_type{annotations_}, geometries{geometries_}, overview{overview_},
|
annotations_type{annotations_}, geometries{geometries_}, overview{overview_},
|
||||||
continue_straight{continue_straight_}
|
continue_straight{continue_straight_}
|
||||||
@ -128,14 +130,21 @@ struct RouteParameters : public BaseParameters
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool steps = false;
|
bool steps = false;
|
||||||
|
// TODO: in v6 we should remove the boolean and only keep the number parameter; for compat.
|
||||||
bool alternatives = false;
|
bool alternatives = false;
|
||||||
|
unsigned number_of_alternatives = 0;
|
||||||
bool annotations = false;
|
bool annotations = false;
|
||||||
AnnotationsType annotations_type = AnnotationsType::None;
|
AnnotationsType annotations_type = AnnotationsType::None;
|
||||||
GeometriesType geometries = GeometriesType::Polyline;
|
GeometriesType geometries = GeometriesType::Polyline;
|
||||||
OverviewType overview = OverviewType::Simplified;
|
OverviewType overview = OverviewType::Simplified;
|
||||||
boost::optional<bool> continue_straight;
|
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)
|
inline bool operator&(RouteParameters::AnnotationsType lhs, RouteParameters::AnnotationsType rhs)
|
||||||
|
@ -53,7 +53,7 @@ template <typename Algorithm> class Engine final : public EngineInterface
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit Engine(const EngineConfig &config)
|
explicit Engine(const EngineConfig &config)
|
||||||
: route_plugin(config.max_locations_viaroute), //
|
: route_plugin(config.max_locations_viaroute, config.max_alternatives), //
|
||||||
table_plugin(config.max_locations_distance_table), //
|
table_plugin(config.max_locations_distance_table), //
|
||||||
nearest_plugin(config.max_results_nearest), //
|
nearest_plugin(config.max_results_nearest), //
|
||||||
trip_plugin(config.max_locations_trip), //
|
trip_plugin(config.max_locations_trip), //
|
||||||
|
@ -87,6 +87,7 @@ struct EngineConfig final
|
|||||||
int max_locations_distance_table = -1;
|
int max_locations_distance_table = -1;
|
||||||
int max_locations_map_matching = -1;
|
int max_locations_map_matching = -1;
|
||||||
int max_results_nearest = -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;
|
bool use_shared_memory = true;
|
||||||
Algorithm algorithm = Algorithm::CH;
|
Algorithm algorithm = Algorithm::CH;
|
||||||
};
|
};
|
||||||
|
@ -61,6 +61,18 @@ struct InternalRouteResult
|
|||||||
{
|
{
|
||||||
return (leg != unpacked_path_segments.size() - 1);
|
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
|
struct InternalManyRoutesResult
|
||||||
|
@ -27,9 +27,10 @@ class ViaRoutePlugin final : public BasePlugin
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
const int max_locations_viaroute;
|
const int max_locations_viaroute;
|
||||||
|
const int max_alternatives;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ViaRoutePlugin(int max_locations_viaroute);
|
explicit ViaRoutePlugin(int max_locations_viaroute, int max_alternatives);
|
||||||
|
|
||||||
Status HandleRequest(const datafacade::ContiguousInternalMemoryDataFacadeBase &facade,
|
Status HandleRequest(const datafacade::ContiguousInternalMemoryDataFacadeBase &facade,
|
||||||
const RoutingAlgorithmsInterface &algorithms,
|
const RoutingAlgorithmsInterface &algorithms,
|
||||||
|
@ -20,7 +20,8 @@ class RoutingAlgorithmsInterface
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual InternalManyRoutesResult
|
virtual InternalManyRoutesResult
|
||||||
AlternativePathSearch(const PhantomNodes &phantom_node_pair) const = 0;
|
AlternativePathSearch(const PhantomNodes &phantom_node_pair,
|
||||||
|
unsigned number_of_alternatives) const = 0;
|
||||||
|
|
||||||
virtual InternalRouteResult
|
virtual InternalRouteResult
|
||||||
ShortestPathSearch(const std::vector<PhantomNodes> &phantom_node_pair,
|
ShortestPathSearch(const std::vector<PhantomNodes> &phantom_node_pair,
|
||||||
@ -66,7 +67,8 @@ template <typename Algorithm> class RoutingAlgorithms final : public RoutingAlgo
|
|||||||
virtual ~RoutingAlgorithms() = default;
|
virtual ~RoutingAlgorithms() = default;
|
||||||
|
|
||||||
InternalManyRoutesResult
|
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(
|
InternalRouteResult ShortestPathSearch(
|
||||||
const std::vector<PhantomNodes> &phantom_node_pair,
|
const std::vector<PhantomNodes> &phantom_node_pair,
|
||||||
@ -130,9 +132,11 @@ template <typename Algorithm> class RoutingAlgorithms final : public RoutingAlgo
|
|||||||
|
|
||||||
template <typename Algorithm>
|
template <typename Algorithm>
|
||||||
InternalManyRoutesResult
|
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>
|
template <typename Algorithm>
|
||||||
@ -189,7 +193,8 @@ inline std::vector<routing_algorithms::TurnData> RoutingAlgorithms<Algorithm>::G
|
|||||||
// CoreCH overrides
|
// CoreCH overrides
|
||||||
template <>
|
template <>
|
||||||
InternalManyRoutesResult inline RoutingAlgorithms<
|
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");
|
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");
|
throw util::exception("ManyToManySearch is disabled due to performance reasons");
|
||||||
}
|
}
|
||||||
|
} // ns engine
|
||||||
// MLD overrides for not implemented
|
} // ns osrm
|
||||||
template <>
|
|
||||||
InternalManyRoutesResult inline RoutingAlgorithms<
|
|
||||||
routing_algorithms::mld::Algorithm>::AlternativePathSearch(const PhantomNodes &) const
|
|
||||||
{
|
|
||||||
throw util::exception("AlternativePathSearch is not implemented");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -15,13 +15,19 @@ namespace engine
|
|||||||
{
|
{
|
||||||
namespace routing_algorithms
|
namespace routing_algorithms
|
||||||
{
|
{
|
||||||
namespace ch
|
|
||||||
{
|
|
||||||
InternalManyRoutesResult
|
InternalManyRoutesResult
|
||||||
alternativePathSearch(SearchEngineData<Algorithm> &search_engine_data,
|
alternativePathSearch(SearchEngineData<ch::Algorithm> &search_engine_data,
|
||||||
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
const datafacade::ContiguousInternalMemoryDataFacade<ch::Algorithm> &facade,
|
||||||
const PhantomNodes &phantom_node_pair);
|
const PhantomNodes &phantom_node_pair,
|
||||||
} // namespace ch
|
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 routing_algorithms
|
||||||
} // namespace engine
|
} // namespace engine
|
||||||
} // namespace osrm
|
} // namespace osrm
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "engine/algorithm.hpp"
|
#include "engine/algorithm.hpp"
|
||||||
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
||||||
#include "engine/internal_route_result.hpp"
|
#include "engine/internal_route_result.hpp"
|
||||||
|
#include "engine/phantom_node.hpp"
|
||||||
#include "engine/search_engine_data.hpp"
|
#include "engine/search_engine_data.hpp"
|
||||||
|
|
||||||
#include "util/coordinate_calculation.hpp"
|
#include "util/coordinate_calculation.hpp"
|
||||||
@ -38,9 +39,11 @@ static constexpr bool REVERSE_DIRECTION = false;
|
|||||||
static constexpr bool DO_NOT_FORCE_LOOPS = false;
|
static constexpr bool DO_NOT_FORCE_LOOPS = false;
|
||||||
|
|
||||||
bool needsLoopForward(const PhantomNode &source_phantom, const PhantomNode &target_phantom);
|
bool needsLoopForward(const PhantomNode &source_phantom, const PhantomNode &target_phantom);
|
||||||
|
|
||||||
bool needsLoopBackwards(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>
|
template <typename Heap>
|
||||||
void insertNodesInHeaps(Heap &forward_heap, Heap &reverse_heap, const PhantomNodes &nodes)
|
void insertNodesInHeaps(Heap &forward_heap, Heap &reverse_heap, const PhantomNodes &nodes)
|
||||||
{
|
{
|
||||||
@ -369,6 +372,39 @@ double getPathDistance(const datafacade::ContiguousInternalMemoryDataFacade<Algo
|
|||||||
return distance;
|
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 routing_algorithms
|
||||||
} // namespace engine
|
} // namespace engine
|
||||||
} // namespace osrm
|
} // namespace osrm
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
|
|
||||||
#include <boost/assert.hpp>
|
#include <boost/assert.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
#include <limits>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace osrm
|
namespace osrm
|
||||||
{
|
{
|
||||||
namespace engine
|
namespace engine
|
||||||
@ -24,7 +30,7 @@ namespace
|
|||||||
// Unrestricted search (Args is const PhantomNodes &):
|
// Unrestricted search (Args is const PhantomNodes &):
|
||||||
// * use partition.GetQueryLevel to find the node query level based on source and target phantoms
|
// * use partition.GetQueryLevel to find the node query level based on source and target phantoms
|
||||||
// * allow to traverse all cells
|
// * allow to traverse all cells
|
||||||
inline LevelID getNodeQureyLevel(const partition::MultiLevelPartitionView &partition,
|
inline LevelID getNodeQueryLevel(const partition::MultiLevelPartitionView &partition,
|
||||||
NodeID node,
|
NodeID node,
|
||||||
const PhantomNodes &phantom_nodes)
|
const PhantomNodes &phantom_nodes)
|
||||||
{
|
{
|
||||||
@ -49,7 +55,7 @@ inline bool checkParentCellRestriction(CellID, const PhantomNodes &) { return tr
|
|||||||
// * use the fixed level for queries
|
// * use the fixed level for queries
|
||||||
// * check if the node cell is the same as the specified parent onr
|
// * check if the node cell is the same as the specified parent onr
|
||||||
inline LevelID
|
inline LevelID
|
||||||
getNodeQureyLevel(const partition::MultiLevelPartitionView &, NodeID, LevelID level, CellID)
|
getNodeQueryLevel(const partition::MultiLevelPartitionView &, NodeID, LevelID level, CellID)
|
||||||
{
|
{
|
||||||
return level;
|
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>
|
template <bool DIRECTION, typename... Args>
|
||||||
void routingStep(const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
void routingStep(const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
||||||
SearchEngineData<Algorithm>::QueryHeap &forward_heap,
|
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)
|
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)
|
if (shortcut_weight != INVALID_EDGE_WEIGHT && node != to)
|
||||||
{
|
{
|
||||||
const EdgeWeight to_weight = weight + shortcut_weight;
|
const EdgeWeight to_weight = weight + shortcut_weight;
|
||||||
|
BOOST_ASSERT(to_weight >= weight);
|
||||||
if (!forward_heap.WasInserted(to))
|
if (!forward_heap.WasInserted(to))
|
||||||
{
|
{
|
||||||
forward_heap.Insert(to, to_weight, {node, true});
|
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)
|
if (shortcut_weight != INVALID_EDGE_WEIGHT && node != to)
|
||||||
{
|
{
|
||||||
const EdgeWeight to_weight = weight + shortcut_weight;
|
const EdgeWeight to_weight = weight + shortcut_weight;
|
||||||
|
BOOST_ASSERT(to_weight >= weight);
|
||||||
if (!forward_heap.WasInserted(to))
|
if (!forward_heap.WasInserted(to))
|
||||||
{
|
{
|
||||||
forward_heap.Insert(to, to_weight, {node, true});
|
forward_heap.Insert(to, to_weight, {node, true});
|
||||||
@ -179,9 +251,17 @@ 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>
|
template <typename... Args>
|
||||||
std::tuple<EdgeWeight, std::vector<NodeID>, std::vector<EdgeID>>
|
UnpackedPath search(SearchEngineData<Algorithm> &engine_working_data,
|
||||||
search(SearchEngineData<Algorithm> &engine_working_data,
|
|
||||||
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
||||||
SearchEngineData<Algorithm>::QueryHeap &forward_heap,
|
SearchEngineData<Algorithm>::QueryHeap &forward_heap,
|
||||||
SearchEngineData<Algorithm>::QueryHeap &reverse_heap,
|
SearchEngineData<Algorithm>::QueryHeap &reverse_heap,
|
||||||
@ -242,27 +322,12 @@ search(SearchEngineData<Algorithm> &engine_working_data,
|
|||||||
return std::make_tuple(INVALID_EDGE_WEIGHT, std::vector<NodeID>(), std::vector<EdgeID>());
|
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}
|
// Get packed path as edges {from node ID, to node ID, from_clique_arc}
|
||||||
std::vector<std::tuple<NodeID, NodeID, bool>> packed_path;
|
auto packed_path = retrievePackedPathFromHeap(forward_heap, reverse_heap, middle);
|
||||||
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;
|
|
||||||
|
|
||||||
current_node = middle, parent_node = reverse_heap.GetData(middle).parent;
|
// Beware the edge case when start, middle, end are all the same.
|
||||||
while (parent_node != current_node)
|
// 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;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unpack path
|
// Unpack path
|
||||||
std::vector<NodeID> unpacked_nodes;
|
std::vector<NodeID> unpacked_nodes;
|
||||||
@ -271,6 +336,7 @@ search(SearchEngineData<Algorithm> &engine_working_data,
|
|||||||
unpacked_edges.reserve(packed_path.size());
|
unpacked_edges.reserve(packed_path.size());
|
||||||
|
|
||||||
unpacked_nodes.push_back(source_node);
|
unpacked_nodes.push_back(source_node);
|
||||||
|
|
||||||
for (auto const &packed_edge : packed_path)
|
for (auto const &packed_edge : packed_path)
|
||||||
{
|
{
|
||||||
NodeID source, target;
|
NodeID source, target;
|
||||||
@ -283,7 +349,7 @@ search(SearchEngineData<Algorithm> &engine_working_data,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // an overlay graph edge
|
{ // an overlay graph edge
|
||||||
LevelID level = getNodeQureyLevel(partition, source, args...);
|
LevelID level = getNodeQueryLevel(partition, source, args...);
|
||||||
CellID parent_cell_id = partition.GetCell(level, source);
|
CellID parent_cell_id = partition.GetCell(level, source);
|
||||||
BOOST_ASSERT(parent_cell_id == partition.GetCell(level, target));
|
BOOST_ASSERT(parent_cell_id == partition.GetCell(level, target));
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/assert.hpp>
|
#include <boost/assert.hpp>
|
||||||
#include <boost/numeric/conversion/cast.hpp>
|
#include <boost/numeric/conversion/cast.hpp>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace osrm
|
namespace osrm
|
||||||
{
|
{
|
||||||
@ -109,7 +110,6 @@ struct ProfileProperties
|
|||||||
std::array<char[MAX_CLASS_NAME_LENGTH + 1], MAX_CLASS_INDEX + 1> class_names;
|
std::array<char[MAX_CLASS_NAME_LENGTH + 1], MAX_CLASS_INDEX + 1> class_names;
|
||||||
unsigned weight_precision = 1;
|
unsigned weight_precision = 1;
|
||||||
bool force_split_edges = false;
|
bool force_split_edges = false;
|
||||||
|
|
||||||
bool call_tagless_node_function = true;
|
bool call_tagless_node_function = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -703,7 +703,7 @@ argumentsToRouteParameter(const Nan::FunctionCallbackInfo<v8::Value> &args,
|
|||||||
|
|
||||||
if (!value->IsBoolean() && !value->IsNull())
|
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();
|
return route_parameters_ptr();
|
||||||
}
|
}
|
||||||
if (value->IsBoolean())
|
if (value->IsBoolean())
|
||||||
@ -718,12 +718,21 @@ argumentsToRouteParameter(const Nan::FunctionCallbackInfo<v8::Value> &args,
|
|||||||
if (value.IsEmpty())
|
if (value.IsEmpty())
|
||||||
return route_parameters_ptr();
|
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();
|
return route_parameters_ptr();
|
||||||
}
|
}
|
||||||
params->alternatives = value->BooleanValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parsedSuccessfully = parseCommonParameters(obj, params);
|
bool parsedSuccessfully = parseCommonParameters(obj, params);
|
||||||
|
@ -30,7 +30,13 @@ struct RouteParametersGrammar : public BaseParametersGrammar<Iterator, Signature
|
|||||||
{
|
{
|
||||||
route_rule =
|
route_rule =
|
||||||
(qi::lit("alternatives=") >
|
(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("continue_straight=") >
|
||||||
(qi::lit("default") |
|
(qi::lit("default") |
|
||||||
qi::bool_[ph::bind(&engine::api::RouteParameters::continue_straight, qi::_r1) =
|
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
|
@ -25,7 +25,8 @@ bool EngineConfig::IsValid() const
|
|||||||
unlimited_or_more_than(max_locations_map_matching, 2) &&
|
unlimited_or_more_than(max_locations_map_matching, 2) &&
|
||||||
unlimited_or_more_than(max_locations_trip, 2) &&
|
unlimited_or_more_than(max_locations_trip, 2) &&
|
||||||
unlimited_or_more_than(max_locations_viaroute, 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;
|
return ((use_shared_memory && all_path_are_empty) || storage_config.IsValid()) && limits_valid;
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@ namespace engine
|
|||||||
namespace plugins
|
namespace plugins
|
||||||
{
|
{
|
||||||
|
|
||||||
ViaRoutePlugin::ViaRoutePlugin(int max_locations_viaroute)
|
ViaRoutePlugin::ViaRoutePlugin(int max_locations_viaroute, int max_alternatives)
|
||||||
: max_locations_viaroute(max_locations_viaroute)
|
: max_locations_viaroute(max_locations_viaroute), max_alternatives(max_alternatives)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +60,16 @@ ViaRoutePlugin::HandleRequest(const datafacade::ContiguousInternalMemoryDataFaca
|
|||||||
json_result);
|
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))
|
if (!CheckAllCoordinates(route_parameters.coordinates))
|
||||||
{
|
{
|
||||||
return Error("InvalidValue", "Invalid coordinate value.", json_result);
|
return Error("InvalidValue", "Invalid coordinate value.", json_result);
|
||||||
@ -88,13 +98,19 @@ ViaRoutePlugin::HandleRequest(const datafacade::ContiguousInternalMemoryDataFaca
|
|||||||
|
|
||||||
InternalManyRoutesResult routes;
|
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
|
// Alternatives do not support vias, only direct s,t queries supported
|
||||||
// See the implementation notes and high-level outline.
|
// See the implementation notes and high-level outline.
|
||||||
// https://github.com/Project-OSRM/osrm-backend/issues/3905
|
// https://github.com/Project-OSRM/osrm-backend/issues/3905
|
||||||
if (1 == start_end_nodes.size() && algorithms.HasAlternativePathSearch() &&
|
if (1 == start_end_nodes.size() && algorithms.HasAlternativePathSearch() && wants_alternatives)
|
||||||
route_parameters.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())
|
else if (1 == start_end_nodes.size() && algorithms.HasDirectShortestPathSearch())
|
||||||
{
|
{
|
||||||
|
@ -8,9 +8,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace osrm
|
namespace osrm
|
||||||
@ -19,8 +17,10 @@ namespace engine
|
|||||||
{
|
{
|
||||||
namespace routing_algorithms
|
namespace routing_algorithms
|
||||||
{
|
{
|
||||||
namespace ch
|
|
||||||
{
|
// Unqualified calls below are from the ch namespace.
|
||||||
|
// This alternative implementation works only for ch.
|
||||||
|
using namespace ch;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -90,7 +90,7 @@ void alternativeRoutingStep(const datafacade::ContiguousInternalMemoryDataFacade
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// check whether there is a loop present at the node
|
// 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;
|
const EdgeWeight new_weight_with_loop = new_weight + loop_weight;
|
||||||
if (loop_weight != INVALID_EDGE_WEIGHT &&
|
if (loop_weight != INVALID_EDGE_WEIGHT &&
|
||||||
new_weight_with_loop <= *upper_bound_to_shortest_path_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)
|
// fetch packed path [s,v)
|
||||||
std::vector<NodeID> packed_v_t_path;
|
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
|
packed_path.pop_back(); // remove middle node. It's in both half-paths
|
||||||
|
|
||||||
// fetch patched path [v,t]
|
// 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());
|
packed_path.insert(packed_path.end(), packed_v_t_path.begin(), packed_v_t_path.end());
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ void computeWeightAndSharingOfViaPath(
|
|||||||
// compute path <s,..,v> by reusing forward search from s
|
// compute path <s,..,v> by reusing forward search from s
|
||||||
while (!new_reverse_heap.Empty())
|
while (!new_reverse_heap.Empty())
|
||||||
{
|
{
|
||||||
ch::routingStep<REVERSE_DIRECTION>(facade,
|
routingStep<REVERSE_DIRECTION>(facade,
|
||||||
new_reverse_heap,
|
new_reverse_heap,
|
||||||
existing_forward_heap,
|
existing_forward_heap,
|
||||||
s_v_middle,
|
s_v_middle,
|
||||||
@ -196,7 +196,7 @@ void computeWeightAndSharingOfViaPath(
|
|||||||
new_forward_heap.Insert(via_node, 0, via_node);
|
new_forward_heap.Insert(via_node, 0, via_node);
|
||||||
while (!new_forward_heap.Empty())
|
while (!new_forward_heap.Empty())
|
||||||
{
|
{
|
||||||
ch::routingStep<FORWARD_DIRECTION>(facade,
|
routingStep<FORWARD_DIRECTION>(facade,
|
||||||
new_forward_heap,
|
new_forward_heap,
|
||||||
existing_reverse_heap,
|
existing_reverse_heap,
|
||||||
v_t_middle,
|
v_t_middle,
|
||||||
@ -213,9 +213,9 @@ void computeWeightAndSharingOfViaPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// retrieve packed paths
|
// retrieve packed paths
|
||||||
ch::retrievePackedPathFromHeap(
|
retrievePackedPathFromHeap(
|
||||||
existing_forward_heap, new_reverse_heap, s_v_middle, packed_s_v_path);
|
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);
|
new_forward_heap, existing_reverse_heap, v_t_middle, packed_v_t_path);
|
||||||
|
|
||||||
// partial unpacking, compute sharing
|
// partial unpacking, compute sharing
|
||||||
@ -235,11 +235,11 @@ void computeWeightAndSharingOfViaPath(
|
|||||||
{
|
{
|
||||||
if (packed_s_v_path[current_node] == packed_shortest_path[current_node])
|
if (packed_s_v_path[current_node] == packed_shortest_path[current_node])
|
||||||
{
|
{
|
||||||
ch::unpackEdge(facade,
|
unpackEdge(facade,
|
||||||
packed_s_v_path[current_node],
|
packed_s_v_path[current_node],
|
||||||
packed_s_v_path[current_node + 1],
|
packed_s_v_path[current_node + 1],
|
||||||
partially_unpacked_via_path);
|
partially_unpacked_via_path);
|
||||||
ch::unpackEdge(facade,
|
unpackEdge(facade,
|
||||||
packed_shortest_path[current_node],
|
packed_shortest_path[current_node],
|
||||||
packed_shortest_path[current_node + 1],
|
packed_shortest_path[current_node + 1],
|
||||||
partially_unpacked_shortest_path);
|
partially_unpacked_shortest_path);
|
||||||
@ -281,11 +281,11 @@ void computeWeightAndSharingOfViaPath(
|
|||||||
{
|
{
|
||||||
if (packed_v_t_path[via_path_index] == packed_shortest_path[shortest_path_index])
|
if (packed_v_t_path[via_path_index] == packed_shortest_path[shortest_path_index])
|
||||||
{
|
{
|
||||||
ch::unpackEdge(facade,
|
unpackEdge(facade,
|
||||||
packed_v_t_path[via_path_index - 1],
|
packed_v_t_path[via_path_index - 1],
|
||||||
packed_v_t_path[via_path_index],
|
packed_v_t_path[via_path_index],
|
||||||
partially_unpacked_via_path);
|
partially_unpacked_via_path);
|
||||||
ch::unpackEdge(facade,
|
unpackEdge(facade,
|
||||||
packed_shortest_path[shortest_path_index - 1],
|
packed_shortest_path[shortest_path_index - 1],
|
||||||
packed_shortest_path[shortest_path_index],
|
packed_shortest_path[shortest_path_index],
|
||||||
partially_unpacked_shortest_path);
|
partially_unpacked_shortest_path);
|
||||||
@ -343,7 +343,7 @@ bool viaNodeCandidatePassesTTest(
|
|||||||
new_reverse_heap.Insert(candidate.node, 0, candidate.node);
|
new_reverse_heap.Insert(candidate.node, 0, candidate.node);
|
||||||
while (new_reverse_heap.Size() > 0)
|
while (new_reverse_heap.Size() > 0)
|
||||||
{
|
{
|
||||||
ch::routingStep<REVERSE_DIRECTION>(facade,
|
routingStep<REVERSE_DIRECTION>(facade,
|
||||||
new_reverse_heap,
|
new_reverse_heap,
|
||||||
existing_forward_heap,
|
existing_forward_heap,
|
||||||
*s_v_middle,
|
*s_v_middle,
|
||||||
@ -364,7 +364,7 @@ bool viaNodeCandidatePassesTTest(
|
|||||||
new_forward_heap.Insert(candidate.node, 0, candidate.node);
|
new_forward_heap.Insert(candidate.node, 0, candidate.node);
|
||||||
while (new_forward_heap.Size() > 0)
|
while (new_forward_heap.Size() > 0)
|
||||||
{
|
{
|
||||||
ch::routingStep<FORWARD_DIRECTION>(facade,
|
routingStep<FORWARD_DIRECTION>(facade,
|
||||||
new_forward_heap,
|
new_forward_heap,
|
||||||
existing_reverse_heap,
|
existing_reverse_heap,
|
||||||
*v_t_middle,
|
*v_t_middle,
|
||||||
@ -382,10 +382,10 @@ bool viaNodeCandidatePassesTTest(
|
|||||||
*weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight;
|
*weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight;
|
||||||
|
|
||||||
// retrieve packed paths
|
// retrieve packed paths
|
||||||
ch::retrievePackedPathFromHeap(
|
retrievePackedPathFromHeap(
|
||||||
existing_forward_heap, new_reverse_heap, *s_v_middle, packed_s_v_path);
|
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);
|
new_forward_heap, existing_reverse_heap, *v_t_middle, packed_v_t_path);
|
||||||
|
|
||||||
NodeID s_P = *s_v_middle, t_P = *v_t_middle;
|
NodeID s_P = *s_v_middle, t_P = *v_t_middle;
|
||||||
@ -537,7 +537,7 @@ bool viaNodeCandidatePassesTTest(
|
|||||||
{
|
{
|
||||||
if (!forward_heap3.Empty())
|
if (!forward_heap3.Empty())
|
||||||
{
|
{
|
||||||
ch::routingStep<FORWARD_DIRECTION>(facade,
|
routingStep<FORWARD_DIRECTION>(facade,
|
||||||
forward_heap3,
|
forward_heap3,
|
||||||
reverse_heap3,
|
reverse_heap3,
|
||||||
middle,
|
middle,
|
||||||
@ -548,7 +548,7 @@ bool viaNodeCandidatePassesTTest(
|
|||||||
}
|
}
|
||||||
if (!reverse_heap3.Empty())
|
if (!reverse_heap3.Empty())
|
||||||
{
|
{
|
||||||
ch::routingStep<REVERSE_DIRECTION>(facade,
|
routingStep<REVERSE_DIRECTION>(facade,
|
||||||
reverse_heap3,
|
reverse_heap3,
|
||||||
forward_heap3,
|
forward_heap3,
|
||||||
middle,
|
middle,
|
||||||
@ -560,12 +560,13 @@ bool viaNodeCandidatePassesTTest(
|
|||||||
}
|
}
|
||||||
return (upper_bound <= t_test_path_weight);
|
return (upper_bound <= t_test_path_weight);
|
||||||
}
|
}
|
||||||
}
|
} // anon. namespace
|
||||||
|
|
||||||
InternalManyRoutesResult
|
InternalManyRoutesResult
|
||||||
alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
|
alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
|
||||||
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
const datafacade::ContiguousInternalMemoryDataFacade<Algorithm> &facade,
|
||||||
const PhantomNodes &phantom_node_pair)
|
const PhantomNodes &phantom_node_pair,
|
||||||
|
unsigned /*number_of_alternatives*/)
|
||||||
{
|
{
|
||||||
InternalRouteResult primary_route;
|
InternalRouteResult primary_route;
|
||||||
InternalRouteResult secondary_route;
|
InternalRouteResult secondary_route;
|
||||||
@ -651,8 +652,8 @@ alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
ch::retrievePackedPathFromSingleHeap(forward_heap1, middle_node, packed_forward_path);
|
retrievePackedPathFromSingleHeap(forward_heap1, middle_node, packed_forward_path);
|
||||||
ch::retrievePackedPathFromSingleHeap(reverse_heap1, middle_node, packed_reverse_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
|
// this set is is used as an indicator if a node is on the shortest path
|
||||||
@ -805,7 +806,7 @@ alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
|
|||||||
primary_route.target_traversed_in_reverse.push_back((
|
primary_route.target_traversed_in_reverse.push_back((
|
||||||
packed_shortest_path.back() != phantom_node_pair.target_phantom.forward_segment_id.id));
|
packed_shortest_path.back() != phantom_node_pair.target_phantom.forward_segment_id.id));
|
||||||
|
|
||||||
ch::unpackPath(facade,
|
unpackPath(facade,
|
||||||
// -- packed input
|
// -- packed input
|
||||||
packed_shortest_path.begin(),
|
packed_shortest_path.begin(),
|
||||||
packed_shortest_path.end(),
|
packed_shortest_path.end(),
|
||||||
@ -837,7 +838,7 @@ alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
|
|||||||
phantom_node_pair.target_phantom.forward_segment_id.id));
|
phantom_node_pair.target_phantom.forward_segment_id.id));
|
||||||
|
|
||||||
// unpack the alternate path
|
// unpack the alternate path
|
||||||
ch::unpackPath(facade,
|
unpackPath(facade,
|
||||||
packed_alternate_path.begin(),
|
packed_alternate_path.begin(),
|
||||||
packed_alternate_path.end(),
|
packed_alternate_path.end(),
|
||||||
phantom_node_pair,
|
phantom_node_pair,
|
||||||
@ -853,7 +854,6 @@ alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
|
|||||||
return InternalManyRoutesResult{{std::move(primary_route), std::move(secondary_route)}};
|
return InternalManyRoutesResult{{std::move(primary_route), std::move(secondary_route)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ch
|
|
||||||
} // namespace routing_algorithms
|
} // namespace routing_algorithms
|
||||||
} // namespace engine
|
} // namespace engine
|
||||||
} // namespace osrm}
|
} // 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
|
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.
|
/// 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
|
/// 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
|
/// 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();
|
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 routing_algorithms
|
||||||
} // namespace engine
|
} // namespace engine
|
||||||
} // namespace osrm
|
} // 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`.
|
* 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.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 {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.
|
* @param {Boolean} [options.alternatives=false] Search for alternative routes.
|
||||||
* *Please note that even if an alternative route is requested, a result cannot be guaranteed.*
|
* @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 {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 {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`.
|
* @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/osrm.hpp"
|
||||||
#include "osrm/storage_config.hpp"
|
#include "osrm/storage_config.hpp"
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
#include <boost/any.hpp>
|
#include <boost/any.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/program_options.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_OK_DO_NOT_START_ENGINE = 1;
|
||||||
const static unsigned INIT_FAILED = -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;
|
return EngineConfig::Algorithm::CH;
|
||||||
if (algorithm == "CoreCH")
|
if (algorithm == "corech")
|
||||||
return EngineConfig::Algorithm::CoreCH;
|
return EngineConfig::Algorithm::CoreCH;
|
||||||
if (algorithm == "MLD")
|
if (algorithm == "mld")
|
||||||
return EngineConfig::Algorithm::MLD;
|
return EngineConfig::Algorithm::MLD;
|
||||||
throw util::RuntimeError(algorithm, ErrorCode::UnknownAlgorithm, SOURCE_REF);
|
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_viaroute,
|
||||||
int &max_locations_distance_table,
|
int &max_locations_distance_table,
|
||||||
int &max_locations_map_matching,
|
int &max_locations_map_matching,
|
||||||
int &max_results_nearest)
|
int &max_results_nearest,
|
||||||
|
int &max_alternatives)
|
||||||
{
|
{
|
||||||
using boost::program_options::value;
|
using boost::program_options::value;
|
||||||
using boost::filesystem::path;
|
using boost::filesystem::path;
|
||||||
@ -119,7 +123,10 @@ inline unsigned generateServerProgramOptions(const int argc,
|
|||||||
"Max. locations supported in map matching query") //
|
"Max. locations supported in map matching query") //
|
||||||
("max-nearest-size",
|
("max-nearest-size",
|
||||||
value<int>(&max_results_nearest)->default_value(100),
|
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
|
// 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");
|
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_viaroute,
|
||||||
config.max_locations_distance_table,
|
config.max_locations_distance_table,
|
||||||
config.max_locations_map_matching,
|
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)
|
if (init_result == INIT_OK_DO_NOT_START_ENGINE)
|
||||||
{
|
{
|
||||||
return EXIT_SUCCESS;
|
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) {
|
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 osrm = new OSRM(monaco_path);
|
||||||
var options = {coordinates: two_test_coordinates};
|
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);
|
||||||
assert.ok(route.routes.length >= 1);
|
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) {
|
test('route: throws with bad params', function(assert) {
|
||||||
|
@ -59,6 +59,8 @@ BOOST_AUTO_TEST_CASE(invalid_route_urls)
|
|||||||
32L);
|
32L);
|
||||||
BOOST_CHECK_EQUAL(
|
BOOST_CHECK_EQUAL(
|
||||||
testInvalidOptions<RouteParameters>("1,2;3,4?overview=false&alternatives=foo"), 36UL);
|
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>(""), 0);
|
||||||
BOOST_CHECK_EQUAL(testInvalidOptions<RouteParameters>("1,2;3.4.unsupported"), 7);
|
BOOST_CHECK_EQUAL(testInvalidOptions<RouteParameters>("1,2;3.4.unsupported"), 7);
|
||||||
BOOST_CHECK_EQUAL(testInvalidOptions<RouteParameters>("1,2;3,4.json?nooptions"), 13);
|
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{};
|
RouteParameters reference_2{};
|
||||||
reference_2.alternatives = true;
|
reference_2.alternatives = true;
|
||||||
|
reference_2.number_of_alternatives = 1;
|
||||||
reference_2.steps = true;
|
reference_2.steps = true;
|
||||||
reference_2.annotations = true;
|
reference_2.annotations = true;
|
||||||
reference_2.coordinates = coords_1;
|
reference_2.coordinates = coords_1;
|
||||||
@ -127,6 +130,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls)
|
|||||||
BOOST_CHECK(result_2);
|
BOOST_CHECK(result_2);
|
||||||
BOOST_CHECK_EQUAL(reference_2.steps, result_2->steps);
|
BOOST_CHECK_EQUAL(reference_2.steps, result_2->steps);
|
||||||
BOOST_CHECK_EQUAL(reference_2.alternatives, result_2->alternatives);
|
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.geometries, result_2->geometries);
|
||||||
BOOST_CHECK_EQUAL(reference_2.annotations, result_2->annotations);
|
BOOST_CHECK_EQUAL(reference_2.annotations, result_2->annotations);
|
||||||
BOOST_CHECK_EQUAL(reference_2.overview, result_2->overview);
|
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(result_3);
|
||||||
BOOST_CHECK_EQUAL(reference_3.steps, result_3->steps);
|
BOOST_CHECK_EQUAL(reference_3.steps, result_3->steps);
|
||||||
BOOST_CHECK_EQUAL(reference_3.alternatives, result_3->alternatives);
|
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.geometries, result_3->geometries);
|
||||||
BOOST_CHECK_EQUAL(reference_3.annotations, result_3->annotations);
|
BOOST_CHECK_EQUAL(reference_3.annotations, result_3->annotations);
|
||||||
BOOST_CHECK_EQUAL(reference_3.overview, result_3->overview);
|
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.approaches, result_18->approaches);
|
||||||
CHECK_EQUAL_RANGE(reference_18.coordinates, result_18->coordinates);
|
CHECK_EQUAL_RANGE(reference_18.coordinates, result_18->coordinates);
|
||||||
CHECK_EQUAL_RANGE(reference_18.hints, result_18->hints);
|
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)
|
BOOST_AUTO_TEST_CASE(valid_table_urls)
|
||||||
|
Loading…
Reference in New Issue
Block a user