From faa51854408f2bdc15b79bc5ee1f6d0fa57650d4 Mon Sep 17 00:00:00 2001 From: Moritz Kobitzsch Date: Sat, 29 Oct 2016 21:38:10 +0200 Subject: [PATCH] add node-based-graph traversal capabilities --- include/extractor/guidance/intersection.hpp | 26 ++- .../guidance/intersection_generator.hpp | 14 +- .../guidance/node_based_graph_walker.hpp | 177 ++++++++++++++++++ .../guidance/node_based_graph_walker.cpp | 100 ++++++++++ 4 files changed, 309 insertions(+), 8 deletions(-) create mode 100644 include/extractor/guidance/node_based_graph_walker.hpp create mode 100644 src/extractor/guidance/node_based_graph_walker.cpp diff --git a/include/extractor/guidance/intersection.hpp b/include/extractor/guidance/intersection.hpp index 0cc5c4712..54bdec19d 100644 --- a/include/extractor/guidance/intersection.hpp +++ b/include/extractor/guidance/intersection.hpp @@ -5,6 +5,7 @@ #include #include "extractor/guidance/turn_instruction.hpp" +#include "util/guidance/toolkit.hpp" #include "util/typedefs.hpp" // EdgeID namespace osrm @@ -59,7 +60,30 @@ struct ConnectedRoad final // small helper function to print the content of a connected road std::string toString(const ConnectedRoad &road); -typedef std::vector Intersection; +struct Intersection final : public std::vector +{ + using Base = std::vector; + + inline Base::iterator findClosestTurn(double angle) + { + return std::min_element(this->begin(), + this->end(), + [angle](const ConnectedRoad &lhs, const ConnectedRoad &rhs) { + return util::guidance::angularDeviation(lhs.turn.angle, angle) < + util::guidance::angularDeviation(rhs.turn.angle, angle); + }); + } + + inline Base::const_iterator findClosestTurn(double angle) const + { + return std::min_element(this->begin(), + this->end(), + [angle](const ConnectedRoad &lhs, const ConnectedRoad &rhs) { + return util::guidance::angularDeviation(lhs.turn.angle, angle) < + util::guidance::angularDeviation(rhs.turn.angle, angle); + }); + } +}; Intersection::const_iterator findClosestTurn(const Intersection &intersection, const double angle); Intersection::iterator findClosestTurn(Intersection &intersection, const double angle); diff --git a/include/extractor/guidance/intersection_generator.hpp b/include/extractor/guidance/intersection_generator.hpp index 2aa861788..c26ea24ae 100644 --- a/include/extractor/guidance/intersection_generator.hpp +++ b/include/extractor/guidance/intersection_generator.hpp @@ -50,13 +50,6 @@ class IntersectionGenerator // Allow access to the coordinate extractor for all owners const CoordinateExtractor &GetCoordinateExtractor() const; - private: - const util::NodeBasedDynamicGraph &node_based_graph; - const RestrictionMap &restriction_map; - const std::unordered_set &barrier_nodes; - const std::vector &node_info_list; - const CoordinateExtractor coordinate_extractor; - // Check for restrictions/barriers and generate a list of valid and invalid turns present at // the // node reached @@ -65,6 +58,13 @@ class IntersectionGenerator OSRM_ATTR_WARN_UNUSED Intersection GetConnectedRoads(const NodeID from_node, const EdgeID via_eid) const; + private: + const util::NodeBasedDynamicGraph &node_based_graph; + const RestrictionMap &restriction_map; + const std::unordered_set &barrier_nodes; + const std::vector &node_info_list; + const CoordinateExtractor coordinate_extractor; + // check if two indices in an intersection can be seen as a single road in the perceived // intersection representation. See below for an example. Utility function for // MergeSegregatedRoads diff --git a/include/extractor/guidance/node_based_graph_walker.hpp b/include/extractor/guidance/node_based_graph_walker.hpp new file mode 100644 index 000000000..661436568 --- /dev/null +++ b/include/extractor/guidance/node_based_graph_walker.hpp @@ -0,0 +1,177 @@ +#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 "extractor/guidance/toolkit.hpp" +#include "extractor/guidance/toolkit.hpp" +#include "util/coordinate.hpp" +#include "util/node_based_graph.hpp" +#include "util/typedefs.hpp" + +#include +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +// forward declaration to allow interaction between the intersection generator and the graph hopper +class IntersectionGenerator; + +/* + * 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 + boost::optional> TraverseRoad(NodeID starting_at_node_id, + EdgeID following_edge_id, + accumulator_type &accumulator, + const selector_type &selector); + + 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 util::NodeBasedDynamicGraph &node_based_graph, + 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); + + const extractor::guidance::CoordinateExtractor &coordinate_extractor; + const util::NodeBasedDynamicGraph &node_based_graph; + const double max_length; + double accumulated_length; + std::vector coordinates; +}; + +/* + * The FollowRoadNameSelector 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 operator()(const NodeID nid, + const EdgeID via_edge_id, + const Intersection &intersection, + const util::NodeBasedDynamicGraph &node_based_graph) const; + + const NameID desired_name_id; + const bool requires_entry; +}; + +template +boost::optional> +NodeBasedGraphWalker::TraverseRoad(NodeID current_node_id, + EdgeID current_edge_id, + accumulator_type &accumulator, + const selector_type &selector) +{ + /* + * 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 auto next_intersection = + intersection_generator.GetConnectedRoads(current_node_id, current_edge_id); + + // 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 {}; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /* OSRM_EXTRACTOR_GUIDANCE_NODE_BASED_GRAPH_WALKER */ diff --git a/src/extractor/guidance/node_based_graph_walker.cpp b/src/extractor/guidance/node_based_graph_walker.cpp new file mode 100644 index 000000000..9351c6c63 --- /dev/null +++ b/src/extractor/guidance/node_based_graph_walker.cpp @@ -0,0 +1,100 @@ +#include "extractor/guidance/node_based_graph_walker.hpp" +#include "util/coordinate_calculation.hpp" + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +// --------------------------------------------------------------------------------- +NodeBasedGraphWalker::NodeBasedGraphWalker(const util::NodeBasedDynamicGraph &node_based_graph, + const IntersectionGenerator &intersection_generator) + : node_based_graph(node_based_graph), intersection_generator(intersection_generator) +{ +} + +LengthLimitedCoordinateAccumulator::LengthLimitedCoordinateAccumulator( + const extractor::guidance::CoordinateExtractor &coordinate_extractor, + const util::NodeBasedDynamicGraph &node_based_graph, + const double max_length) + : coordinate_extractor(coordinate_extractor), node_based_graph(node_based_graph), + max_length(max_length), accumulated_length(0) +{ +} + +bool LengthLimitedCoordinateAccumulator::terminate() { return accumulated_length >= max_length; } + +// update the accumulator +void LengthLimitedCoordinateAccumulator::update(const NodeID from_node, + const EdgeID via_edge, + const NodeID to_node) + +{ + const util::NodeBasedEdgeData &edge_data = node_based_graph.GetEdgeData(via_edge); + + const auto current_coordinates = coordinate_extractor.GetForwardCoordinatesAlongRoad( + from_node, via_edge); + + const auto length = util::coordinate_calculation::getLength( + coordinates, util::coordinate_calculation::haversineDistance); + + // in case we get too many coordinates, we limit them to our desired length + if (length + accumulated_length > max_length) + coordinate_extractor.TrimCoordinatesToLength(current_coordinates, + max_length - accumulated_length); + + coordinates.insert(coordinates.end(), current_coordinates.begin(), current_coordinates.end()); + + accumulated_length += length; +} + +// --------------------------------------------------------------------------------- +SelectRoadByNameOnlyChoiceAndStraightness::SelectRoadByNameOnlyChoiceAndStraightness( + const NameID desired_name_id, const bool requires_entry) + : desired_name_id(desired_name_id), requires_entry(requires_entry) +{ +} + +boost::optional SelectRoadByNameOnlyChoiceAndStraightness:: +operator()(const NodeID /*nid*/, + const EdgeID /*via_edge_id*/, + const Intersection &intersection, + const util::NodeBasedDynamicGraph &node_based_graph) const +{ + BOOST_ASSERT(!intersection.empty()); + const auto comparator = [this, &node_based_graph](const ConnectedRoad &lhs, + const ConnectedRoad &rhs) { + // the score of an elemnt results in an ranking preferring valid entries, if required over + // invalid + // requested name_ids over non-requested + // narrow deviations over non-narrow + const auto score = [this, &node_based_graph](const ConnectedRoad &road) { + double result_score = 0; + // since angular deviation is limited by 0-180, we add 360 for invalid + if (requires_entry && !road.entry_allowed) + result_score += 360.; + + // 180 for undesired name-ids + if (desired_name_id != node_based_graph.GetEdgeData(road.turn.eid).name_id) + result_score += 180; + + return result_score + angularDeviation(road.turn.angle, STRAIGHT_ANGLE); + }; + + return score(lhs) < score(rhs); + }; + + const auto min_element = + std::min_element(std::next(std::begin(intersection)), std::end(intersection), comparator); + + if (min_element == intersection.end() || (requires_entry && !min_element->entry_allowed)) + return {}; + else + return min_element->turn.eid; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm