osrm-backend/include/extractor/guidance/node_based_graph_walker.hpp
Moritz Kobitzsch e6ff17ab2a refactor merging of segregated roads
adjust to generalFindMaximum function
moved parallel detection to ratio/absolute based regression testing
considerably improved detection quality using normalised regression lines
only follow initial direction/narrow turns for parallel detection
2017-01-03 12:32:51 +01:00

281 lines
11 KiB
C++

#ifndef OSRM_EXTRACTOR_GUIDANCE_NODE_BASED_GRAPH_WALKER
#define OSRM_EXTRACTOR_GUIDANCE_NODE_BASED_GRAPH_WALKER
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "util/coordinate.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp"
#include <boost/assert.hpp>
#include <boost/optional.hpp>
#include <cstdint>
#include <utility>
namespace osrm
{
namespace extractor
{
namespace guidance
{
/*
* The graph hopper is a utility that lets you find certain intersections with a node based graph,
* accumulating information along the way
*/
class NodeBasedGraphWalker
{
public:
NodeBasedGraphWalker(const util::NodeBasedDynamicGraph &node_based_graph,
const IntersectionGenerator &intersection_generator);
/*
* the returned node-id, edge-id are either the last ones used, just prior accumulator
* terminating or empty if the traversal ran into a dead end. For examples of the
* selector/accumulator look below. You can find an example for both (and the required interface
* description). The function returns the last used `NodeID` and `EdgeID` (node just prior to
* the last intersection and the edge it was reached by), if it wasn't stopped early (e.g. the
* selector not provinding any further edge to traverse)
*/
template <class accumulator_type, class selector_type>
boost::optional<std::pair<NodeID, EdgeID>> TraverseRoad(NodeID starting_at_node_id,
EdgeID following_edge_id,
accumulator_type &accumulator,
const selector_type &selector) const;
private:
const util::NodeBasedDynamicGraph &node_based_graph;
const IntersectionGenerator &intersection_generator;
};
/*
* Accumulate all coordinates following a road until we
* Example of a possible accumulator for walking a node-based graph
*/
struct LengthLimitedCoordinateAccumulator
{
LengthLimitedCoordinateAccumulator(
const extractor::guidance::CoordinateExtractor &coordinate_extractor,
const double max_length);
/*
* !! REQUIRED - Function for the use of TraverseRoad in the graph walker.
* Terminate should return true if the last intersection given to accumulator is supposed to
* stop the search. A typical example would be to find the next intersection with degree larger
* than 2 (an actual intersection). Here you should return true if the last intersection you
* looked at was of degree larger than 2.
*/
bool terminate(); // true if the path has traversed enough distance
/*
* !! REQUIRED - Function for the use of TraverseRoad in the graph walker.
* starting with the very first provided node and edge, the graph walker will call `update` on
* your accumulator. Here you can choose to accumulate any data that you might want to collect /
* update your termination criteria. The accumulator described here will extract the coordinates
* that we see traversing `via_edge` and store them for later usage.
*/
void update(const NodeID from_node, const EdgeID via_edge, const NodeID to_node);
double accumulated_length = 0;
std::vector<util::Coordinate> coordinates;
private:
const extractor::guidance::CoordinateExtractor &coordinate_extractor;
const double max_length;
};
/*
* The SelectRoadByNameOnlyChoiceAndStraightness tries to follow a given name along a route. We
* offer methods to skip
* over bridges/similar situations if desired, following narrow turns
* This struct offers an example implementation of a possible road selector for traversing the
* node-based graph using the NodeBasedGraphWalker
*/
struct SelectRoadByNameOnlyChoiceAndStraightness
{
SelectRoadByNameOnlyChoiceAndStraightness(const NameID desired_name_id,
const bool requires_entry);
/*
* !! REQUIRED - Function for the use of TraverseRoad in the graph walker.
* The operator() needs to return (if any is found) the next road to continue in the graph
* traversal. If no such edge is found, return {} is allowed. Usually you want to choose some
* form of obious turn to follow.
*/
boost::optional<EdgeID> operator()(const NodeID nid,
const EdgeID via_edge_id,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const;
private:
const NameID desired_name_id;
const bool requires_entry;
};
/* Following only a straight road
* Follow only the straightmost turn, as long as its the only choice or has the desired name
*/
struct SelectStraightmostRoadByNameAndOnlyChoice
{
SelectStraightmostRoadByNameAndOnlyChoice(const NameID desired_name_id,
const double initial_bearing,
const bool requires_entry);
/*
* !! REQUIRED - Function for the use of TraverseRoad in the graph walker.
* The operator() needs to return (if any is found) the next road to continue in the graph
* traversal. If no such edge is found, return {} is allowed. Usually you want to choose some
* form of obious turn to follow.
*/
boost::optional<EdgeID> operator()(const NodeID nid,
const EdgeID via_edge_id,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const;
private:
const NameID desired_name_id;
const double initial_bearing;
const bool requires_entry;
};
// find the next intersection given a hop limit
struct IntersectionFinderAccumulator
{
IntersectionFinderAccumulator(const std::uint8_t hop_limit,
const IntersectionGenerator &intersection_generator);
// true if the path has traversed enough distance
bool terminate();
// update the accumulator
void update(const NodeID from_node, const EdgeID via_edge, const NodeID to_node);
std::uint8_t hops;
const std::uint8_t hop_limit;
// we need to be able to look-up the intersection
const IntersectionGenerator &intersection_generator;
// the result we are looking for
NodeID nid;
EdgeID via_edge_id;
IntersectionView intersection;
};
template <class accumulator_type, class selector_type>
boost::optional<std::pair<NodeID, EdgeID>>
NodeBasedGraphWalker::TraverseRoad(NodeID current_node_id,
EdgeID current_edge_id,
accumulator_type &accumulator,
const selector_type &selector) const
{
/*
* since graph hopping is used in many ways, we don't generate an adjusted intersection
* (otherwise we could end up in infinite recursion if we call the graph hopper during the
* adjustment itself). Relying only on `GetConnectedRoads` (which itself does no graph hopping),
* we prevent this from happening.
*/
const auto stop_node_id = current_node_id;
/* we wan't to put out the last valid entries. To do so, we need to update within the following
* loop. We use a for loop since traversal of the node-based-graph is expensive and we don't
* want to look at many coordinates. If you require more than 2/3 intersections down the road,
* you are doing something wrong/unsupported by OSRM. To not fail hard in cases that offer
* strange loop contractions, we restrict ourselves to an extremely large number of possible
* steps and simply warn in cases were extraction runs into these limits.
*/
for (std::size_t safety_hop_limit = 0; safety_hop_limit < 1000; ++safety_hop_limit)
{
accumulator.update(
current_node_id, current_edge_id, node_based_graph.GetTarget(current_edge_id));
// we have looped back to our initial intersection
if (node_based_graph.GetTarget(current_edge_id) == stop_node_id)
return {};
// look at the next intersection
const constexpr auto LOW_PRECISION = true;
const auto next_intersection = intersection_generator.GetConnectedRoads(
current_node_id, current_edge_id, LOW_PRECISION);
// don't follow u-turns or go past our initial intersection
if (next_intersection.size() <= 1)
return {};
auto next_edge_id =
selector(current_node_id, current_edge_id, next_intersection, node_based_graph);
if (!next_edge_id)
return {};
if (accumulator.terminate())
return {std::make_pair(current_node_id, current_edge_id)};
current_node_id = node_based_graph.GetTarget(current_edge_id);
current_edge_id = *next_edge_id;
}
BOOST_ASSERT(
"Reached safety hop limit. Graph hopper seems to have been caught in an endless loop");
return {};
}
struct SkipTrafficSignalBarrierRoadSelector
{
boost::optional<EdgeID> operator()(const NodeID,
const EdgeID,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &) const
{
if (intersection.isTrafficSignalOrBarrier())
{
return boost::make_optional(intersection[1].eid);
}
else
{
return boost::none;
}
}
};
struct DistanceToNextIntersectionAccumulator
{
DistanceToNextIntersectionAccumulator(
const extractor::guidance::CoordinateExtractor &extractor_,
const util::NodeBasedDynamicGraph &graph_,
const double threshold)
: extractor{extractor_}, graph{graph_}, threshold{threshold}
{
}
bool terminate()
{
if (distance > threshold)
{
too_far_away = true;
return true;
}
return false;
}
void update(const NodeID start, const EdgeID onto, const NodeID)
{
using namespace util::coordinate_calculation;
const auto coords = extractor.GetForwardCoordinatesAlongRoad(start, onto);
distance += getLength(coords.begin(), coords.end(), &haversineDistance);
}
const extractor::guidance::CoordinateExtractor &extractor;
const util::NodeBasedDynamicGraph &graph;
const double threshold;
bool too_far_away = false;
double distance = 0.;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /* OSRM_EXTRACTOR_GUIDANCE_NODE_BASED_GRAPH_WALKER */