Refactor routing_algorithms to only contain free functions
This commit is contained in:
committed by
Patrick Niklaus
parent
2fa8d0f534
commit
436b34ffea
@@ -1,22 +1,11 @@
|
||||
#ifndef ALTERNATIVE_PATH_ROUTING_HPP
|
||||
#define ALTERNATIVE_PATH_ROUTING_HPP
|
||||
|
||||
#include "engine/datafacade/datafacade_base.hpp"
|
||||
#include "engine/routing_algorithms/routing_base.hpp"
|
||||
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
||||
#include "engine/internal_route_result.hpp"
|
||||
|
||||
#include "engine/algorithm.hpp"
|
||||
#include "engine/search_engine_data.hpp"
|
||||
#include "util/integer_range.hpp"
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
@@ -25,181 +14,13 @@ namespace engine
|
||||
namespace routing_algorithms
|
||||
{
|
||||
|
||||
const double constexpr VIAPATH_ALPHA = 0.10;
|
||||
const double constexpr VIAPATH_EPSILON = 0.15; // alternative at most 15% longer
|
||||
const double constexpr VIAPATH_GAMMA = 0.75; // alternative shares at most 75% with the shortest.
|
||||
|
||||
template <typename AlgorithmT> class AlternativeRouting;
|
||||
|
||||
template <> class AlternativeRouting<algorithm::CH> final : private BasicRouting<algorithm::CH>
|
||||
{
|
||||
using super = BasicRouting<algorithm::CH>;
|
||||
using FacadeT = datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH>;
|
||||
using QueryHeap = SearchEngineData::QueryHeap;
|
||||
using SearchSpaceEdge = std::pair<NodeID, NodeID>;
|
||||
|
||||
struct RankedCandidateNode
|
||||
{
|
||||
RankedCandidateNode(const NodeID node, const int length, const int sharing)
|
||||
: node(node), length(length), sharing(sharing)
|
||||
{
|
||||
}
|
||||
|
||||
NodeID node;
|
||||
int length;
|
||||
int sharing;
|
||||
|
||||
bool operator<(const RankedCandidateNode &other) const
|
||||
{
|
||||
return (2 * length + sharing) < (2 * other.length + other.sharing);
|
||||
}
|
||||
};
|
||||
SearchEngineData &engine_working_data;
|
||||
|
||||
public:
|
||||
AlternativeRouting(SearchEngineData &engine_working_data)
|
||||
: engine_working_data(engine_working_data)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~AlternativeRouting() {}
|
||||
|
||||
void operator()(const FacadeT &facade,
|
||||
const PhantomNodes &phantom_node_pair,
|
||||
InternalRouteResult &raw_route_data);
|
||||
|
||||
private:
|
||||
// unpack alternate <s,..,v,..,t> by exploring search spaces from v
|
||||
void RetrievePackedAlternatePath(const QueryHeap &forward_heap1,
|
||||
const QueryHeap &reverse_heap1,
|
||||
const QueryHeap &forward_heap2,
|
||||
const QueryHeap &reverse_heap2,
|
||||
const NodeID s_v_middle,
|
||||
const NodeID v_t_middle,
|
||||
std::vector<NodeID> &packed_path) const;
|
||||
|
||||
// TODO: reorder parameters
|
||||
// compute and unpack <s,..,v> and <v,..,t> by exploring search spaces
|
||||
// from v and intersecting against queues. only half-searches have to be
|
||||
// done at this stage
|
||||
void ComputeLengthAndSharingOfViaPath(const FacadeT &facade,
|
||||
const NodeID via_node,
|
||||
int *real_length_of_via_path,
|
||||
int *sharing_of_via_path,
|
||||
const std::vector<NodeID> &packed_shortest_path,
|
||||
const EdgeWeight min_edge_offset);
|
||||
|
||||
// todo: reorder parameters
|
||||
template <bool is_forward_directed>
|
||||
void AlternativeRoutingStep(const FacadeT &facade,
|
||||
QueryHeap &heap1,
|
||||
QueryHeap &heap2,
|
||||
NodeID *middle_node,
|
||||
EdgeWeight *upper_bound_to_shortest_path_weight,
|
||||
std::vector<NodeID> &search_space_intersection,
|
||||
std::vector<SearchSpaceEdge> &search_space,
|
||||
const EdgeWeight min_edge_offset) const
|
||||
{
|
||||
QueryHeap &forward_heap = (is_forward_directed ? heap1 : heap2);
|
||||
QueryHeap &reverse_heap = (is_forward_directed ? heap2 : heap1);
|
||||
|
||||
const NodeID node = forward_heap.DeleteMin();
|
||||
const EdgeWeight weight = forward_heap.GetKey(node);
|
||||
// const NodeID parentnode = forward_heap.GetData(node).parent;
|
||||
// util::Log() << (is_forward_directed ? "[fwd] " : "[rev] ") << "settled
|
||||
// edge ("
|
||||
// << parentnode << "," << node << "), dist: " << weight;
|
||||
|
||||
const auto scaled_weight =
|
||||
static_cast<EdgeWeight>((weight + min_edge_offset) / (1. + VIAPATH_EPSILON));
|
||||
if ((INVALID_EDGE_WEIGHT != *upper_bound_to_shortest_path_weight) &&
|
||||
(scaled_weight > *upper_bound_to_shortest_path_weight))
|
||||
{
|
||||
forward_heap.DeleteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
search_space.emplace_back(forward_heap.GetData(node).parent, node);
|
||||
|
||||
if (reverse_heap.WasInserted(node))
|
||||
{
|
||||
search_space_intersection.emplace_back(node);
|
||||
const EdgeWeight new_weight = reverse_heap.GetKey(node) + weight;
|
||||
if (new_weight < *upper_bound_to_shortest_path_weight)
|
||||
{
|
||||
if (new_weight >= 0)
|
||||
{
|
||||
*middle_node = node;
|
||||
*upper_bound_to_shortest_path_weight = new_weight;
|
||||
// util::Log() << "accepted middle_node " << *middle_node
|
||||
// << " at
|
||||
// weight " << new_weight;
|
||||
// } else {
|
||||
// util::Log() << "discarded middle_node " << *middle_node
|
||||
// << "
|
||||
// at weight " << new_weight;
|
||||
}
|
||||
else
|
||||
{
|
||||
// check whether there is a loop present at the node
|
||||
const auto loop_weight = super::GetLoopWeight<false>(facade, node);
|
||||
const EdgeWeight new_weight_with_loop = new_weight + loop_weight;
|
||||
if (loop_weight != INVALID_EDGE_WEIGHT &&
|
||||
new_weight_with_loop <= *upper_bound_to_shortest_path_weight)
|
||||
{
|
||||
*middle_node = node;
|
||||
*upper_bound_to_shortest_path_weight = loop_weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto edge : facade.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
const auto &data = facade.GetEdgeData(edge);
|
||||
const bool edge_is_forward_directed =
|
||||
(is_forward_directed ? data.forward : data.backward);
|
||||
if (edge_is_forward_directed)
|
||||
{
|
||||
const NodeID to = facade.GetTarget(edge);
|
||||
const EdgeWeight edge_weight = data.weight;
|
||||
|
||||
BOOST_ASSERT(edge_weight > 0);
|
||||
const EdgeWeight to_weight = weight + edge_weight;
|
||||
|
||||
// New Node discovered -> Add to Heap + Node Info Storage
|
||||
if (!forward_heap.WasInserted(to))
|
||||
{
|
||||
forward_heap.Insert(to, to_weight, node);
|
||||
}
|
||||
// Found a shorter Path -> Update weight
|
||||
else if (to_weight < forward_heap.GetKey(to))
|
||||
{
|
||||
// new parent
|
||||
forward_heap.GetData(to).parent = node;
|
||||
// decreased weight
|
||||
forward_heap.DecreaseKey(to, to_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// conduct T-Test
|
||||
bool ViaNodeCandidatePassesTTest(const FacadeT &facade,
|
||||
QueryHeap &existing_forward_heap,
|
||||
QueryHeap &existing_reverse_heap,
|
||||
QueryHeap &new_forward_heap,
|
||||
QueryHeap &new_reverse_heap,
|
||||
const RankedCandidateNode &candidate,
|
||||
const int length_of_shortest_path,
|
||||
int *length_of_via_path,
|
||||
NodeID *s_v_middle,
|
||||
NodeID *v_t_middle,
|
||||
const EdgeWeight min_edge_offset) const;
|
||||
};
|
||||
InternalRouteResult
|
||||
alternativePathSearch(SearchEngineData &search_engine_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const PhantomNodes &phantom_node_pair);
|
||||
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
} // namespace osrm
|
||||
|
||||
#endif /* ALTERNATIVE_PATH_ROUTING_HPP */
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef DIRECT_SHORTEST_PATH_HPP
|
||||
#define DIRECT_SHORTEST_PATH_HPP
|
||||
|
||||
#include "engine/routing_algorithms/routing_base.hpp"
|
||||
|
||||
#include "engine/algorithm.hpp"
|
||||
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
||||
#include "engine/internal_route_result.hpp"
|
||||
#include "engine/search_engine_data.hpp"
|
||||
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
namespace osrm
|
||||
@@ -14,34 +15,16 @@ namespace engine
|
||||
namespace routing_algorithms
|
||||
{
|
||||
|
||||
template <typename AlgorithmT> class DirectShortestPathRouting;
|
||||
|
||||
/// This is a striped down version of the general shortest path algorithm.
|
||||
/// The general algorithm always computes two queries for each leg. This is only
|
||||
/// necessary in case of vias, where the directions of the start node is constrainted
|
||||
/// by the previous route.
|
||||
/// This variation is only an optimazation for graphs with slow queries, for example
|
||||
/// not fully contracted graphs.
|
||||
template <>
|
||||
class DirectShortestPathRouting<algorithm::CH> final : public BasicRouting<algorithm::CH>
|
||||
{
|
||||
using super = BasicRouting<algorithm::CH>;
|
||||
using FacadeT = datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH>;
|
||||
using QueryHeap = SearchEngineData::QueryHeap;
|
||||
SearchEngineData &engine_working_data;
|
||||
|
||||
public:
|
||||
DirectShortestPathRouting(SearchEngineData &engine_working_data)
|
||||
: engine_working_data(engine_working_data)
|
||||
{
|
||||
}
|
||||
|
||||
~DirectShortestPathRouting() {}
|
||||
|
||||
void operator()(const FacadeT &facade,
|
||||
const std::vector<PhantomNodes> &phantom_nodes_vector,
|
||||
InternalRouteResult &raw_route_data) const;
|
||||
};
|
||||
InternalRouteResult directShortestPathSearch(
|
||||
SearchEngineData &engine_working_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const std::vector<PhantomNodes> &phantom_nodes_vector);
|
||||
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
|
||||
@@ -1,138 +1,28 @@
|
||||
#ifndef MANY_TO_MANY_ROUTING_HPP
|
||||
#define MANY_TO_MANY_ROUTING_HPP
|
||||
|
||||
#include "engine/routing_algorithms/routing_base.hpp"
|
||||
|
||||
#include "engine/algorithm.hpp"
|
||||
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
||||
#include "engine/search_engine_data.hpp"
|
||||
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace engine
|
||||
{
|
||||
|
||||
namespace routing_algorithms
|
||||
{
|
||||
|
||||
template <typename AlgorithmT> class ManyToManyRouting;
|
||||
|
||||
template <> class ManyToManyRouting<algorithm::CH> final : public BasicRouting<algorithm::CH>
|
||||
{
|
||||
using super = BasicRouting<algorithm::CH>;
|
||||
using FacadeT = datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH>;
|
||||
using QueryHeap = SearchEngineData::ManyToManyQueryHeap;
|
||||
SearchEngineData &engine_working_data;
|
||||
|
||||
struct NodeBucket
|
||||
{
|
||||
unsigned target_id; // essentially a row in the weight matrix
|
||||
EdgeWeight weight;
|
||||
EdgeWeight duration;
|
||||
NodeBucket(const unsigned target_id, const EdgeWeight weight, const EdgeWeight duration)
|
||||
: target_id(target_id), weight(weight), duration(duration)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME This should be replaced by an std::unordered_multimap, though this needs benchmarking
|
||||
using SearchSpaceWithBuckets = std::unordered_map<NodeID, std::vector<NodeBucket>>;
|
||||
|
||||
public:
|
||||
ManyToManyRouting(SearchEngineData &engine_working_data)
|
||||
: engine_working_data(engine_working_data)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<EdgeWeight> operator()(const FacadeT &facade,
|
||||
const std::vector<PhantomNode> &phantom_nodes,
|
||||
const std::vector<std::size_t> &source_indices,
|
||||
const std::vector<std::size_t> &target_indices) const;
|
||||
|
||||
void ForwardRoutingStep(const FacadeT &facade,
|
||||
const unsigned row_idx,
|
||||
const unsigned number_of_targets,
|
||||
QueryHeap &query_heap,
|
||||
const SearchSpaceWithBuckets &search_space_with_buckets,
|
||||
std::vector<EdgeWeight> &weights_table,
|
||||
std::vector<EdgeWeight> &durations_table) const;
|
||||
|
||||
void BackwardRoutingStep(const FacadeT &facade,
|
||||
const unsigned column_idx,
|
||||
QueryHeap &query_heap,
|
||||
SearchSpaceWithBuckets &search_space_with_buckets) const;
|
||||
|
||||
template <bool forward_direction>
|
||||
inline void RelaxOutgoingEdges(const FacadeT &facade,
|
||||
const NodeID node,
|
||||
const EdgeWeight weight,
|
||||
const EdgeWeight duration,
|
||||
QueryHeap &query_heap) const
|
||||
{
|
||||
for (auto edge : facade.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
const auto &data = facade.GetEdgeData(edge);
|
||||
const bool direction_flag = (forward_direction ? data.forward : data.backward);
|
||||
if (direction_flag)
|
||||
{
|
||||
const NodeID to = facade.GetTarget(edge);
|
||||
const EdgeWeight edge_weight = data.weight;
|
||||
const EdgeWeight edge_duration = data.duration;
|
||||
|
||||
BOOST_ASSERT_MSG(edge_weight > 0, "edge_weight invalid");
|
||||
const EdgeWeight to_weight = weight + edge_weight;
|
||||
const EdgeWeight to_duration = duration + edge_duration;
|
||||
|
||||
// New Node discovered -> Add to Heap + Node Info Storage
|
||||
if (!query_heap.WasInserted(to))
|
||||
{
|
||||
query_heap.Insert(to, to_weight, {node, to_duration});
|
||||
}
|
||||
// Found a shorter Path -> Update weight
|
||||
else if (to_weight < query_heap.GetKey(to))
|
||||
{
|
||||
// new parent
|
||||
query_heap.GetData(to) = {node, to_duration};
|
||||
query_heap.DecreaseKey(to, to_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stalling
|
||||
template <bool forward_direction>
|
||||
inline bool StallAtNode(const FacadeT &facade,
|
||||
const NodeID node,
|
||||
const EdgeWeight weight,
|
||||
QueryHeap &query_heap) const
|
||||
{
|
||||
for (auto edge : facade.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
const auto &data = facade.GetEdgeData(edge);
|
||||
const bool reverse_flag = ((!forward_direction) ? data.forward : data.backward);
|
||||
if (reverse_flag)
|
||||
{
|
||||
const NodeID to = facade.GetTarget(edge);
|
||||
const EdgeWeight edge_weight = data.weight;
|
||||
BOOST_ASSERT_MSG(edge_weight > 0, "edge_weight invalid");
|
||||
if (query_heap.WasInserted(to))
|
||||
{
|
||||
if (query_heap.GetKey(to) + edge_weight < weight)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
std::vector<EdgeWeight>
|
||||
manyToManySearch(SearchEngineData &engine_working_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const std::vector<PhantomNode> &phantom_nodes,
|
||||
const std::vector<std::size_t> &source_indices,
|
||||
const std::vector<std::size_t> &target_indices);
|
||||
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
|
||||
@@ -1,25 +1,11 @@
|
||||
#ifndef MAP_MATCHING_HPP
|
||||
#define MAP_MATCHING_HPP
|
||||
|
||||
#include "engine/routing_algorithms/routing_base.hpp"
|
||||
|
||||
#include "engine/algorithm.hpp"
|
||||
#include "engine/map_matching/hidden_markov_model.hpp"
|
||||
#include "engine/map_matching/matching_confidence.hpp"
|
||||
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
||||
#include "engine/map_matching/sub_matching.hpp"
|
||||
#include "engine/search_engine_data.hpp"
|
||||
|
||||
#include "extractor/profile_properties.hpp"
|
||||
#include "util/coordinate_calculation.hpp"
|
||||
#include "util/for_each_pair.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
@@ -31,45 +17,16 @@ namespace routing_algorithms
|
||||
|
||||
using CandidateList = std::vector<PhantomNodeWithDistance>;
|
||||
using CandidateLists = std::vector<CandidateList>;
|
||||
using HMM = map_matching::HiddenMarkovModel<CandidateLists>;
|
||||
using SubMatchingList = std::vector<map_matching::SubMatching>;
|
||||
|
||||
constexpr static const unsigned MAX_BROKEN_STATES = 10;
|
||||
static const constexpr double MATCHING_BETA = 10;
|
||||
constexpr static const double MAX_DISTANCE_DELTA = 2000.;
|
||||
static const constexpr double DEFAULT_GPS_PRECISION = 5;
|
||||
|
||||
template <typename AlgorithmT> class MapMatching;
|
||||
|
||||
// implements a hidden markov model map matching algorithm
|
||||
template <> class MapMatching<algorithm::CH> final : public BasicRouting<algorithm::CH>
|
||||
{
|
||||
using super = BasicRouting<algorithm::CH>;
|
||||
using FacadeT = datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH>;
|
||||
using QueryHeap = SearchEngineData::QueryHeap;
|
||||
SearchEngineData &engine_working_data;
|
||||
map_matching::EmissionLogProbability default_emission_log_probability;
|
||||
map_matching::TransitionLogProbability transition_log_probability;
|
||||
map_matching::MatchingConfidence confidence;
|
||||
extractor::ProfileProperties m_profile_properties;
|
||||
|
||||
unsigned GetMedianSampleTime(const std::vector<unsigned> ×tamps) const;
|
||||
|
||||
public:
|
||||
MapMatching(SearchEngineData &engine_working_data)
|
||||
: engine_working_data(engine_working_data),
|
||||
default_emission_log_probability(DEFAULT_GPS_PRECISION),
|
||||
transition_log_probability(MATCHING_BETA)
|
||||
{
|
||||
}
|
||||
|
||||
SubMatchingList
|
||||
operator()(const FacadeT &facade,
|
||||
const CandidateLists &candidates_list,
|
||||
const std::vector<util::Coordinate> &trace_coordinates,
|
||||
const std::vector<unsigned> &trace_timestamps,
|
||||
const std::vector<boost::optional<double>> &trace_gps_precision) const;
|
||||
};
|
||||
SubMatchingList
|
||||
mapMatching(SearchEngineData &engine_working_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const CandidateLists &candidates_list,
|
||||
const std::vector<util::Coordinate> &trace_coordinates,
|
||||
const std::vector<unsigned> &trace_timestamps,
|
||||
const std::vector<boost::optional<double>> &trace_gps_precision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "engine/algorithm.hpp"
|
||||
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
||||
#include "engine/edge_unpacker.hpp"
|
||||
#include "engine/internal_route_result.hpp"
|
||||
#include "engine/search_engine_data.hpp"
|
||||
|
||||
@@ -35,402 +34,476 @@ namespace engine
|
||||
namespace routing_algorithms
|
||||
{
|
||||
|
||||
template <typename AlgorithmT> class BasicRouting;
|
||||
/*
|
||||
min_edge_offset is needed in case we use multiple
|
||||
nodes as start/target nodes with different (even negative) offsets.
|
||||
In that case the termination criterion is not correct
|
||||
anymore.
|
||||
|
||||
// TODO: There is no reason these functions are contained in a class other then for namespace
|
||||
// purposes. This should be a namespace with free functions.
|
||||
template <> class BasicRouting<algorithm::CH>
|
||||
{
|
||||
protected:
|
||||
using FacadeT = datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH>;
|
||||
using EdgeData = typename FacadeT::EdgeData;
|
||||
Example:
|
||||
forward heap: a(-100), b(0),
|
||||
reverse heap: c(0), d(100)
|
||||
|
||||
public:
|
||||
/*
|
||||
min_edge_offset is needed in case we use multiple
|
||||
nodes as start/target nodes with different (even negative) offsets.
|
||||
In that case the termination criterion is not correct
|
||||
anymore.
|
||||
a --- d
|
||||
\ /
|
||||
/ \
|
||||
b --- c
|
||||
|
||||
Example:
|
||||
forward heap: a(-100), b(0),
|
||||
reverse heap: c(0), d(100)
|
||||
This is equivalent to running a bi-directional Dijkstra on the following graph:
|
||||
|
||||
a --- d
|
||||
\ /
|
||||
/ \
|
||||
/ \ / \
|
||||
y x z
|
||||
\ / \ /
|
||||
b --- c
|
||||
|
||||
This is equivalent to running a bi-directional Dijkstra on the following graph:
|
||||
The graph is constructed by inserting nodes y and z that are connected to the initial nodes
|
||||
using edges (y, a) with weight -100, (y, b) with weight 0 and,
|
||||
(d, z) with weight 100, (c, z) with weight 0 corresponding.
|
||||
Since we are dealing with a graph that contains _negative_ edges,
|
||||
we need to add an offset to the termination criterion.
|
||||
*/
|
||||
void routingStep(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
NodeID &middle_node_id,
|
||||
std::int32_t &upper_bound,
|
||||
std::int32_t min_edge_offset,
|
||||
const bool forward_direction,
|
||||
const bool stalling,
|
||||
const bool force_loop_forward,
|
||||
const bool force_loop_reverse);
|
||||
|
||||
a --- d
|
||||
/ \ / \
|
||||
y x z
|
||||
\ / \ /
|
||||
b --- c
|
||||
|
||||
The graph is constructed by inserting nodes y and z that are connected to the initial nodes
|
||||
using edges (y, a) with weight -100, (y, b) with weight 0 and,
|
||||
(d, z) with weight 100, (c, z) with weight 0 corresponding.
|
||||
Since we are dealing with a graph that contains _negative_ edges,
|
||||
we need to add an offset to the termination criterion.
|
||||
*/
|
||||
void RoutingStep(const FacadeT &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
NodeID &middle_node_id,
|
||||
std::int32_t &upper_bound,
|
||||
std::int32_t min_edge_offset,
|
||||
const bool forward_direction,
|
||||
const bool stalling,
|
||||
const bool force_loop_forward,
|
||||
const bool force_loop_reverse) const;
|
||||
|
||||
template <bool UseDuration> EdgeWeight GetLoopWeight(const FacadeT &facade, NodeID node) const
|
||||
template <bool UseDuration>
|
||||
EdgeWeight
|
||||
getLoopWeight(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
NodeID node)
|
||||
{
|
||||
EdgeWeight loop_weight = UseDuration ? MAXIMAL_EDGE_DURATION : INVALID_EDGE_WEIGHT;
|
||||
for (auto edge : facade.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
EdgeWeight loop_weight = UseDuration ? MAXIMAL_EDGE_DURATION : INVALID_EDGE_WEIGHT;
|
||||
for (auto edge : facade.GetAdjacentEdgeRange(node))
|
||||
const auto &data = facade.GetEdgeData(edge);
|
||||
if (data.forward)
|
||||
{
|
||||
const auto &data = facade.GetEdgeData(edge);
|
||||
if (data.forward)
|
||||
const NodeID to = facade.GetTarget(edge);
|
||||
if (to == node)
|
||||
{
|
||||
const NodeID to = facade.GetTarget(edge);
|
||||
if (to == node)
|
||||
{
|
||||
const auto value = UseDuration ? data.duration : data.weight;
|
||||
loop_weight = std::min(loop_weight, value);
|
||||
}
|
||||
const auto value = UseDuration ? data.duration : data.weight;
|
||||
loop_weight = std::min(loop_weight, value);
|
||||
}
|
||||
}
|
||||
return loop_weight;
|
||||
}
|
||||
return loop_weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a sequence of connected `NodeID`s in the CH graph, performs a depth-first unpacking of
|
||||
* the shortcut
|
||||
* edges. For every "original" edge found, it calls the `callback` with the two NodeIDs for the
|
||||
* edge, and the EdgeData
|
||||
* for that edge.
|
||||
*
|
||||
* The primary purpose of this unpacking is to expand a path through the CH into the original
|
||||
* route through the
|
||||
* pre-contracted graph.
|
||||
*
|
||||
* Because of the depth-first-search, the `callback` will effectively be called in sequence for
|
||||
* the original route
|
||||
* from beginning to end.
|
||||
*
|
||||
* @param packed_path_begin iterator pointing to the start of the NodeID list
|
||||
* @param packed_path_end iterator pointing to the end of the NodeID list
|
||||
* @param callback void(const std::pair<NodeID, NodeID>, const EdgeData &) called for each
|
||||
* original edge found.
|
||||
*/
|
||||
template <typename BidirectionalIterator, typename Callback>
|
||||
void unpackPath(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
BidirectionalIterator packed_path_begin,
|
||||
BidirectionalIterator packed_path_end,
|
||||
Callback &&callback)
|
||||
{
|
||||
// make sure we have at least something to unpack
|
||||
if (packed_path_begin == packed_path_end)
|
||||
return;
|
||||
|
||||
std::stack<std::pair<NodeID, NodeID>> recursion_stack;
|
||||
|
||||
// We have to push the path in reverse order onto the stack because it's LIFO.
|
||||
for (auto current = std::prev(packed_path_end); current != packed_path_begin;
|
||||
current = std::prev(current))
|
||||
{
|
||||
recursion_stack.emplace(*std::prev(current), *current);
|
||||
}
|
||||
|
||||
template <typename RandomIter>
|
||||
void UnpackPath(const FacadeT &facade,
|
||||
RandomIter packed_path_begin,
|
||||
RandomIter packed_path_end,
|
||||
const PhantomNodes &phantom_node_pair,
|
||||
std::vector<PathData> &unpacked_path) const
|
||||
std::pair<NodeID, NodeID> edge;
|
||||
while (!recursion_stack.empty())
|
||||
{
|
||||
BOOST_ASSERT(std::distance(packed_path_begin, packed_path_end) > 0);
|
||||
edge = recursion_stack.top();
|
||||
recursion_stack.pop();
|
||||
|
||||
const bool start_traversed_in_reverse =
|
||||
(*packed_path_begin != phantom_node_pair.source_phantom.forward_segment_id.id);
|
||||
const bool target_traversed_in_reverse =
|
||||
(*std::prev(packed_path_end) != phantom_node_pair.target_phantom.forward_segment_id.id);
|
||||
// Look for an edge on the forward CH graph (.forward)
|
||||
EdgeID smaller_edge_id = facade.FindSmallestEdge(
|
||||
edge.first, edge.second, [](const auto &data) { return data.forward; });
|
||||
|
||||
BOOST_ASSERT(*packed_path_begin == phantom_node_pair.source_phantom.forward_segment_id.id ||
|
||||
*packed_path_begin == phantom_node_pair.source_phantom.reverse_segment_id.id);
|
||||
BOOST_ASSERT(
|
||||
*std::prev(packed_path_end) == phantom_node_pair.target_phantom.forward_segment_id.id ||
|
||||
*std::prev(packed_path_end) == phantom_node_pair.target_phantom.reverse_segment_id.id);
|
||||
|
||||
UnpackCHPath(
|
||||
facade,
|
||||
packed_path_begin,
|
||||
packed_path_end,
|
||||
[this,
|
||||
&facade,
|
||||
&unpacked_path,
|
||||
&phantom_node_pair,
|
||||
&start_traversed_in_reverse,
|
||||
&target_traversed_in_reverse](std::pair<NodeID, NodeID> & /* edge */,
|
||||
const EdgeData &edge_data) {
|
||||
|
||||
BOOST_ASSERT_MSG(!edge_data.shortcut, "original edge flagged as shortcut");
|
||||
const auto name_index = facade.GetNameIndexFromEdgeID(edge_data.id);
|
||||
const auto turn_instruction = facade.GetTurnInstructionForEdgeID(edge_data.id);
|
||||
const extractor::TravelMode travel_mode =
|
||||
(unpacked_path.empty() && start_traversed_in_reverse)
|
||||
? phantom_node_pair.source_phantom.backward_travel_mode
|
||||
: facade.GetTravelModeForEdgeID(edge_data.id);
|
||||
|
||||
const auto geometry_index = facade.GetGeometryIndexForEdgeID(edge_data.id);
|
||||
std::vector<NodeID> id_vector;
|
||||
|
||||
std::vector<EdgeWeight> weight_vector;
|
||||
std::vector<EdgeWeight> duration_vector;
|
||||
std::vector<DatasourceID> datasource_vector;
|
||||
if (geometry_index.forward)
|
||||
{
|
||||
id_vector = facade.GetUncompressedForwardGeometry(geometry_index.id);
|
||||
weight_vector = facade.GetUncompressedForwardWeights(geometry_index.id);
|
||||
duration_vector = facade.GetUncompressedForwardDurations(geometry_index.id);
|
||||
datasource_vector = facade.GetUncompressedForwardDatasources(geometry_index.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
id_vector = facade.GetUncompressedReverseGeometry(geometry_index.id);
|
||||
weight_vector = facade.GetUncompressedReverseWeights(geometry_index.id);
|
||||
duration_vector = facade.GetUncompressedReverseDurations(geometry_index.id);
|
||||
datasource_vector = facade.GetUncompressedReverseDatasources(geometry_index.id);
|
||||
}
|
||||
BOOST_ASSERT(id_vector.size() > 0);
|
||||
BOOST_ASSERT(datasource_vector.size() > 0);
|
||||
BOOST_ASSERT(weight_vector.size() == id_vector.size() - 1);
|
||||
BOOST_ASSERT(duration_vector.size() == id_vector.size() - 1);
|
||||
const bool is_first_segment = unpacked_path.empty();
|
||||
|
||||
const std::size_t start_index =
|
||||
(is_first_segment
|
||||
? ((start_traversed_in_reverse)
|
||||
? weight_vector.size() -
|
||||
phantom_node_pair.source_phantom.fwd_segment_position - 1
|
||||
: phantom_node_pair.source_phantom.fwd_segment_position)
|
||||
: 0);
|
||||
const std::size_t end_index = weight_vector.size();
|
||||
|
||||
BOOST_ASSERT(start_index >= 0);
|
||||
BOOST_ASSERT(start_index < end_index);
|
||||
for (std::size_t segment_idx = start_index; segment_idx < end_index; ++segment_idx)
|
||||
{
|
||||
unpacked_path.push_back(
|
||||
PathData{id_vector[segment_idx + 1],
|
||||
name_index,
|
||||
weight_vector[segment_idx],
|
||||
duration_vector[segment_idx],
|
||||
extractor::guidance::TurnInstruction::NO_TURN(),
|
||||
{{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID},
|
||||
travel_mode,
|
||||
INVALID_ENTRY_CLASSID,
|
||||
datasource_vector[segment_idx],
|
||||
util::guidance::TurnBearing(0),
|
||||
util::guidance::TurnBearing(0)});
|
||||
}
|
||||
BOOST_ASSERT(unpacked_path.size() > 0);
|
||||
if (facade.hasLaneData(edge_data.id))
|
||||
unpacked_path.back().lane_data = facade.GetLaneData(edge_data.id);
|
||||
|
||||
unpacked_path.back().entry_classid = facade.GetEntryClassID(edge_data.id);
|
||||
unpacked_path.back().turn_instruction = turn_instruction;
|
||||
unpacked_path.back().duration_until_turn +=
|
||||
facade.GetDurationPenaltyForEdgeID(edge_data.id);
|
||||
unpacked_path.back().weight_until_turn +=
|
||||
facade.GetWeightPenaltyForEdgeID(edge_data.id);
|
||||
unpacked_path.back().pre_turn_bearing = facade.PreTurnBearing(edge_data.id);
|
||||
unpacked_path.back().post_turn_bearing = facade.PostTurnBearing(edge_data.id);
|
||||
});
|
||||
|
||||
std::size_t start_index = 0, end_index = 0;
|
||||
std::vector<unsigned> id_vector;
|
||||
std::vector<EdgeWeight> weight_vector;
|
||||
std::vector<EdgeWeight> duration_vector;
|
||||
std::vector<DatasourceID> datasource_vector;
|
||||
const bool is_local_path = (phantom_node_pair.source_phantom.packed_geometry_id ==
|
||||
phantom_node_pair.target_phantom.packed_geometry_id) &&
|
||||
unpacked_path.empty();
|
||||
|
||||
if (target_traversed_in_reverse)
|
||||
// If we didn't find one there, the we might be looking at a part of the path that
|
||||
// was found using the backward search. Here, we flip the node order (.second, .first)
|
||||
// and only consider edges with the `.backward` flag.
|
||||
if (SPECIAL_EDGEID == smaller_edge_id)
|
||||
{
|
||||
id_vector = facade.GetUncompressedReverseGeometry(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
smaller_edge_id = facade.FindSmallestEdge(
|
||||
edge.second, edge.first, [](const auto &data) { return data.backward; });
|
||||
}
|
||||
|
||||
weight_vector = facade.GetUncompressedReverseWeights(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
// If we didn't find anything *still*, then something is broken and someone has
|
||||
// called this function with bad values.
|
||||
BOOST_ASSERT_MSG(smaller_edge_id != SPECIAL_EDGEID, "Invalid smaller edge ID");
|
||||
|
||||
duration_vector = facade.GetUncompressedReverseDurations(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
const auto &data = facade.GetEdgeData(smaller_edge_id);
|
||||
BOOST_ASSERT_MSG(data.weight != std::numeric_limits<EdgeWeight>::max(),
|
||||
"edge weight invalid");
|
||||
|
||||
datasource_vector = facade.GetUncompressedReverseDatasources(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
if (is_local_path)
|
||||
{
|
||||
start_index = weight_vector.size() -
|
||||
phantom_node_pair.source_phantom.fwd_segment_position - 1;
|
||||
}
|
||||
end_index =
|
||||
weight_vector.size() - phantom_node_pair.target_phantom.fwd_segment_position - 1;
|
||||
// If the edge is a shortcut, we need to add the two halfs to the stack.
|
||||
if (data.shortcut)
|
||||
{ // unpack
|
||||
const NodeID middle_node_id = data.id;
|
||||
// Note the order here - we're adding these to a stack, so we
|
||||
// want the first->middle to get visited before middle->second
|
||||
recursion_stack.emplace(middle_node_id, edge.second);
|
||||
recursion_stack.emplace(edge.first, middle_node_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_local_path)
|
||||
{
|
||||
start_index = phantom_node_pair.source_phantom.fwd_segment_position;
|
||||
}
|
||||
end_index = phantom_node_pair.target_phantom.fwd_segment_position;
|
||||
|
||||
id_vector = facade.GetUncompressedForwardGeometry(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
weight_vector = facade.GetUncompressedForwardWeights(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
duration_vector = facade.GetUncompressedForwardDurations(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
datasource_vector = facade.GetUncompressedForwardDatasources(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
}
|
||||
|
||||
// Given the following compressed geometry:
|
||||
// U---v---w---x---y---Z
|
||||
// s t
|
||||
// s: fwd_segment 0
|
||||
// t: fwd_segment 3
|
||||
// -> (U, v), (v, w), (w, x)
|
||||
// note that (x, t) is _not_ included but needs to be added later.
|
||||
for (std::size_t segment_idx = start_index; segment_idx != end_index;
|
||||
(start_index < end_index ? ++segment_idx : --segment_idx))
|
||||
{
|
||||
BOOST_ASSERT(segment_idx < id_vector.size() - 1);
|
||||
BOOST_ASSERT(phantom_node_pair.target_phantom.forward_travel_mode > 0);
|
||||
unpacked_path.push_back(PathData{
|
||||
id_vector[start_index < end_index ? segment_idx + 1 : segment_idx - 1],
|
||||
phantom_node_pair.target_phantom.name_id,
|
||||
weight_vector[segment_idx],
|
||||
duration_vector[segment_idx],
|
||||
extractor::guidance::TurnInstruction::NO_TURN(),
|
||||
{{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID},
|
||||
target_traversed_in_reverse ? phantom_node_pair.target_phantom.backward_travel_mode
|
||||
: phantom_node_pair.target_phantom.forward_travel_mode,
|
||||
INVALID_ENTRY_CLASSID,
|
||||
datasource_vector[segment_idx],
|
||||
util::guidance::TurnBearing(0),
|
||||
util::guidance::TurnBearing(0)});
|
||||
}
|
||||
|
||||
if (unpacked_path.size() > 0)
|
||||
{
|
||||
const auto source_weight = start_traversed_in_reverse
|
||||
? phantom_node_pair.source_phantom.reverse_weight
|
||||
: phantom_node_pair.source_phantom.forward_weight;
|
||||
const auto source_duration = start_traversed_in_reverse
|
||||
? phantom_node_pair.source_phantom.reverse_duration
|
||||
: phantom_node_pair.source_phantom.forward_duration;
|
||||
// The above code will create segments for (v, w), (w,x), (x, y) and (y, Z).
|
||||
// However the first segment duration needs to be adjusted to the fact that the source
|
||||
// phantom is in the middle of the segment. We do this by subtracting v--s from the
|
||||
// duration.
|
||||
|
||||
// Since it's possible duration_until_turn can be less than source_weight here if
|
||||
// a negative enough turn penalty is used to modify this edge weight during
|
||||
// osrm-contract, we clamp to 0 here so as not to return a negative duration
|
||||
// for this segment.
|
||||
|
||||
// TODO this creates a scenario where it's possible the duration from a phantom
|
||||
// node to the first turn would be the same as from end to end of a segment,
|
||||
// which is obviously incorrect and not ideal...
|
||||
unpacked_path.front().weight_until_turn =
|
||||
std::max(unpacked_path.front().weight_until_turn - source_weight, 0);
|
||||
unpacked_path.front().duration_until_turn =
|
||||
std::max(unpacked_path.front().duration_until_turn - source_duration, 0);
|
||||
}
|
||||
|
||||
// there is no equivalent to a node-based node in an edge-expanded graph.
|
||||
// two equivalent routes may start (or end) at different node-based edges
|
||||
// as they are added with the offset how much "weight" on the edge
|
||||
// has already been traversed. Depending on offset one needs to remove
|
||||
// the last node.
|
||||
if (unpacked_path.size() > 1)
|
||||
{
|
||||
const std::size_t last_index = unpacked_path.size() - 1;
|
||||
const std::size_t second_to_last_index = last_index - 1;
|
||||
|
||||
if (unpacked_path[last_index].turn_via_node ==
|
||||
unpacked_path[second_to_last_index].turn_via_node)
|
||||
{
|
||||
unpacked_path.pop_back();
|
||||
}
|
||||
BOOST_ASSERT(!unpacked_path.empty());
|
||||
// We found an original edge, call our callback.
|
||||
std::forward<Callback>(callback)(edge, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a single edge (NodeID->NodeID) from the CH graph down to it's original non-shortcut
|
||||
* route.
|
||||
* @param from the node the CH edge starts at
|
||||
* @param to the node the CH edge finishes at
|
||||
* @param unpacked_path the sequence of original NodeIDs that make up the expanded CH edge
|
||||
*/
|
||||
void UnpackEdge(const FacadeT &facade,
|
||||
const NodeID from,
|
||||
const NodeID to,
|
||||
std::vector<NodeID> &unpacked_path) const;
|
||||
// Should work both for CH and not CH if the unpackPath function above is implemented a proper
|
||||
// implementation.
|
||||
template <typename RandomIter, typename FacadeT>
|
||||
void unpackPath(const FacadeT &facade,
|
||||
RandomIter packed_path_begin,
|
||||
RandomIter packed_path_end,
|
||||
const PhantomNodes &phantom_node_pair,
|
||||
std::vector<PathData> &unpacked_path)
|
||||
{
|
||||
BOOST_ASSERT(std::distance(packed_path_begin, packed_path_end) > 0);
|
||||
|
||||
void RetrievePackedPathFromHeap(const SearchEngineData::QueryHeap &forward_heap,
|
||||
const SearchEngineData::QueryHeap &reverse_heap,
|
||||
const NodeID middle_node_id,
|
||||
std::vector<NodeID> &packed_path) const;
|
||||
const bool start_traversed_in_reverse =
|
||||
(*packed_path_begin != phantom_node_pair.source_phantom.forward_segment_id.id);
|
||||
const bool target_traversed_in_reverse =
|
||||
(*std::prev(packed_path_end) != phantom_node_pair.target_phantom.forward_segment_id.id);
|
||||
|
||||
void RetrievePackedPathFromSingleHeap(const SearchEngineData::QueryHeap &search_heap,
|
||||
const NodeID middle_node_id,
|
||||
std::vector<NodeID> &packed_path) const;
|
||||
BOOST_ASSERT(*packed_path_begin == phantom_node_pair.source_phantom.forward_segment_id.id ||
|
||||
*packed_path_begin == phantom_node_pair.source_phantom.reverse_segment_id.id);
|
||||
BOOST_ASSERT(
|
||||
*std::prev(packed_path_end) == phantom_node_pair.target_phantom.forward_segment_id.id ||
|
||||
*std::prev(packed_path_end) == phantom_node_pair.target_phantom.reverse_segment_id.id);
|
||||
|
||||
// assumes that heaps are already setup correctly.
|
||||
// ATTENTION: This only works if no additional offset is supplied next to the Phantom Node
|
||||
// Offsets.
|
||||
// In case additional offsets are supplied, you might have to force a loop first.
|
||||
// A forced loop might be necessary, if source and target are on the same segment.
|
||||
// If this is the case and the offsets of the respective direction are larger for the source
|
||||
// than the target
|
||||
// then a force loop is required (e.g. source_phantom.forward_segment_id ==
|
||||
// target_phantom.forward_segment_id
|
||||
// && source_phantom.GetForwardWeightPlusOffset() > target_phantom.GetForwardWeightPlusOffset())
|
||||
// requires
|
||||
// a force loop, if the heaps have been initialized with positive offsets.
|
||||
void Search(const FacadeT &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
std::int32_t &weight,
|
||||
std::vector<NodeID> &packed_leg,
|
||||
const bool force_loop_forward,
|
||||
const bool force_loop_reverse,
|
||||
const int duration_upper_bound = INVALID_EDGE_WEIGHT) const;
|
||||
unpackPath(
|
||||
facade,
|
||||
packed_path_begin,
|
||||
packed_path_end,
|
||||
[&facade,
|
||||
&unpacked_path,
|
||||
&phantom_node_pair,
|
||||
&start_traversed_in_reverse,
|
||||
&target_traversed_in_reverse](std::pair<NodeID, NodeID> & /* edge */,
|
||||
const auto &edge_data) {
|
||||
|
||||
// assumes that heaps are already setup correctly.
|
||||
// A forced loop might be necessary, if source and target are on the same segment.
|
||||
// If this is the case and the offsets of the respective direction are larger for the source
|
||||
// than the target
|
||||
// then a force loop is required (e.g. source_phantom.forward_segment_id ==
|
||||
// target_phantom.forward_segment_id
|
||||
// && source_phantom.GetForwardWeightPlusOffset() > target_phantom.GetForwardWeightPlusOffset())
|
||||
// requires
|
||||
// a force loop, if the heaps have been initialized with positive offsets.
|
||||
void SearchWithCore(const FacadeT &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
SearchEngineData::QueryHeap &forward_core_heap,
|
||||
SearchEngineData::QueryHeap &reverse_core_heap,
|
||||
int &weight,
|
||||
std::vector<NodeID> &packed_leg,
|
||||
const bool force_loop_forward,
|
||||
const bool force_loop_reverse,
|
||||
int duration_upper_bound = INVALID_EDGE_WEIGHT) const;
|
||||
BOOST_ASSERT_MSG(!edge_data.shortcut, "original edge flagged as shortcut");
|
||||
const auto name_index = facade.GetNameIndexFromEdgeID(edge_data.id);
|
||||
const auto turn_instruction = facade.GetTurnInstructionForEdgeID(edge_data.id);
|
||||
const extractor::TravelMode travel_mode =
|
||||
(unpacked_path.empty() && start_traversed_in_reverse)
|
||||
? phantom_node_pair.source_phantom.backward_travel_mode
|
||||
: facade.GetTravelModeForEdgeID(edge_data.id);
|
||||
|
||||
bool NeedsLoopForward(const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom) const;
|
||||
const auto geometry_index = facade.GetGeometryIndexForEdgeID(edge_data.id);
|
||||
std::vector<NodeID> id_vector;
|
||||
|
||||
bool NeedsLoopBackwards(const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom) const;
|
||||
std::vector<EdgeWeight> weight_vector;
|
||||
std::vector<EdgeWeight> duration_vector;
|
||||
std::vector<DatasourceID> datasource_vector;
|
||||
if (geometry_index.forward)
|
||||
{
|
||||
id_vector = facade.GetUncompressedForwardGeometry(geometry_index.id);
|
||||
weight_vector = facade.GetUncompressedForwardWeights(geometry_index.id);
|
||||
duration_vector = facade.GetUncompressedForwardDurations(geometry_index.id);
|
||||
datasource_vector = facade.GetUncompressedForwardDatasources(geometry_index.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
id_vector = facade.GetUncompressedReverseGeometry(geometry_index.id);
|
||||
weight_vector = facade.GetUncompressedReverseWeights(geometry_index.id);
|
||||
duration_vector = facade.GetUncompressedReverseDurations(geometry_index.id);
|
||||
datasource_vector = facade.GetUncompressedReverseDatasources(geometry_index.id);
|
||||
}
|
||||
BOOST_ASSERT(id_vector.size() > 0);
|
||||
BOOST_ASSERT(datasource_vector.size() > 0);
|
||||
BOOST_ASSERT(weight_vector.size() == id_vector.size() - 1);
|
||||
BOOST_ASSERT(duration_vector.size() == id_vector.size() - 1);
|
||||
const bool is_first_segment = unpacked_path.empty();
|
||||
|
||||
double GetPathDistance(const FacadeT &facade,
|
||||
const std::vector<NodeID> &packed_path,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom) const;
|
||||
const std::size_t start_index =
|
||||
(is_first_segment
|
||||
? ((start_traversed_in_reverse)
|
||||
? weight_vector.size() -
|
||||
phantom_node_pair.source_phantom.fwd_segment_position - 1
|
||||
: phantom_node_pair.source_phantom.fwd_segment_position)
|
||||
: 0);
|
||||
const std::size_t end_index = weight_vector.size();
|
||||
|
||||
// Requires the heaps for be empty
|
||||
// If heaps should be adjusted to be initialized outside of this function,
|
||||
// the addition of force_loop parameters might be required
|
||||
double GetNetworkDistanceWithCore(const FacadeT &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
SearchEngineData::QueryHeap &forward_core_heap,
|
||||
SearchEngineData::QueryHeap &reverse_core_heap,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom,
|
||||
int duration_upper_bound = INVALID_EDGE_WEIGHT) const;
|
||||
BOOST_ASSERT(start_index >= 0);
|
||||
BOOST_ASSERT(start_index < end_index);
|
||||
for (std::size_t segment_idx = start_index; segment_idx < end_index; ++segment_idx)
|
||||
{
|
||||
unpacked_path.push_back(PathData{id_vector[segment_idx + 1],
|
||||
name_index,
|
||||
weight_vector[segment_idx],
|
||||
duration_vector[segment_idx],
|
||||
extractor::guidance::TurnInstruction::NO_TURN(),
|
||||
{{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID},
|
||||
travel_mode,
|
||||
INVALID_ENTRY_CLASSID,
|
||||
datasource_vector[segment_idx],
|
||||
util::guidance::TurnBearing(0),
|
||||
util::guidance::TurnBearing(0)});
|
||||
}
|
||||
BOOST_ASSERT(unpacked_path.size() > 0);
|
||||
if (facade.hasLaneData(edge_data.id))
|
||||
unpacked_path.back().lane_data = facade.GetLaneData(edge_data.id);
|
||||
|
||||
// Requires the heaps for be empty
|
||||
// If heaps should be adjusted to be initialized outside of this function,
|
||||
// the addition of force_loop parameters might be required
|
||||
double GetNetworkDistance(const FacadeT &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom,
|
||||
int duration_upper_bound = INVALID_EDGE_WEIGHT) const;
|
||||
};
|
||||
unpacked_path.back().entry_classid = facade.GetEntryClassID(edge_data.id);
|
||||
unpacked_path.back().turn_instruction = turn_instruction;
|
||||
unpacked_path.back().duration_until_turn +=
|
||||
facade.GetDurationPenaltyForEdgeID(edge_data.id);
|
||||
unpacked_path.back().weight_until_turn +=
|
||||
facade.GetWeightPenaltyForEdgeID(edge_data.id);
|
||||
unpacked_path.back().pre_turn_bearing = facade.PreTurnBearing(edge_data.id);
|
||||
unpacked_path.back().post_turn_bearing = facade.PostTurnBearing(edge_data.id);
|
||||
});
|
||||
|
||||
std::size_t start_index = 0, end_index = 0;
|
||||
std::vector<unsigned> id_vector;
|
||||
std::vector<EdgeWeight> weight_vector;
|
||||
std::vector<EdgeWeight> duration_vector;
|
||||
std::vector<DatasourceID> datasource_vector;
|
||||
const bool is_local_path = (phantom_node_pair.source_phantom.packed_geometry_id ==
|
||||
phantom_node_pair.target_phantom.packed_geometry_id) &&
|
||||
unpacked_path.empty();
|
||||
|
||||
if (target_traversed_in_reverse)
|
||||
{
|
||||
id_vector = facade.GetUncompressedReverseGeometry(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
weight_vector = facade.GetUncompressedReverseWeights(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
duration_vector = facade.GetUncompressedReverseDurations(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
datasource_vector = facade.GetUncompressedReverseDatasources(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
if (is_local_path)
|
||||
{
|
||||
start_index =
|
||||
weight_vector.size() - phantom_node_pair.source_phantom.fwd_segment_position - 1;
|
||||
}
|
||||
end_index =
|
||||
weight_vector.size() - phantom_node_pair.target_phantom.fwd_segment_position - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_local_path)
|
||||
{
|
||||
start_index = phantom_node_pair.source_phantom.fwd_segment_position;
|
||||
}
|
||||
end_index = phantom_node_pair.target_phantom.fwd_segment_position;
|
||||
|
||||
id_vector = facade.GetUncompressedForwardGeometry(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
weight_vector = facade.GetUncompressedForwardWeights(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
duration_vector = facade.GetUncompressedForwardDurations(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
|
||||
datasource_vector = facade.GetUncompressedForwardDatasources(
|
||||
phantom_node_pair.target_phantom.packed_geometry_id);
|
||||
}
|
||||
|
||||
// Given the following compressed geometry:
|
||||
// U---v---w---x---y---Z
|
||||
// s t
|
||||
// s: fwd_segment 0
|
||||
// t: fwd_segment 3
|
||||
// -> (U, v), (v, w), (w, x)
|
||||
// note that (x, t) is _not_ included but needs to be added later.
|
||||
for (std::size_t segment_idx = start_index; segment_idx != end_index;
|
||||
(start_index < end_index ? ++segment_idx : --segment_idx))
|
||||
{
|
||||
BOOST_ASSERT(segment_idx < id_vector.size() - 1);
|
||||
BOOST_ASSERT(phantom_node_pair.target_phantom.forward_travel_mode > 0);
|
||||
unpacked_path.push_back(PathData{
|
||||
id_vector[start_index < end_index ? segment_idx + 1 : segment_idx - 1],
|
||||
phantom_node_pair.target_phantom.name_id,
|
||||
weight_vector[segment_idx],
|
||||
duration_vector[segment_idx],
|
||||
extractor::guidance::TurnInstruction::NO_TURN(),
|
||||
{{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID},
|
||||
target_traversed_in_reverse ? phantom_node_pair.target_phantom.backward_travel_mode
|
||||
: phantom_node_pair.target_phantom.forward_travel_mode,
|
||||
INVALID_ENTRY_CLASSID,
|
||||
datasource_vector[segment_idx],
|
||||
util::guidance::TurnBearing(0),
|
||||
util::guidance::TurnBearing(0)});
|
||||
}
|
||||
|
||||
if (unpacked_path.size() > 0)
|
||||
{
|
||||
const auto source_weight = start_traversed_in_reverse
|
||||
? phantom_node_pair.source_phantom.reverse_weight
|
||||
: phantom_node_pair.source_phantom.forward_weight;
|
||||
const auto source_duration = start_traversed_in_reverse
|
||||
? phantom_node_pair.source_phantom.reverse_duration
|
||||
: phantom_node_pair.source_phantom.forward_duration;
|
||||
// The above code will create segments for (v, w), (w,x), (x, y) and (y, Z).
|
||||
// However the first segment duration needs to be adjusted to the fact that the source
|
||||
// phantom is in the middle of the segment. We do this by subtracting v--s from the
|
||||
// duration.
|
||||
|
||||
// Since it's possible duration_until_turn can be less than source_weight here if
|
||||
// a negative enough turn penalty is used to modify this edge weight during
|
||||
// osrm-contract, we clamp to 0 here so as not to return a negative duration
|
||||
// for this segment.
|
||||
|
||||
// TODO this creates a scenario where it's possible the duration from a phantom
|
||||
// node to the first turn would be the same as from end to end of a segment,
|
||||
// which is obviously incorrect and not ideal...
|
||||
unpacked_path.front().weight_until_turn =
|
||||
std::max(unpacked_path.front().weight_until_turn - source_weight, 0);
|
||||
unpacked_path.front().duration_until_turn =
|
||||
std::max(unpacked_path.front().duration_until_turn - source_duration, 0);
|
||||
}
|
||||
|
||||
// there is no equivalent to a node-based node in an edge-expanded graph.
|
||||
// two equivalent routes may start (or end) at different node-based edges
|
||||
// as they are added with the offset how much "weight" on the edge
|
||||
// has already been traversed. Depending on offset one needs to remove
|
||||
// the last node.
|
||||
if (unpacked_path.size() > 1)
|
||||
{
|
||||
const std::size_t last_index = unpacked_path.size() - 1;
|
||||
const std::size_t second_to_last_index = last_index - 1;
|
||||
|
||||
if (unpacked_path[last_index].turn_via_node ==
|
||||
unpacked_path[second_to_last_index].turn_via_node)
|
||||
{
|
||||
unpacked_path.pop_back();
|
||||
}
|
||||
BOOST_ASSERT(!unpacked_path.empty());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a single edge (NodeID->NodeID) from the CH graph down to it's original non-shortcut
|
||||
* route.
|
||||
* @param from the node the CH edge starts at
|
||||
* @param to the node the CH edge finishes at
|
||||
* @param unpacked_path the sequence of original NodeIDs that make up the expanded CH edge
|
||||
*/
|
||||
void unpackEdge(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const NodeID from,
|
||||
const NodeID to,
|
||||
std::vector<NodeID> &unpacked_path);
|
||||
|
||||
void retrievePackedPathFromHeap(const SearchEngineData::QueryHeap &forward_heap,
|
||||
const SearchEngineData::QueryHeap &reverse_heap,
|
||||
const NodeID middle_node_id,
|
||||
std::vector<NodeID> &packed_path);
|
||||
|
||||
void retrievePackedPathFromSingleHeap(const SearchEngineData::QueryHeap &search_heap,
|
||||
const NodeID middle_node_id,
|
||||
std::vector<NodeID> &packed_path);
|
||||
|
||||
// assumes that heaps are already setup correctly.
|
||||
// ATTENTION: This only works if no additional offset is supplied next to the Phantom Node
|
||||
// Offsets.
|
||||
// In case additional offsets are supplied, you might have to force a loop first.
|
||||
// A forced loop might be necessary, if source and target are on the same segment.
|
||||
// If this is the case and the offsets of the respective direction are larger for the source
|
||||
// than the target
|
||||
// then a force loop is required (e.g. source_phantom.forward_segment_id ==
|
||||
// target_phantom.forward_segment_id
|
||||
// && source_phantom.GetForwardWeightPlusOffset() > target_phantom.GetForwardWeightPlusOffset())
|
||||
// requires
|
||||
// a force loop, if the heaps have been initialized with positive offsets.
|
||||
void search(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
std::int32_t &weight,
|
||||
std::vector<NodeID> &packed_leg,
|
||||
const bool force_loop_forward,
|
||||
const bool force_loop_reverse,
|
||||
const int duration_upper_bound = INVALID_EDGE_WEIGHT);
|
||||
|
||||
// assumes that heaps are already setup correctly.
|
||||
// A forced loop might be necessary, if source and target are on the same segment.
|
||||
// If this is the case and the offsets of the respective direction are larger for the source
|
||||
// than the target
|
||||
// then a force loop is required (e.g. source_phantom.forward_segment_id ==
|
||||
// target_phantom.forward_segment_id
|
||||
// && source_phantom.GetForwardWeightPlusOffset() > target_phantom.GetForwardWeightPlusOffset())
|
||||
// requires
|
||||
// a force loop, if the heaps have been initialized with positive offsets.
|
||||
void searchWithCore(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
SearchEngineData::QueryHeap &forward_core_heap,
|
||||
SearchEngineData::QueryHeap &reverse_core_heap,
|
||||
int &weight,
|
||||
std::vector<NodeID> &packed_leg,
|
||||
const bool force_loop_forward,
|
||||
const bool force_loop_reverse,
|
||||
int duration_upper_bound = INVALID_EDGE_WEIGHT);
|
||||
|
||||
bool needsLoopForward(const PhantomNode &source_phantom, const PhantomNode &target_phantom);
|
||||
|
||||
bool needsLoopBackwards(const PhantomNode &source_phantom, const PhantomNode &target_phantom);
|
||||
|
||||
double getPathDistance(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const std::vector<NodeID> &packed_path,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom);
|
||||
|
||||
// Requires the heaps for be empty
|
||||
// If heaps should be adjusted to be initialized outside of this function,
|
||||
// the addition of force_loop parameters might be required
|
||||
double getNetworkDistanceWithCore(
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
SearchEngineData::QueryHeap &forward_core_heap,
|
||||
SearchEngineData::QueryHeap &reverse_core_heap,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom,
|
||||
int duration_upper_bound = INVALID_EDGE_WEIGHT);
|
||||
|
||||
// Requires the heaps for be empty
|
||||
// If heaps should be adjusted to be initialized outside of this function,
|
||||
// the addition of force_loop parameters might be required
|
||||
double
|
||||
getNetworkDistance(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
SearchEngineData::QueryHeap &forward_heap,
|
||||
SearchEngineData::QueryHeap &reverse_heap,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom,
|
||||
int duration_upper_bound = INVALID_EDGE_WEIGHT);
|
||||
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
|
||||
@@ -4,13 +4,8 @@
|
||||
#include "engine/algorithm.hpp"
|
||||
#include "engine/routing_algorithms/routing_base.hpp"
|
||||
#include "engine/search_engine_data.hpp"
|
||||
#include "util/integer_range.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace engine
|
||||
@@ -18,75 +13,12 @@ namespace engine
|
||||
namespace routing_algorithms
|
||||
{
|
||||
|
||||
template <typename AlgorithmT> class ShortestPathRouting;
|
||||
InternalRouteResult
|
||||
shortestPathSearch(SearchEngineData &engine_working_data,
|
||||
const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const std::vector<PhantomNodes> &phantom_nodes_vector,
|
||||
const boost::optional<bool> continue_straight_at_waypoint);
|
||||
|
||||
template <> class ShortestPathRouting<algorithm::CH> final : public BasicRouting<algorithm::CH>
|
||||
{
|
||||
using super = BasicRouting<algorithm::CH>;
|
||||
using FacadeT = datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH>;
|
||||
using QueryHeap = SearchEngineData::QueryHeap;
|
||||
SearchEngineData &engine_working_data;
|
||||
const static constexpr bool DO_NOT_FORCE_LOOP = false;
|
||||
|
||||
public:
|
||||
ShortestPathRouting(SearchEngineData &engine_working_data)
|
||||
: engine_working_data(engine_working_data)
|
||||
{
|
||||
}
|
||||
|
||||
~ShortestPathRouting() {}
|
||||
|
||||
// allows a uturn at the target_phantom
|
||||
// searches source forward/reverse -> target forward/reverse
|
||||
void SearchWithUTurn(const FacadeT &facade,
|
||||
QueryHeap &forward_heap,
|
||||
QueryHeap &reverse_heap,
|
||||
QueryHeap &forward_core_heap,
|
||||
QueryHeap &reverse_core_heap,
|
||||
const bool search_from_forward_node,
|
||||
const bool search_from_reverse_node,
|
||||
const bool search_to_forward_node,
|
||||
const bool search_to_reverse_node,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom,
|
||||
const int total_weight_to_forward,
|
||||
const int total_weight_to_reverse,
|
||||
int &new_total_weight,
|
||||
std::vector<NodeID> &leg_packed_path) const;
|
||||
|
||||
// searches shortest path between:
|
||||
// source forward/reverse -> target forward
|
||||
// source forward/reverse -> target reverse
|
||||
void Search(const FacadeT &facade,
|
||||
QueryHeap &forward_heap,
|
||||
QueryHeap &reverse_heap,
|
||||
QueryHeap &forward_core_heap,
|
||||
QueryHeap &reverse_core_heap,
|
||||
const bool search_from_forward_node,
|
||||
const bool search_from_reverse_node,
|
||||
const bool search_to_forward_node,
|
||||
const bool search_to_reverse_node,
|
||||
const PhantomNode &source_phantom,
|
||||
const PhantomNode &target_phantom,
|
||||
const int total_weight_to_forward,
|
||||
const int total_weight_to_reverse,
|
||||
int &new_total_weight_to_forward,
|
||||
int &new_total_weight_to_reverse,
|
||||
std::vector<NodeID> &leg_packed_path_forward,
|
||||
std::vector<NodeID> &leg_packed_path_reverse) const;
|
||||
|
||||
void UnpackLegs(const FacadeT &facade,
|
||||
const std::vector<PhantomNodes> &phantom_nodes_vector,
|
||||
const std::vector<NodeID> &total_packed_path,
|
||||
const std::vector<std::size_t> &packed_leg_begin,
|
||||
const int shortest_path_length,
|
||||
InternalRouteResult &raw_route_data) const;
|
||||
|
||||
void operator()(const FacadeT &facade,
|
||||
const std::vector<PhantomNodes> &phantom_nodes_vector,
|
||||
const boost::optional<bool> continue_straight_at_waypoint,
|
||||
InternalRouteResult &raw_route_data) const;
|
||||
};
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
} // namespace osrm
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#ifndef OSRM_ENGINE_ROUTING_ALGORITHMS_TILE_TURNS_HPP
|
||||
#define OSRM_ENGINE_ROUTING_ALGORITHMS_TILE_TURNS_HPP
|
||||
|
||||
#include "engine/routing_algorithms/routing_base.hpp"
|
||||
|
||||
#include "engine/algorithm.hpp"
|
||||
#include "engine/search_engine_data.hpp"
|
||||
#include "engine/datafacade/contiguous_internalmem_datafacade.hpp"
|
||||
|
||||
#include "util/coordinate.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace engine
|
||||
@@ -14,8 +16,6 @@ namespace engine
|
||||
namespace routing_algorithms
|
||||
{
|
||||
|
||||
template <typename AlgorithmT> class TileTurns;
|
||||
|
||||
// Used to accumulate all the information we want in the tile about a turn.
|
||||
struct TurnData final
|
||||
{
|
||||
@@ -25,19 +25,12 @@ struct TurnData final
|
||||
const int weight;
|
||||
};
|
||||
|
||||
/// This class is used to extract turn information for the tile plugin from a CH graph
|
||||
template <> class TileTurns<algorithm::CH> final : public BasicRouting<algorithm::CH>
|
||||
{
|
||||
using super = BasicRouting<algorithm::CH>;
|
||||
using FacadeT = datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH>;
|
||||
using RTreeLeaf = datafacade::BaseDataFacade::RTreeLeaf;
|
||||
|
||||
using RTreeLeaf = datafacade::BaseDataFacade::RTreeLeaf;
|
||||
|
||||
public:
|
||||
std::vector<TurnData> operator()(const FacadeT &facade,
|
||||
const std::vector<RTreeLeaf> &edges,
|
||||
const std::vector<std::size_t> &sorted_edge_indexes) const;
|
||||
};
|
||||
std::vector<TurnData>
|
||||
getTileTurns(const datafacade::ContiguousInternalMemoryDataFacade<algorithm::CH> &facade,
|
||||
const std::vector<RTreeLeaf> &edges,
|
||||
const std::vector<std::size_t> &sorted_edge_indexes);
|
||||
|
||||
} // namespace routing_algorithms
|
||||
} // namespace engine
|
||||
|
||||
Reference in New Issue
Block a user