diff --git a/include/extractor/guidance/intersection.hpp b/include/extractor/guidance/intersection.hpp new file mode 100644 index 000000000..8b350e24d --- /dev/null +++ b/include/extractor/guidance/intersection.hpp @@ -0,0 +1,66 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_ + +#include +#include + +#include "extractor/guidance/turn_instruction.hpp" +#include "util/typedefs.hpp" // EdgeID + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +// Every Turn Operation describes a way of switching onto a segment, indicated by an EdgeID. The +// associated turn is described by an angle and an instruction that is used to announce it. +// The Turn Operation indicates what is exposed to the outside of the turn analysis. +struct TurnOperation final +{ + EdgeID eid; + double angle; + TurnInstruction instruction; +}; + +// A Connected Road is the internal representation of a potential turn. Internally, we require +// full list of all connected roads to determine the outcome. +// The reasoning behind is that even invalid turns can influence the perceived angles, or even +// instructions themselves. An pososible example can be described like this: +// +// aaa(2)aa +// a - bbbbb +// aaa(1)aa +// +// will not be perceived as a turn from (1) -> b, and as a U-turn from (1) -> (2). +// In addition, they can influence whether a turn is obvious or not. b->(2) would also be no +// turn-operation, +// but rather a name change. +// +// If this were a normal intersection with +// +// cccccccc +// o bbbbb +// aaaaaaaa +// +// We would perceive a->c as a sharp turn, a->b as a slight turn, and b->c as a slight turn. +struct ConnectedRoad final +{ + ConnectedRoad(const TurnOperation turn, const bool entry_allowed = false); + + // a turn may be relevant to good instructions, even if we cannot enter the road + bool entry_allowed; + TurnOperation turn; +}; + +// small helper function to print the content of a connected road +std::string toString(const ConnectedRoad &road); + +typedef std::vector Intersection; + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /*OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_*/ diff --git a/include/extractor/guidance/intersection_generator.hpp b/include/extractor/guidance/intersection_generator.hpp new file mode 100644 index 000000000..be99a71c9 --- /dev/null +++ b/include/extractor/guidance/intersection_generator.hpp @@ -0,0 +1,68 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_GENERATOR_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_GENERATOR_HPP_ + +#include "extractor/compressed_edge_container.hpp" +#include "extractor/guidance/intersection.hpp" +#include "extractor/query_node.hpp" +#include "extractor/restriction_map.hpp" +#include "util/node_based_graph.hpp" +#include "util/typedefs.hpp" + +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +// The Intersection Generator is given a turn location and generates an intersection representation +// from it. For this all turn possibilities are analysed. +// We consider turn restrictions to indicate possible turns. U-turns are generated based on profile +// decisions. + +class IntersectionGenerator +{ + public: + IntersectionGenerator(const util::NodeBasedDynamicGraph &node_based_graph, + const RestrictionMap &restriction_map, + const std::unordered_set &barrier_nodes, + const std::vector &node_info_list, + const CompressedEdgeContainer &compressed_edge_container); + + Intersection operator()(const NodeID nid, 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 CompressedEdgeContainer &compressed_edge_container; + + // Check for restrictions/barriers and generate a list of valid and invalid turns present at + // the + // node reached + // from `from_node` via `via_eid` + // The resulting candidates have to be analysed for their actual instructions later on. + Intersection getConnectedRoads(const NodeID from_node, const EdgeID via_eid) const; + + // Merge segregated roads to omit invalid turns in favor of treating segregated roads as + // one. + // This function combines roads the following way: + // + // * * + // * is converted to * + // v ^ + + // v ^ + + // + // The treatment results in a straight turn angle of 180º rather than a turn angle of approx + // 160 + Intersection mergeSegregatedRoads(Intersection intersection) const; +}; + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /* OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_GENERATOR_HPP_ */ diff --git a/include/extractor/guidance/intersection_handler.hpp b/include/extractor/guidance/intersection_handler.hpp new file mode 100644 index 000000000..25d2f4a53 --- /dev/null +++ b/include/extractor/guidance/intersection_handler.hpp @@ -0,0 +1,71 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HANDLER_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HANDLER_HPP_ + +#include "extractor/guidance/intersection.hpp" +#include "extractor/query_node.hpp" + +#include "util/name_table.hpp" +#include "util/node_based_graph.hpp" + +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +// Intersection handlers deal with all issues related to intersections. +// They assign appropriate turn operations to the TurnOperations. +// This base class provides both the interface and implementations for +// common functions. +class IntersectionHandler +{ + public: + IntersectionHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table); + virtual ~IntersectionHandler(); + + // check whether the handler can actually handle the intersection + virtual bool + canProcess(const NodeID nid, const EdgeID via_eid, const Intersection &intersection) const = 0; + + // process the intersection + virtual Intersection + operator()(const NodeID nid, const EdgeID via_eid, Intersection intersection) const = 0; + + protected: + const util::NodeBasedDynamicGraph &node_based_graph; + const std::vector &node_info_list; + const util::NameTable &name_table; + + // counts the number on allowed entry roads + std::size_t countValid(const Intersection &intersection) const; + + // Decide on a basic turn types + TurnType findBasicTurnType(const EdgeID via_edge, const ConnectedRoad &candidate) const; + + // Get the Instruction for an obvious turn + TurnInstruction getInstructionForObvious(const std::size_t number_of_candidates, + const EdgeID via_edge, + const bool through_street, + const ConnectedRoad &candidate) const; + + // Treating potential forks + void assignFork(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const; + void assignFork(const EdgeID via_edge, + ConnectedRoad &left, + ConnectedRoad ¢er, + ConnectedRoad &right) const; + + bool isThroughStreet(const std::size_t index, const Intersection &intersection) const; +}; + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /*OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HANDLER_HPP_*/ diff --git a/include/extractor/guidance/motorway_handler.hpp b/include/extractor/guidance/motorway_handler.hpp new file mode 100644 index 000000000..e3d9e34b6 --- /dev/null +++ b/include/extractor/guidance/motorway_handler.hpp @@ -0,0 +1,51 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_ + +#include "extractor/guidance/intersection_handler.hpp" +#include "extractor/guidance/intersection.hpp" +#include "extractor/query_node.hpp" + +#include "util/name_table.hpp" +#include "util/node_based_graph.hpp" + +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +// Intersection handlers deal with all issues related to intersections. +// They assign appropriate turn operations to the TurnOperations. +class MotorwayHandler : public IntersectionHandler +{ + public: + MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table); + ~MotorwayHandler() override final; + + // check whether the handler can actually handle the intersection + bool canProcess(const NodeID nid, + const EdgeID via_eid, + const Intersection &intersection) const override final; + + // process the intersection + Intersection operator()(const NodeID nid, + const EdgeID via_eid, + Intersection intersection) const override final; + + private: + Intersection fromMotorway(const EdgeID via_edge, Intersection intersection) const; + Intersection fromRamp(const EdgeID via_edge, Intersection intersection) const; + + Intersection fallback(Intersection intersection) const; +}; + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /*OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_*/ diff --git a/include/extractor/guidance/roundabout_handler.hpp b/include/extractor/guidance/roundabout_handler.hpp new file mode 100644 index 000000000..6a48387d5 --- /dev/null +++ b/include/extractor/guidance/roundabout_handler.hpp @@ -0,0 +1,71 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_ + +#include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/intersection_handler.hpp" +#include "extractor/query_node.hpp" + +#include "util/name_table.hpp" +#include "util/node_based_graph.hpp" +#include "util/typedefs.hpp" + +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +namespace detail +{ +struct RoundaboutFlags +{ + bool on_roundabout; + bool can_enter; + bool can_exit_separately; +}; +} // namespace detail + +// The roundabout handler processes all roundabout related instructions. +// It performs both the distinction between rotaries and roundabouts and +// assigns appropriate entry/exit instructions. +class RoundaboutHandler : public IntersectionHandler +{ + public: + RoundaboutHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table); + + ~RoundaboutHandler() override final; + + // check whether the handler can actually handle the intersection + bool canProcess(const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const override final; + + // process the intersection + Intersection operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersection) const override final; + + private: + detail::RoundaboutFlags getRoundaboutFlags(const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const; + + // decide whether we lookk at a roundabout or a rotary + bool isRotary(const NodeID nid) const; + + // TODO handle bike/walk cases that allow crossing a roundabout! + // Processing of roundabouts + // Produces instructions to enter/exit a roundabout or to stay on it. + // Performs the distinction between roundabout and rotaries. + Intersection handleRoundabouts(const bool is_rotary, + const EdgeID via_edge, + const bool on_roundabout, + const bool can_exit_roundabout, + Intersection intersection) const; +}; + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /*OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_*/ diff --git a/include/extractor/guidance/turn_analysis.hpp b/include/extractor/guidance/turn_analysis.hpp index 169ec234d..f52d1b259 100644 --- a/include/extractor/guidance/turn_analysis.hpp +++ b/include/extractor/guidance/turn_analysis.hpp @@ -2,11 +2,18 @@ #define OSRM_EXTRACTOR_TURN_ANALYSIS #include "extractor/compressed_edge_container.hpp" +#include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/intersection_generator.hpp" #include "extractor/guidance/toolkit.hpp" #include "extractor/guidance/turn_classification.hpp" +#include "extractor/guidance/roundabout_handler.hpp" +#include "extractor/guidance/motorway_handler.hpp" +#include "extractor/guidance/turn_handler.hpp" +#include "extractor/query_node.hpp" #include "extractor/restriction_map.hpp" #include "util/name_table.hpp" +#include "util/node_based_graph.hpp" #include @@ -23,46 +30,6 @@ namespace extractor namespace guidance { -// What is exposed to the outside -struct TurnOperation final -{ - EdgeID eid; - double angle; - TurnInstruction instruction; -}; - -// For the turn analysis, we require a full list of all connected roads to determine the outcome. -// Invalid turns can influence the perceived angles -// -// aaa(2)aa -// a - bbbbb -// aaa(1)aa -// -// will not be perceived as a turn from (1) -> b, and as a U-turn from (1) -> (2). -// In addition, they can influence whether a turn is obvious or not. -struct ConnectedRoad final -{ - ConnectedRoad(const TurnOperation turn, const bool entry_allowed = false); - - TurnOperation turn; - bool entry_allowed; // a turn may be relevant to good instructions, even if we cannot take - // the road - - std::string toString() const - { - std::string result = "[connection] "; - result += std::to_string(turn.eid); - result += " allows entry: "; - result += std::to_string(entry_allowed); - result += " angle: "; - result += std::to_string(turn.angle); - result += " instruction: "; - result += std::to_string(static_cast(turn.instruction.type)) + " " + - std::to_string(static_cast(turn.instruction.direction_modifier)); - return result; - } -}; - class TurnAnalysis { @@ -79,123 +46,14 @@ class TurnAnalysis private: const util::NodeBasedDynamicGraph &node_based_graph; - const std::vector &node_info_list; - const RestrictionMap &restriction_map; - const std::unordered_set &barrier_nodes; - const CompressedEdgeContainer &compressed_edge_container; - const util::NameTable &name_table; - - // Check for restrictions/barriers and generate a list of valid and invalid turns present at - // the - // node reached - // from `from_node` via `via_eid` - // The resulting candidates have to be analysed for their actual instructions later on. - std::vector getConnectedRoads(const NodeID from_node, - const EdgeID via_eid) const; - - // Merge segregated roads to omit invalid turns in favor of treating segregated roads as - // one. - // This function combines roads the following way: - // - // * * - // * is converted to * - // v ^ + - // v ^ + - // - // The treatment results in a straight turn angle of 180º rather than a turn angle of approx - // 160 - std::vector mergeSegregatedRoads(std::vector intersection) const; - - // TODO distinguish roundabouts and rotaries - // TODO handle bike/walk cases that allow crossing a roundabout! - - // Processing of roundabouts - // Produces instructions to enter/exit a roundabout or to stay on it. - // Performs the distinction between roundabout and rotaries. - std::vector handleRoundabouts(const bool is_rotary, - const EdgeID via_edge, - const bool on_roundabout, - const bool can_exit_roundabout, - std::vector intersection) const; - - // Indicates a Junction containing a motoryway - bool isMotorwayJunction(const EdgeID via_edge, - const std::vector &intersection) const; - - // Decide whether a turn is a turn or a ramp access - TurnType findBasicTurnType(const EdgeID via_edge, const ConnectedRoad &candidate) const; - - // Get the Instruction for an obvious turn - // Instruction will be a silent instruction - TurnInstruction getInstructionForObvious(const std::size_t number_of_candidates, - const EdgeID via_edge, - const bool through_street, - const ConnectedRoad &candidate) const; - - // Helper Function that decides between NoTurn or NewName - TurnInstruction - noTurnOrNewName(const NodeID from, const EdgeID via_edge, const ConnectedRoad &candidate) const; - - // Basic Turn Handling - - // Dead end. - std::vector handleOneWayTurn(std::vector intersection) const; - - // Mode Changes, new names... - std::vector handleTwoWayTurn(const EdgeID via_edge, - std::vector intersection) const; - - // Forks, T intersections and similar - std::vector handleThreeWayTurn(const EdgeID via_edge, - std::vector intersection) const; - - // Handling of turns larger then degree three - std::vector handleComplexTurn(const EdgeID via_edge, - std::vector intersection) const; - - // Any Junction containing motorways - std::vector - handleMotorwayJunction(const EdgeID via_edge, std::vector intersection) const; - - std::vector handleFromMotorway(const EdgeID via_edge, - std::vector intersection) const; - - std::vector handleMotorwayRamp(const EdgeID via_edge, - std::vector intersection) const; + const IntersectionGenerator intersection_generator; + const RoundaboutHandler roundabout_handler; + const MotorwayHandler motorway_handler; + const TurnHandler turn_handler; // Utility function, setting basic turn types. Prepares for normal turn handling. - std::vector setTurnTypes(const NodeID from, - const EdgeID via_edge, - std::vector intersection) const; - - // Assignment of specific turn types - void assignFork(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const; - void assignFork(const EdgeID via_edge, - ConnectedRoad &left, - ConnectedRoad ¢er, - ConnectedRoad &right) const; - - void - handleDistinctConflict(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const; - - // Type specific fallbacks - std::vector - fallbackTurnAssignmentMotorway(std::vector intersection) const; - - // Classification - std::size_t findObviousTurn(const EdgeID via_edge, - const std::vector &intersection) const; - std::pair - findFork(const std::vector &intersection) const; - - std::vector assignLeftTurns(const EdgeID via_edge, - std::vector intersection, - const std::size_t starting_at) const; - std::vector assignRightTurns(const EdgeID via_edge, - std::vector intersection, - const std::size_t up_to) const; - - bool isRotary(const NodeID nid) const; + Intersection + setTurnTypes(const NodeID from, const EdgeID via_edge, Intersection intersection) const; }; // class TurnAnalysis } // namespace guidance diff --git a/include/extractor/guidance/turn_handler.hpp b/include/extractor/guidance/turn_handler.hpp new file mode 100644 index 000000000..3420aa788 --- /dev/null +++ b/include/extractor/guidance/turn_handler.hpp @@ -0,0 +1,74 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_HANDLER_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_TURN_HANDLER_HPP_ + +#include "extractor/guidance/intersection_handler.hpp" +#include "extractor/guidance/intersection.hpp" +#include "extractor/query_node.hpp" + +#include "util/name_table.hpp" +#include "util/node_based_graph.hpp" + +#include +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +// Intersection handlers deal with all issues related to intersections. +// They assign appropriate turn operations to the TurnOperations. +class TurnHandler : public IntersectionHandler +{ + public: + TurnHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table); + ~TurnHandler() override final; + + // check whether the handler can actually handle the intersection + bool canProcess(const NodeID nid, + const EdgeID via_eid, + const Intersection &intersection) const override final; + + // process the intersection + Intersection operator()(const NodeID nid, + const EdgeID via_eid, + Intersection intersection) const override final; + + private: + // Dead end. + Intersection handleOneWayTurn(Intersection intersection) const; + + // Mode Changes, new names... + Intersection handleTwoWayTurn(const EdgeID via_edge, Intersection intersection) const; + + // Forks, T intersections and similar + Intersection handleThreeWayTurn(const EdgeID via_edge, Intersection intersection) const; + + // Handling of turns larger then degree three + Intersection handleComplexTurn(const EdgeID via_edge, Intersection intersection) const; + + void + handleDistinctConflict(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const; + + // Classification + std::size_t findObviousTurn(const EdgeID via_edge, const Intersection &intersection) const; + std::pair findFork(const Intersection &intersection) const; + + Intersection assignLeftTurns(const EdgeID via_edge, + Intersection intersection, + const std::size_t starting_at) const; + Intersection assignRightTurns(const EdgeID via_edge, + Intersection intersection, + const std::size_t up_to) const; +}; + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /*OSRM_EXTRACTOR_GUIDANCE_TURN_HANDLER_HPP_*/ diff --git a/src/extractor/guidance/intersection.cpp b/src/extractor/guidance/intersection.cpp new file mode 100644 index 000000000..8902ba4a0 --- /dev/null +++ b/src/extractor/guidance/intersection.cpp @@ -0,0 +1,31 @@ +#include "extractor/guidance/intersection.hpp" + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +ConnectedRoad::ConnectedRoad(const TurnOperation turn, const bool entry_allowed) + : entry_allowed(entry_allowed), turn(turn) +{ +} + +std::string toString(const ConnectedRoad &road) +{ + std::string result = "[connection] "; + result += std::to_string(road.turn.eid); + result += " allows entry: "; + result += std::to_string(road.entry_allowed); + result += " angle: "; + result += std::to_string(road.turn.angle); + result += " instruction: "; + result += std::to_string(static_cast(road.turn.instruction.type)) + " " + + std::to_string(static_cast(road.turn.instruction.direction_modifier)); + return result; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/intersection_generator.cpp b/src/extractor/guidance/intersection_generator.cpp new file mode 100644 index 000000000..5f01cda6b --- /dev/null +++ b/src/extractor/guidance/intersection_generator.cpp @@ -0,0 +1,255 @@ +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/intersection_generator.hpp" +#include "extractor/guidance/toolkit.hpp" + +#include +#include +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +IntersectionGenerator::IntersectionGenerator(const util::NodeBasedDynamicGraph &node_based_graph, + const RestrictionMap &restriction_map, + const std::unordered_set &barrier_nodes, + const std::vector &node_info_list, + const CompressedEdgeContainer &compressed_edge_container) + : node_based_graph(node_based_graph), restriction_map(restriction_map), + barrier_nodes(barrier_nodes), node_info_list(node_info_list), + compressed_edge_container(compressed_edge_container) +{ +} + +Intersection IntersectionGenerator::operator()(const NodeID from_node, const EdgeID via_eid) const +{ + return getConnectedRoads(from_node, via_eid); +} + +// a +// | +// | +// v +// For an intersection from_node --via_edi--> turn_node ----> c +// ^ +// | +// | +// b +// This functions returns _all_ turns as if the graph was undirected. +// That means we not only get (from_node, turn_node, c) in the above example +// but also (from_node, turn_node, a), (from_node, turn_node, b). These turns are +// marked as invalid and only needed for intersection classification. +Intersection IntersectionGenerator::getConnectedRoads(const NodeID from_node, + const EdgeID via_eid) const +{ + Intersection intersection; + const NodeID turn_node = node_based_graph.GetTarget(via_eid); + const NodeID only_restriction_to_node = + restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node); + const bool is_barrier_node = barrier_nodes.find(turn_node) != barrier_nodes.end(); + + bool has_uturn_edge = false; + for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) + { + BOOST_ASSERT(onto_edge != SPECIAL_EDGEID); + const NodeID to_node = node_based_graph.GetTarget(onto_edge); + + bool turn_is_valid = + // reverse edges are never valid turns because the resulting turn would look like this: + // from_node --via_edge--> turn_node <--onto_edge-- to_node + // however we need this for capture intersection shape for incoming one-ways + !node_based_graph.GetEdgeData(onto_edge).reversed && + // we are not turning over a barrier + (!is_barrier_node || from_node == to_node) && + // We are at an only_-restriction but not at the right turn. + (only_restriction_to_node == SPECIAL_NODEID || to_node == only_restriction_to_node) && + // the turn is not restricted + !restriction_map.CheckIfTurnIsRestricted(from_node, turn_node, to_node); + + auto angle = 0.; + if (from_node == to_node) + { + if (turn_is_valid && !is_barrier_node) + { + // we only add u-turns for dead-end streets. + if (node_based_graph.GetOutDegree(turn_node) > 1) + { + auto number_of_emmiting_bidirectional_edges = 0; + for (auto edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) + { + auto target = node_based_graph.GetTarget(edge); + auto reverse_edge = node_based_graph.FindEdge(target, turn_node); + BOOST_ASSERT(reverse_edge != SPECIAL_EDGEID); + if (!node_based_graph.GetEdgeData(reverse_edge).reversed) + { + ++number_of_emmiting_bidirectional_edges; + } + } + // is a dead-end + turn_is_valid = number_of_emmiting_bidirectional_edges <= 1; + } + } + has_uturn_edge = true; + BOOST_ASSERT(angle >= 0. && angle < std::numeric_limits::epsilon()); + } + else + { + // unpack first node of second segment if packed + const auto first_coordinate = getRepresentativeCoordinate( + from_node, turn_node, via_eid, INVERT, compressed_edge_container, node_info_list); + const auto third_coordinate = getRepresentativeCoordinate( + turn_node, to_node, onto_edge, !INVERT, compressed_edge_container, node_info_list); + angle = util::coordinate_calculation::computeAngle( + first_coordinate, node_info_list[turn_node], third_coordinate); + if (angle < std::numeric_limits::epsilon()) + has_uturn_edge = true; + } + + intersection.push_back(ConnectedRoad( + TurnOperation{onto_edge, angle, {TurnType::Invalid, DirectionModifier::UTurn}}, + turn_is_valid)); + } + + // We hit the case of a street leading into nothing-ness. Since the code here assumes that this + // will + // never happen we add an artificial invalid uturn in this case. + if (!has_uturn_edge) + { + intersection.push_back( + {TurnOperation{via_eid, 0., {TurnType::Invalid, DirectionModifier::UTurn}}, false}); + } + + const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) { + return first.turn.angle < second.turn.angle; + }; + std::sort(std::begin(intersection), std::end(intersection), ByAngle); + + BOOST_ASSERT(intersection[0].turn.angle >= 0. && + intersection[0].turn.angle < std::numeric_limits::epsilon()); + + return mergeSegregatedRoads(std::move(intersection)); +} + +/* + * Segregated Roads often merge onto a single intersection. + * While technically representing different roads, they are + * often looked at as a single road. + * Due to the merging, turn Angles seem off, wenn we compute them from the + * initial positions. + * + * bb>b>b(2)>b>b>b + * + * Would be seen as a slight turn going fro a to (2). A Sharp turn going from + * (1) to (2). + * + * In cases like these, we megre this segregated roads into a single road to + * end up with a case like: + * + * aaaaa-bbbbbb + * + * for the turn representation. + * Anything containing the first u-turn in a merge affects all other angles + * and is handled separately from all others. + */ +Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersection) const +{ + const auto getRight = [&](std::size_t index) { + return (index + intersection.size() - 1) % intersection.size(); + }; + + const auto mergable = [&](std::size_t first, std::size_t second) -> bool { + const auto &first_data = node_based_graph.GetEdgeData(intersection[first].turn.eid); + const auto &second_data = node_based_graph.GetEdgeData(intersection[second].turn.eid); + + return first_data.name_id != INVALID_NAME_ID && first_data.name_id == second_data.name_id && + !first_data.roundabout && !second_data.roundabout && + first_data.travel_mode == second_data.travel_mode && + first_data.road_classification == second_data.road_classification && + // compatible threshold + angularDeviation(intersection[first].turn.angle, intersection[second].turn.angle) < + 60 && + first_data.reversed != second_data.reversed; + }; + + const auto merge = [](const ConnectedRoad &first, + const ConnectedRoad &second) -> ConnectedRoad { + if (!first.entry_allowed) + { + ConnectedRoad result = second; + result.turn.angle = (first.turn.angle + second.turn.angle) / 2; + if (first.turn.angle - second.turn.angle > 180) + result.turn.angle += 180; + if (result.turn.angle > 360) + result.turn.angle -= 360; + + return result; + } + else + { + BOOST_ASSERT(!second.entry_allowed); + ConnectedRoad result = first; + result.turn.angle = (first.turn.angle + second.turn.angle) / 2; + + if (first.turn.angle - second.turn.angle > 180) + result.turn.angle += 180; + if (result.turn.angle > 360) + result.turn.angle -= 360; + + return result; + } + }; + if (intersection.size() == 1) + return intersection; + + // check for merges including the basic u-turn + // these result in an adjustment of all other angles + if (mergable(0, intersection.size() - 1)) + { + const double correction_factor = + (360 - intersection[intersection.size() - 1].turn.angle) / 2; + for (std::size_t i = 1; i + 1 < intersection.size(); ++i) + intersection[i].turn.angle += correction_factor; + intersection[0] = merge(intersection.front(), intersection.back()); + intersection[0].turn.angle = 0; + intersection.pop_back(); + } + + else if (mergable(0, 1)) + { + const double correction_factor = (intersection[1].turn.angle) / 2; + for (std::size_t i = 2; i < intersection.size(); ++i) + intersection[i].turn.angle += correction_factor; + intersection[0] = merge(intersection[0], intersection[1]); + intersection[0].turn.angle = 0; + intersection.erase(intersection.begin() + 1); + } + + // a merge including the first u-turn requres an adjustment of the turn angles + // therefore these are handled prior to this step + for (std::size_t index = 2; index < intersection.size(); ++index) + { + if (mergable(index, getRight(index))) + { + intersection[getRight(index)] = + merge(intersection[getRight(index)], intersection[index]); + intersection.erase(intersection.begin() + index); + --index; + } + } + + const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) { + return first.turn.angle < second.turn.angle; + }; + std::sort(std::begin(intersection), std::end(intersection), ByAngle); + return intersection; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/intersection_handler.cpp b/src/extractor/guidance/intersection_handler.cpp new file mode 100644 index 000000000..b77f4edc7 --- /dev/null +++ b/src/extractor/guidance/intersection_handler.cpp @@ -0,0 +1,307 @@ +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/intersection_handler.hpp" +#include "extractor/guidance/toolkit.hpp" + +#include + +using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData; + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +namespace detail +{ +inline bool requiresAnnouncement(const EdgeData &from, const EdgeData &to) +{ + return !from.IsCompatibleTo(to); +} +} + +IntersectionHandler::IntersectionHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table) + : node_based_graph(node_based_graph), node_info_list(node_info_list), name_table(name_table) +{ +} + +IntersectionHandler::~IntersectionHandler() {} + +std::size_t IntersectionHandler::countValid(const Intersection &intersection) const +{ + return std::count_if(intersection.begin(), intersection.end(), + [](const ConnectedRoad &road) { return road.entry_allowed; }); +} + +TurnType IntersectionHandler::findBasicTurnType(const EdgeID via_edge, + const ConnectedRoad &road) const +{ + + const auto &in_data = node_based_graph.GetEdgeData(via_edge); + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + + bool on_ramp = isRampClass(in_data.road_classification.road_class); + + bool onto_ramp = isRampClass(out_data.road_classification.road_class); + + if (!on_ramp && onto_ramp) + return TurnType::Ramp; + + if (in_data.name_id == out_data.name_id && in_data.name_id != INVALID_NAME_ID) + { + return TurnType::Continue; + } + + return TurnType::Turn; +} + +TurnInstruction IntersectionHandler::getInstructionForObvious(const std::size_t num_roads, + const EdgeID via_edge, + const bool through_street, + const ConnectedRoad &road) const +{ + const auto type = findBasicTurnType(via_edge, road); + // handle travel modes: + const auto in_mode = node_based_graph.GetEdgeData(via_edge).travel_mode; + const auto out_mode = node_based_graph.GetEdgeData(road.turn.eid).travel_mode; + if (type == TurnType::Ramp) + { + return {TurnType::Ramp, getTurnDirection(road.turn.angle)}; + } + + if (angularDeviation(road.turn.angle, 0) < 0.01) + { + return {TurnType::Turn, DirectionModifier::UTurn}; + } + if (type == TurnType::Turn) + { + const auto &in_data = node_based_graph.GetEdgeData(via_edge); + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + if (in_data.name_id != out_data.name_id && + requiresNameAnnounced(name_table.GetNameForID(in_data.name_id), + name_table.GetNameForID(out_data.name_id))) + { + // obvious turn onto a through street is a merge + if (through_street) + { + return {TurnType::Merge, road.turn.angle > STRAIGHT_ANGLE + ? DirectionModifier::SlightRight + : DirectionModifier::SlightLeft}; + } + else + { + return {TurnType::NewName, getTurnDirection(road.turn.angle)}; + } + } + else + { + if (in_mode == out_mode) + return {TurnType::Suppressed, getTurnDirection(road.turn.angle)}; + else + return {TurnType::Notification, getTurnDirection(road.turn.angle)}; + } + } + BOOST_ASSERT(type == TurnType::Continue); + if (in_mode != out_mode) + { + return {TurnType::Notification, getTurnDirection(road.turn.angle)}; + } + if (num_roads > 2) + { + return {TurnType::Suppressed, getTurnDirection(road.turn.angle)}; + } + else + { + return {TurnType::NoTurn, getTurnDirection(road.turn.angle)}; + } +} + +void IntersectionHandler::assignFork(const EdgeID via_edge, + ConnectedRoad &left, + ConnectedRoad &right) const +{ + const auto &in_data = node_based_graph.GetEdgeData(via_edge); + const bool low_priority_left = isLowPriorityRoadClass( + node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class); + const bool low_priority_right = isLowPriorityRoadClass( + node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class); + if ((angularDeviation(left.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION && + angularDeviation(right.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)) + { + // left side is actually straight + const auto &out_data = node_based_graph.GetEdgeData(left.turn.eid); + if (detail::requiresAnnouncement(in_data, out_data)) + { + if (low_priority_right && !low_priority_left) + { + left.turn.instruction = getInstructionForObvious(3, via_edge, false, left); + right.turn.instruction = {findBasicTurnType(via_edge, right), + DirectionModifier::SlightRight}; + } + else + { + if (low_priority_left && !low_priority_right) + { + left.turn.instruction = {findBasicTurnType(via_edge, left), + DirectionModifier::SlightLeft}; + right.turn.instruction = {findBasicTurnType(via_edge, right), + DirectionModifier::SlightRight}; + } + else + { + left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; + right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; + } + } + } + else + { + left.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; + right.turn.instruction = {findBasicTurnType(via_edge, right), + DirectionModifier::SlightRight}; + } + } + else if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) < + MAXIMAL_ALLOWED_NO_TURN_DEVIATION && + angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) + { + // right side is actually straight + const auto &out_data = node_based_graph.GetEdgeData(right.turn.eid); + if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) < + MAXIMAL_ALLOWED_NO_TURN_DEVIATION && + angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) + { + if (detail::requiresAnnouncement(in_data, out_data)) + { + if (low_priority_left && !low_priority_right) + { + left.turn.instruction = {findBasicTurnType(via_edge, left), + DirectionModifier::SlightLeft}; + right.turn.instruction = getInstructionForObvious(3, via_edge, false, right); + } + else + { + if (low_priority_right && !low_priority_left) + { + left.turn.instruction = {findBasicTurnType(via_edge, left), + DirectionModifier::SlightLeft}; + right.turn.instruction = {findBasicTurnType(via_edge, right), + DirectionModifier::SlightRight}; + } + else + { + right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; + left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; + } + } + } + else + { + right.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; + left.turn.instruction = {findBasicTurnType(via_edge, left), + DirectionModifier::SlightLeft}; + } + } + } + // left side of fork + if (low_priority_right && !low_priority_left) + left.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft}; + else + { + if (low_priority_left && !low_priority_right) + left.turn.instruction = {TurnType::Turn, DirectionModifier::SlightLeft}; + else + left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; + } + + // right side of fork + if (low_priority_left && !low_priority_right) + right.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft}; + else + { + if (low_priority_right && !low_priority_left) + right.turn.instruction = {TurnType::Turn, DirectionModifier::SlightRight}; + else + right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; + } +} + +void IntersectionHandler::assignFork(const EdgeID via_edge, + ConnectedRoad &left, + ConnectedRoad ¢er, + ConnectedRoad &right) const +{ + // TODO handle low priority road classes in a reasonable way + if (left.entry_allowed && center.entry_allowed && right.entry_allowed) + { + left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; + if (angularDeviation(center.turn.angle, 180) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) + { + const auto &in_data = node_based_graph.GetEdgeData(via_edge); + const auto &out_data = node_based_graph.GetEdgeData(center.turn.eid); + if (detail::requiresAnnouncement(in_data, out_data)) + { + center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight}; + } + else + { + center.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; + } + } + else + { + center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight}; + } + right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; + } + else if (left.entry_allowed) + { + if (right.entry_allowed) + assignFork(via_edge, left, right); + else if (center.entry_allowed) + assignFork(via_edge, left, center); + else + left.turn.instruction = {findBasicTurnType(via_edge, left), + getTurnDirection(left.turn.angle)}; + } + else if (right.entry_allowed) + { + if (center.entry_allowed) + assignFork(via_edge, center, right); + else + right.turn.instruction = {findBasicTurnType(via_edge, right), + getTurnDirection(right.turn.angle)}; + } + else + { + if (center.entry_allowed) + center.turn.instruction = {findBasicTurnType(via_edge, center), + getTurnDirection(center.turn.angle)}; + } +} + +bool IntersectionHandler::isThroughStreet(const std::size_t index, + const Intersection &intersection) const +{ + if (node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id == INVALID_NAME_ID) + return false; + for (const auto &road : intersection) + { + // a through street cannot start at our own position + if (road.turn.angle < std::numeric_limits::epsilon()) + continue; + if (angularDeviation(road.turn.angle, intersection[index].turn.angle) > + (STRAIGHT_ANGLE - NARROW_TURN_ANGLE) && + node_based_graph.GetEdgeData(road.turn.eid).name_id == + node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id) + return true; + } + return false; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/motorway_handler.cpp b/src/extractor/guidance/motorway_handler.cpp new file mode 100644 index 000000000..c855b21b4 --- /dev/null +++ b/src/extractor/guidance/motorway_handler.cpp @@ -0,0 +1,524 @@ +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/motorway_handler.hpp" +#include "extractor/guidance/toolkit.hpp" + +#include "util/simple_logger.hpp" + +#include +#include + +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace detail +{ +inline bool isMotorwayClass(const FunctionalRoadClass road_class) +{ + return road_class == FunctionalRoadClass::MOTORWAY || road_class == FunctionalRoadClass::TRUNK; +} + +inline bool isMotorwayClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph) +{ + return isMotorwayClass(node_based_graph.GetEdgeData(eid).road_classification.road_class); +} +inline FunctionalRoadClass roadClass(const ConnectedRoad &road, + const util::NodeBasedDynamicGraph &graph) +{ + return graph.GetEdgeData(road.turn.eid).road_classification.road_class; +} + +inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph) +{ + return isRampClass(node_based_graph.GetEdgeData(eid).road_classification.road_class); +} +} + +MotorwayHandler::MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table) + : IntersectionHandler(node_based_graph, node_info_list, name_table) +{ +} + +MotorwayHandler::~MotorwayHandler() {} + +bool MotorwayHandler::canProcess(const NodeID, + const EdgeID via_eid, + const Intersection &intersection) const +{ + bool has_motorway = false; + bool has_normal_roads = false; + + for (const auto &road : intersection) + { + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + // not merging or forking? + if (road.entry_allowed && angularDeviation(road.turn.angle, STRAIGHT_ANGLE) > 60) + return false; + else if (out_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY || + out_data.road_classification.road_class == FunctionalRoadClass::TRUNK) + { + if (road.entry_allowed) + has_motorway = true; + } + else if (!isRampClass(out_data.road_classification.road_class)) + has_normal_roads = true; + } + + if (has_normal_roads) + return false; + + const auto &in_data = node_based_graph.GetEdgeData(via_eid); + return has_motorway || + in_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY || + in_data.road_classification.road_class == FunctionalRoadClass::TRUNK; +} + +Intersection MotorwayHandler:: +operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const +{ + const auto &in_data = node_based_graph.GetEdgeData(via_eid); + + // coming from motorway + if (detail::isMotorwayClass(in_data.road_classification.road_class)) + { + return fromMotorway(via_eid, std::move(intersection)); + } + else // coming from a ramp + { + return fromRamp(via_eid, std::move(intersection)); + // ramp merging straight onto motorway + } +} + +Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection intersection) const +{ + const auto &in_data = node_based_graph.GetEdgeData(via_eid); + BOOST_ASSERT(detail::isMotorwayClass(in_data.road_classification.road_class)); + + const auto countExitingMotorways = [this](const Intersection &intersection) { + unsigned count = 0; + for (const auto &road : intersection) + { + if (road.entry_allowed && detail::isMotorwayClass(road.turn.eid, node_based_graph)) + ++count; + } + return count; + }; + + // find the angle that continues on our current highway + const auto getContinueAngle = [this, in_data](const Intersection &intersection) { + for (const auto &road : intersection) + { + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + if (road.turn.angle != 0 && in_data.name_id == out_data.name_id && + in_data.name_id != INVALID_NAME_ID && + detail::isMotorwayClass(out_data.road_classification.road_class)) + return road.turn.angle; + } + return intersection[0].turn.angle; + }; + + const auto getMostLikelyContinue = [this, in_data](const Intersection &intersection) { + double angle = intersection[0].turn.angle; + double best = 180; + for (const auto &road : intersection) + { + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + if (detail::isMotorwayClass(out_data.road_classification.road_class) && + angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < best) + { + best = angularDeviation(road.turn.angle, STRAIGHT_ANGLE); + angle = road.turn.angle; + } + } + return angle; + }; + + const auto findBestContinue = [&]() { + const double continue_angle = getContinueAngle(intersection); + if (continue_angle != intersection[0].turn.angle) + return continue_angle; + else + return getMostLikelyContinue(intersection); + }; + + // find continue angle + const double continue_angle = findBestContinue(); + // highway does not continue and has no obvious choice + if (continue_angle == intersection[0].turn.angle) + { + if (intersection.size() == 2) + { + // do not announce ramps at the end of a highway + intersection[1].turn.instruction = {TurnType::NoTurn, + getTurnDirection(intersection[1].turn.angle)}; + } + else if (intersection.size() == 3) + { + // splitting ramp at the end of a highway + if (intersection[1].entry_allowed && intersection[2].entry_allowed) + { + assignFork(via_eid, intersection[2], intersection[1]); + } + else + { + // ending in a passing ramp + if (intersection[1].entry_allowed) + intersection[1].turn.instruction = { + TurnType::NoTurn, getTurnDirection(intersection[1].turn.angle)}; + else + intersection[2].turn.instruction = { + TurnType::NoTurn, getTurnDirection(intersection[2].turn.angle)}; + } + } + else if (intersection.size() == 4 && + detail::roadClass(intersection[1], node_based_graph) == + detail::roadClass(intersection[2], node_based_graph) && + detail::roadClass(intersection[2], node_based_graph) == + detail::roadClass(intersection[3], node_based_graph)) + { + // tripple fork at the end + assignFork(via_eid, intersection[3], intersection[2], intersection[1]); + } + + else if (countValid(intersection) > 0) // check whether turns exist at all + { + // FALLBACK, this should hopefully never be reached + util::SimpleLogger().Write(logDEBUG) + << "Fallback reached from motorway, no continue angle, " << intersection.size() + << " roads, " << countValid(intersection) << " valid ones."; + return fallback(std::move(intersection)); + } + } + else + { + const unsigned exiting_motorways = countExitingMotorways(intersection); + + if (exiting_motorways == 0) + { + // Ending in Ramp + for (auto &road : intersection) + { + if (road.entry_allowed) + { + BOOST_ASSERT(detail::isRampClass(road.turn.eid, node_based_graph)); + road.turn.instruction = + TurnInstruction::SUPPRESSED(getTurnDirection(road.turn.angle)); + } + } + } + else if (exiting_motorways == 1) + { + // normal motorway passing some ramps or mering onto another motorway + if (intersection.size() == 2) + { + BOOST_ASSERT(!detail::isRampClass(intersection[1].turn.eid, node_based_graph)); + + intersection[1].turn.instruction = + getInstructionForObvious(intersection.size(), via_eid, + isThroughStreet(1,intersection), intersection[1]); + } + else + { + // Normal Highway exit or merge + for (auto &road : intersection) + { + // ignore invalid uturns/other + if (!road.entry_allowed) + continue; + + if (road.turn.angle == continue_angle) + { + road.turn.instruction = getInstructionForObvious( + intersection.size(), via_eid, isThroughStreet(1,intersection), road); + } + else if (road.turn.angle < continue_angle) + { + road.turn.instruction = { + detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp + : TurnType::Turn, + (road.turn.angle < 145) ? DirectionModifier::Right + : DirectionModifier::SlightRight}; + } + else if (road.turn.angle > continue_angle) + { + road.turn.instruction = { + detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp + : TurnType::Turn, + (road.turn.angle > 215) ? DirectionModifier::Left + : DirectionModifier::SlightLeft}; + } + } + } + } + // handle motorway forks + else if (exiting_motorways > 1) + { + if (exiting_motorways == 2 && intersection.size() == 2) + { + intersection[1].turn.instruction = + getInstructionForObvious(intersection.size(), via_eid, + isThroughStreet(1,intersection), intersection[1]); + util::SimpleLogger().Write(logDEBUG) << "Disabled U-Turn on a freeway"; + intersection[0].entry_allowed = false; // UTURN on the freeway + } + else if (exiting_motorways == 2) + { + // standard fork + std::size_t first_valid = std::numeric_limits::max(), + second_valid = std::numeric_limits::max(); + for (std::size_t i = 0; i < intersection.size(); ++i) + { + if (intersection[i].entry_allowed && + detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph)) + { + if (first_valid < intersection.size()) + { + second_valid = i; + break; + } + else + { + first_valid = i; + } + } + } + assignFork(via_eid, intersection[second_valid], intersection[first_valid]); + } + else if (exiting_motorways == 3) + { + // triple fork + std::size_t first_valid = std::numeric_limits::max(), + second_valid = std::numeric_limits::max(), + third_valid = std::numeric_limits::max(); + for (std::size_t i = 0; i < intersection.size(); ++i) + { + if (intersection[i].entry_allowed && + detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph)) + { + if (second_valid < intersection.size()) + { + third_valid = i; + break; + } + else if (first_valid < intersection.size()) + { + second_valid = i; + } + else + { + first_valid = i; + } + } + } + assignFork(via_eid, intersection[third_valid], intersection[second_valid], + intersection[first_valid]); + } + else + { + util::SimpleLogger().Write(logDEBUG) << "Found motorway junction with more than " + "2 exiting motorways or additional ramps"; + return fallback(std::move(intersection)); + } + } // done for more than one highway exit + } + return intersection; +} + +Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection intersection) const +{ + auto num_valid_turns = countValid(intersection); + // ramp straight into a motorway/ramp + if (intersection.size() == 2 && num_valid_turns == 1) + { + BOOST_ASSERT(!intersection[0].entry_allowed); + BOOST_ASSERT(detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph)); + + intersection[1].turn.instruction = getInstructionForObvious( + intersection.size(), via_eid, isThroughStreet(1,intersection), intersection[1]); + } + else if (intersection.size() == 3) + { + // merging onto a passing highway / or two ramps merging onto the same highway + if (num_valid_turns == 1) + { + BOOST_ASSERT(!intersection[0].entry_allowed); + // check order of highways + // 4 + // 5 3 + // + // 6 2 + // + // 7 1 + // 0 + if (intersection[1].entry_allowed) + { + if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) && + node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id != + INVALID_NAME_ID && + node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id == + node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id) + { + // circular order indicates a merge to the left (0-3 onto 4 + if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < + NARROW_TURN_ANGLE) + intersection[1].turn.instruction = {TurnType::Merge, + DirectionModifier::SlightLeft}; + else // fallback + intersection[1].turn.instruction = { + TurnType::Merge, getTurnDirection(intersection[1].turn.angle)}; + } + else // passing by the end of a motorway + { + intersection[1].turn.instruction = + getInstructionForObvious(intersection.size(), via_eid, + isThroughStreet(1,intersection), intersection[1]); + } + } + else + { + BOOST_ASSERT(intersection[2].entry_allowed); + if (detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph) && + node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id != + INVALID_NAME_ID && + node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id == + node_based_graph.GetEdgeData(intersection[0].turn.eid).name_id) + { + // circular order (5-0) onto 4 + if (angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) < + NARROW_TURN_ANGLE) + intersection[2].turn.instruction = {TurnType::Merge, + DirectionModifier::SlightRight}; + else // fallback + intersection[2].turn.instruction = { + TurnType::Merge, getTurnDirection(intersection[2].turn.angle)}; + } + else // passing the end of a highway + { + intersection[2].turn.instruction = + getInstructionForObvious(intersection.size(), via_eid, + isThroughStreet(2,intersection), intersection[2]); + } + } + } + else + { + BOOST_ASSERT(num_valid_turns == 2); + // UTurn on ramps is not possible + BOOST_ASSERT(!intersection[0].entry_allowed); + BOOST_ASSERT(intersection[1].entry_allowed); + BOOST_ASSERT(intersection[2].entry_allowed); + // two motorways starting at end of ramp (fork) + // M M + // \ / + // | + // R + if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) && + detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph)) + { + assignFork(via_eid, intersection[2], intersection[1]); + } + else + { + // continued ramp passing motorway entry + // M R + // M R + // | / + // R + if (detail::isMotorwayClass(node_based_graph.GetEdgeData(intersection[1].turn.eid) + .road_classification.road_class)) + { + intersection[1].turn.instruction = {TurnType::Turn, + DirectionModifier::SlightRight}; + intersection[2].turn.instruction = {TurnType::Continue, + DirectionModifier::SlightLeft}; + } + else + { + assignFork(via_eid, intersection[2], intersection[1]); + } + } + } + } + // On - Off Ramp on passing Motorway, Ramp onto Fork(?) + else if (intersection.size() == 4) + { + bool passed_highway_entry = false; + for (auto &road : intersection) + { + const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid); + if (!road.entry_allowed && + detail::isMotorwayClass(edge_data.road_classification.road_class)) + { + passed_highway_entry = true; + } + else if (detail::isMotorwayClass(edge_data.road_classification.road_class)) + { + road.turn.instruction = {TurnType::Merge, passed_highway_entry + ? DirectionModifier::SlightRight + : DirectionModifier::SlightLeft}; + } + else + { + BOOST_ASSERT(isRampClass(edge_data.road_classification.road_class)); + road.turn.instruction = {TurnType::Ramp, getTurnDirection(road.turn.angle)}; + } + } + } + else + { // FALLBACK, hopefully this should never been reached + util::SimpleLogger().Write(logDEBUG) << "Reached fallback on motorway ramp with " + << intersection.size() << " roads and " + << countValid(intersection) << " valid turns."; + return fallback(std::move(intersection)); + } + return intersection; +} + +Intersection MotorwayHandler::fallback(Intersection intersection) const +{ + for (auto &road : intersection) + { + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + + util::SimpleLogger().Write(logDEBUG) + << "road: " << toString(road) << " Name: " << out_data.name_id + << " Road Class: " << (int)out_data.road_classification.road_class; + + if (!road.entry_allowed) + continue; + + const auto type = detail::isMotorwayClass(out_data.road_classification.road_class) + ? TurnType::Merge + : TurnType::Turn; + + if (type == TurnType::Turn) + { + if (angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE) + road.turn.instruction = {type, DirectionModifier::Straight}; + else + { + road.turn.instruction = {type, road.turn.angle > STRAIGHT_ANGLE + ? DirectionModifier::SlightLeft + : DirectionModifier::SlightRight}; + } + } + else + { + road.turn.instruction = {type, road.turn.angle < STRAIGHT_ANGLE + ? DirectionModifier::SlightLeft + : DirectionModifier::SlightRight}; + } + } + return intersection; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/roundabout_handler.cpp b/src/extractor/guidance/roundabout_handler.cpp new file mode 100644 index 000000000..6bd72dca0 --- /dev/null +++ b/src/extractor/guidance/roundabout_handler.cpp @@ -0,0 +1,257 @@ +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/roundabout_handler.hpp" +#include "extractor/guidance/toolkit.hpp" + +#include "util/simple_logger.hpp" + +#include +#include +#include + +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +RoundaboutHandler::RoundaboutHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table) + : IntersectionHandler(node_based_graph, node_info_list, name_table) +{ +} + +RoundaboutHandler::~RoundaboutHandler() {} + +bool RoundaboutHandler::canProcess(const NodeID from_nid, + const EdgeID via_eid, + const Intersection &intersection) const +{ + const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection); + return flags.on_roundabout || flags.can_enter; +} + +Intersection RoundaboutHandler:: +operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersection) const +{ + const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection); + const bool is_rotary = isRotary(node_based_graph.GetTarget(via_eid)); + // find the radius of the roundabout + return handleRoundabouts(is_rotary, via_eid, flags.on_roundabout, flags.can_exit_separately, + std::move(intersection)); +} + +detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags( + const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const +{ + const auto &in_edge_data = node_based_graph.GetEdgeData(via_eid); + bool on_roundabout = in_edge_data.roundabout; + bool can_enter_roundabout = false; + bool can_exit_roundabout_separately = false; + for (const auto &road : intersection) + { + const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid); + // only check actual outgoing edges + if (edge_data.reversed) + continue; + + if (edge_data.roundabout) + { + can_enter_roundabout = true; + } + // Exiting roundabouts at an entry point is technically a data-modelling issue. + // This workaround handles cases in which an exit follows the entry. + // To correctly represent perceived exits, we only count exits leading to a + // separate vertex than the one we are coming from that are in the direction of + // the roundabout. + // The sorting of the angles represents a problem for left-sided driving, though. + // FIXME in case of left-sided driving, we have to check whether we can enter the + // roundabout later in the cycle, rather than prior. + // FIXME requires consideration of crossing the roundabout + else if (node_based_graph.GetTarget(road.turn.eid) != from_nid && !can_enter_roundabout) + { + can_exit_roundabout_separately = true; + } + } + return {on_roundabout, can_enter_roundabout, can_exit_roundabout_separately}; +} + +bool RoundaboutHandler::isRotary(const NodeID nid) const +{ + // translate a node ID into its respective coordinate stored in the node_info_list + const auto getCoordinate = [this](const NodeID node) { + return util::Coordinate(node_info_list[node].lon, node_info_list[node].lat); + }; + + unsigned roundabout_name_id = 0; + std::unordered_set connected_names; + + const auto getNextOnRoundabout = [this, &roundabout_name_id, + &connected_names](const NodeID node) { + EdgeID continue_edge = SPECIAL_EDGEID; + for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node)) + { + const auto &edge_data = node_based_graph.GetEdgeData(edge); + if (!edge_data.reversed && edge_data.roundabout) + { + if (SPECIAL_EDGEID != continue_edge) + { + // fork in roundabout + return SPECIAL_EDGEID; + } + // roundabout does not keep its name + if (roundabout_name_id != 0 && roundabout_name_id != edge_data.name_id && + requiresNameAnnounced(name_table.GetNameForID(roundabout_name_id), + name_table.GetNameForID(edge_data.name_id))) + { + return SPECIAL_EDGEID; + } + + roundabout_name_id = edge_data.name_id; + + continue_edge = edge; + } + else if (!edge_data.roundabout) + { + // remember all connected road names + connected_names.insert(edge_data.name_id); + } + } + return continue_edge; + }; + // the roundabout radius has to be the same for all locations we look at it from + // to guarantee this, we search the full roundabout for its vertices + // and select the three smalles ids + std::set roundabout_nodes; // needs to be sorted + + // this value is a hard abort to deal with potential self-loops + NodeID last_node = nid; + while (0 == roundabout_nodes.count(last_node)) + { + roundabout_nodes.insert(last_node); + const auto eid = getNextOnRoundabout(last_node); + + if (eid == SPECIAL_EDGEID) + { + util::SimpleLogger().Write(logDEBUG) << "Non-Loop Roundabout found."; + return false; + } + + last_node = node_based_graph.GetTarget(eid); + } + + // do we have a dedicated name for the rotary, if not its a roundabout + // This function can theoretically fail if the roundabout name is partly + // used with a reference and without. This will be fixed automatically + // when we handle references separately or if the useage is more consistent + if (roundabout_name_id == 0 || connected_names.count(roundabout_name_id)) + { + return false; + } + + if (roundabout_nodes.size() <= 1) + { + return false; + } + // calculate the radius of the roundabout/rotary. For two coordinates, we assume a minimal + // circle + // with both vertices right at the other side (so half their distance in meters). + // Otherwise, we construct a circle through the first tree vertices. + const auto getRadius = [&roundabout_nodes, &getCoordinate]() { + auto node_itr = roundabout_nodes.begin(); + if (roundabout_nodes.size() == 2) + { + const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++); + return 0.5 * util::coordinate_calculation::haversineDistance(first, second); + } + else + { + const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++), + third = getCoordinate(*node_itr++); + return util::coordinate_calculation::circleRadius(first, second, third); + } + }; + const double radius = getRadius(); + + // check whether the circle computation has gone wrong + // The radius computation can result in infinity, if the three coordinates are non-distinct. + // To stay on the safe side, we say its not a rotary + if (std::isinf(radius)) + return false; + + return radius > MAX_ROUNDABOUT_RADIUS; +} + +Intersection RoundaboutHandler::handleRoundabouts(const bool is_rotary, + const EdgeID via_eid, + const bool on_roundabout, + const bool can_exit_roundabout_separately, + Intersection intersection) const +{ + // TODO requires differentiation between roundabouts and rotaries + // detect via radius (get via circle through three vertices) + NodeID node_v = node_based_graph.GetTarget(via_eid); + if (on_roundabout) + { + // Shoule hopefully have only a single exit and continue + // at least for cars. How about bikes? + for (auto &road : intersection) + { + auto &turn = road.turn; + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + if (out_data.roundabout) + { + // TODO can forks happen in roundabouts? E.g. required lane changes + if (1 == node_based_graph.GetDirectedOutDegree(node_v)) + { + // No turn possible. + turn.instruction = TurnInstruction::NO_TURN(); + } + else + { + turn.instruction = + TurnInstruction::REMAIN_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); + } + } + else + { + turn.instruction = + TurnInstruction::EXIT_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); + } + } + return intersection; + } + else + for (auto &road : intersection) + { + if (!road.entry_allowed) + continue; + auto &turn = road.turn; + const auto &out_data = node_based_graph.GetEdgeData(turn.eid); + if (out_data.roundabout) + { + turn.instruction = + TurnInstruction::ENTER_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); + if (can_exit_roundabout_separately) + { + if (turn.instruction.type == TurnType::EnterRotary) + turn.instruction.type = TurnType::EnterRotaryAtExit; + if (turn.instruction.type == TurnType::EnterRoundabout) + turn.instruction.type = TurnType::EnterRoundaboutAtExit; + } + } + else + { + turn.instruction = TurnInstruction::ENTER_AND_EXIT_ROUNDABOUT( + is_rotary, getTurnDirection(turn.angle)); + } + } + return intersection; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/turn_analysis.cpp b/src/extractor/guidance/turn_analysis.cpp index 1fdff1c25..9413f87f7 100644 --- a/src/extractor/guidance/turn_analysis.cpp +++ b/src/extractor/guidance/turn_analysis.cpp @@ -20,246 +20,51 @@ namespace guidance using EdgeData = util::NodeBasedDynamicGraph::EdgeData; -ConnectedRoad::ConnectedRoad(const TurnOperation turn, const bool entry_allowed) - : turn(turn), entry_allowed(entry_allowed) -{ -} - bool requiresAnnouncement(const EdgeData &from, const EdgeData &to) { return !from.IsCompatibleTo(to); } -struct Localizer -{ - const std::vector *node_info_list = nullptr; - - util::Coordinate operator()(const NodeID nid) - { - if (node_info_list) - { - return {(*node_info_list)[nid].lon, (*node_info_list)[nid].lat}; - } - return {}; - } -}; - -static Localizer localizer; - TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph, const std::vector &node_info_list, const RestrictionMap &restriction_map, const std::unordered_set &barrier_nodes, const CompressedEdgeContainer &compressed_edge_container, const util::NameTable &name_table) - : node_based_graph(node_based_graph), node_info_list(node_info_list), - restriction_map(restriction_map), barrier_nodes(barrier_nodes), - compressed_edge_container(compressed_edge_container), name_table(name_table) + : node_based_graph(node_based_graph), intersection_generator(node_based_graph, + restriction_map, + barrier_nodes, + node_info_list, + compressed_edge_container), + roundabout_handler(node_based_graph, node_info_list, name_table), + motorway_handler(node_based_graph, node_info_list, name_table), + turn_handler(node_based_graph, node_info_list, name_table) { } -// some small tool functions to simplify decisions down the line -namespace detail +std::vector TurnAnalysis::getTurns(const NodeID from_nid, const EdgeID via_eid) const { + auto intersection = intersection_generator(from_nid, via_eid); -inline FunctionalRoadClass roadClass(const ConnectedRoad &road, - const util::NodeBasedDynamicGraph &graph) -{ - return graph.GetEdgeData(road.turn.eid).road_classification.road_class; -} - -inline bool isMotorwayClass(FunctionalRoadClass road_class) -{ - return road_class == FunctionalRoadClass::MOTORWAY || road_class == FunctionalRoadClass::TRUNK; -} - -inline bool isMotorwayClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph) -{ - return isMotorwayClass(node_based_graph.GetEdgeData(eid).road_classification.road_class); -} - -inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph) -{ - return isRampClass(node_based_graph.GetEdgeData(eid).road_classification.road_class); -} - -} // namespace detail - -bool TurnAnalysis::isRotary(const NodeID nid) const -{ - // translate a node ID into its respective coordinate stored in the node_info_list - const auto getCoordinate = [this](const NodeID node) { - return util::Coordinate(node_info_list[node].lon, node_info_list[node].lat); - }; - - unsigned roundabout_name_id = 0; - std::unordered_set connected_names; - - const auto getNextOnRoundabout = [this, &roundabout_name_id, - &connected_names](const NodeID node) { - EdgeID continue_edge = SPECIAL_EDGEID; - for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node)) - { - const auto &edge_data = node_based_graph.GetEdgeData(edge); - if (!edge_data.reversed && edge_data.roundabout) - { - if (SPECIAL_EDGEID != continue_edge) - { - // fork in roundabout - return SPECIAL_EDGEID; - } - // roundabout does not keep its name - if (roundabout_name_id != 0 && roundabout_name_id != edge_data.name_id && - requiresNameAnnounced(name_table.GetNameForID(roundabout_name_id), - name_table.GetNameForID(edge_data.name_id))) - { - return SPECIAL_EDGEID; - } - - roundabout_name_id = edge_data.name_id; - - continue_edge = edge; - } - else if (!edge_data.roundabout) - { - // remember all connected road names - connected_names.insert(edge_data.name_id); - } - } - return continue_edge; - }; - - // the roundabout radius has to be the same for all locations we look at it from - // to guarantee this, we search the full roundabout for its vertices - // and select the three smalles ids - std::set roundabout_nodes; // needs to be sorted - - // this value is a hard abort to deal with potential self-loops - NodeID last_node = nid; - while (0 == roundabout_nodes.count(last_node)) + // Roundabouts are a main priority. If there is a roundabout instruction present, we process the + // turn as a roundabout + if (roundabout_handler.canProcess(from_nid, via_eid, intersection)) { - roundabout_nodes.insert(last_node); - const auto eid = getNextOnRoundabout(last_node); - - if (eid == SPECIAL_EDGEID) - { - util::SimpleLogger().Write(logDEBUG) << "Non-Loop Roundabout found."; - return false; - } - - last_node = node_based_graph.GetTarget(eid); - } - - // do we have a dedicated name for the rotary, if not its a roundabout - // This function can theoretically fail if the roundabout name is partly - // used with a reference and without. This will be fixed automatically - // when we handle references separately or if the useage is more consistent - if (roundabout_name_id == 0 || connected_names.count(roundabout_name_id)) - { - return false; - } - - if (roundabout_nodes.size() <= 1) - { - return false; - } - - // calculate the radius of the roundabout/rotary. For two coordinates, we assume a minimal - // circle - // with both vertices right at the other side (so half their distance in meters). - // Otherwise, we construct a circle through the first tree vertices. - const auto getRadius = [&roundabout_nodes,&getCoordinate](){ - auto node_itr = roundabout_nodes.begin(); - if( roundabout_nodes.size() == 2 ) - { - const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++); - return 0.5 * util::coordinate_calculation::haversineDistance( first, second ); - } - else - { - const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++), third = getCoordinate(*node_itr++); - return util::coordinate_calculation::circleRadius(first,second,third); - } - }; - const double radius = getRadius(); - - // check whether the circle computation has gone wrong - // The radius computation can result in infinity, if the three coordinates are non-distinct. - // To stay on the safe side, we say its not a rotary - if (std::isinf(radius)) - return false; - - return radius > MAX_ROUNDABOUT_RADIUS; -} - -std::vector TurnAnalysis::getTurns(const NodeID from, const EdgeID via_edge) const -{ - localizer.node_info_list = &node_info_list; - auto intersection = getConnectedRoads(from, via_edge); - - const auto &in_edge_data = node_based_graph.GetEdgeData(via_edge); - - // main priority: roundabouts - bool on_roundabout = in_edge_data.roundabout; - bool can_enter_roundabout = false; - bool can_exit_roundabout_separately = false; - for (const auto &road : intersection) - { - const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid); - // only check actual outgoing edges - if (edge_data.reversed) - continue; - - if (edge_data.roundabout) - { - can_enter_roundabout = true; - } - // Exiting roundabouts at an entry point is technically a data-modelling issue. - // This workaround handles cases in which an exit follows the entry. - // To correctly represent perceived exits, we only count exits leading to a - // separate vertex than the one we are coming from that are in the direction of - // the roundabout. - // The sorting of the angles represents a problem for left-sided driving, though. - // FIXME in case of left-sided driving, we have to check whether we can enter the - // roundabout later in the cycle, rather than prior. - else if (node_based_graph.GetTarget(road.turn.eid) != from && !can_enter_roundabout) - { - can_exit_roundabout_separately = true; - } - } - if (on_roundabout || can_enter_roundabout) - { - const bool is_rotary = isRotary(node_based_graph.GetTarget(via_edge)); - // find the radius of the roundabout - intersection = handleRoundabouts(is_rotary, via_edge, on_roundabout, - can_exit_roundabout_separately, std::move(intersection)); + intersection = roundabout_handler(from_nid, via_eid, std::move(intersection)); } else { // set initial defaults for normal turns and modifier based on angle - intersection = setTurnTypes(from, via_edge, std::move(intersection)); - if (isMotorwayJunction(via_edge, intersection)) + intersection = setTurnTypes(from_nid, via_eid, std::move(intersection)); + if (motorway_handler.canProcess(from_nid, via_eid, intersection)) { - intersection = handleMotorwayJunction(via_edge, std::move(intersection)); - } - - else if (intersection.size() == 1) - { - intersection = handleOneWayTurn(std::move(intersection)); - } - else if (intersection.size() == 2) - { - intersection = handleTwoWayTurn(via_edge, std::move(intersection)); - } - else if (intersection.size() == 3) - { - intersection = handleThreeWayTurn(via_edge, std::move(intersection)); + intersection = motorway_handler(from_nid, via_eid, std::move(intersection)); } else { - intersection = handleComplexTurn(via_edge, std::move(intersection)); + BOOST_ASSERT(turn_handler.canProcess(from_nid, via_eid, intersection)); + intersection = turn_handler(from_nid, via_eid, std::move(intersection)); } - // complex intersection, potentially requires conflict resolution } std::vector turns; @@ -270,1213 +75,10 @@ std::vector TurnAnalysis::getTurns(const NodeID from, const EdgeI return turns; } -inline std::size_t countValid(const std::vector &intersection) -{ - return std::count_if(intersection.begin(), intersection.end(), - [](const ConnectedRoad &road) { return road.entry_allowed; }); -} - -std::vector -TurnAnalysis::handleRoundabouts(const bool is_rotary, - const EdgeID via_edge, - const bool on_roundabout, - const bool can_exit_roundabout_separately, - std::vector intersection) const -{ - // TODO requires differentiation between roundabouts and rotaries - // detect via radius (get via circle through three vertices) - NodeID node_v = node_based_graph.GetTarget(via_edge); - if (on_roundabout) - { - // Shoule hopefully have only a single exit and continue - // at least for cars. How about bikes? - for (auto &road : intersection) - { - auto &turn = road.turn; - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - if (out_data.roundabout) - { - // TODO can forks happen in roundabouts? E.g. required lane changes - if (1 == node_based_graph.GetDirectedOutDegree(node_v)) - { - // No turn possible. - turn.instruction = TurnInstruction::NO_TURN(); - } - else - { - turn.instruction = - TurnInstruction::REMAIN_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); - } - } - else - { - turn.instruction = - TurnInstruction::EXIT_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); - } - } - return intersection; - } - else - { - for (auto &road : intersection) - { - if (!road.entry_allowed) - continue; - auto &turn = road.turn; - const auto &out_data = node_based_graph.GetEdgeData(turn.eid); - if (out_data.roundabout) - { - turn.instruction = - TurnInstruction::ENTER_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); - if (can_exit_roundabout_separately) - { - if (turn.instruction.type == TurnType::EnterRotary) - turn.instruction.type = TurnType::EnterRotaryAtExit; - if (turn.instruction.type == TurnType::EnterRoundabout) - turn.instruction.type = TurnType::EnterRoundaboutAtExit; - } - } - else - { - turn.instruction = TurnInstruction::ENTER_AND_EXIT_ROUNDABOUT( - is_rotary, getTurnDirection(turn.angle)); - } - } - return intersection; - } -} - -std::vector -TurnAnalysis::fallbackTurnAssignmentMotorway(std::vector intersection) const -{ - for (auto &road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - - util::SimpleLogger().Write(logWARNING) - << "road: " << road.toString() << " Name: " << out_data.name_id - << " Road Class: " << (int)out_data.road_classification.road_class - << " At: " << localizer(node_based_graph.GetTarget(road.turn.eid)); - - if (!road.entry_allowed) - continue; - - const auto type = detail::isMotorwayClass(out_data.road_classification.road_class) - ? TurnType::Merge - : TurnType::Turn; - - if (type == TurnType::Turn) - { - if (angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE) - road.turn.instruction = {type, DirectionModifier::Straight}; - else - { - road.turn.instruction = {type, road.turn.angle > STRAIGHT_ANGLE - ? DirectionModifier::SlightLeft - : DirectionModifier::SlightRight}; - } - } - else - { - road.turn.instruction = {type, road.turn.angle < STRAIGHT_ANGLE - ? DirectionModifier::SlightLeft - : DirectionModifier::SlightRight}; - } - } - return intersection; -} - -std::vector -TurnAnalysis::handleFromMotorway(const EdgeID via_edge, - std::vector intersection) const -{ - const auto &in_data = node_based_graph.GetEdgeData(via_edge); - BOOST_ASSERT(detail::isMotorwayClass(in_data.road_classification.road_class)); - - const auto countExitingMotorways = [this](const std::vector &intersection) { - unsigned count = 0; - for (const auto &road : intersection) - { - if (road.entry_allowed && detail::isMotorwayClass(road.turn.eid, node_based_graph)) - ++count; - } - return count; - }; - - // find the angle that continues on our current highway - const auto getContinueAngle = [this, in_data](const std::vector &intersection) { - for (const auto &road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - if (road.turn.angle != 0 && in_data.name_id == out_data.name_id && - in_data.name_id != INVALID_NAME_ID && - detail::isMotorwayClass(out_data.road_classification.road_class)) - return road.turn.angle; - } - return intersection[0].turn.angle; - }; - - const auto getMostLikelyContinue = [this, - in_data](const std::vector &intersection) { - double angle = intersection[0].turn.angle; - double best = 180; - for (const auto &road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - if (detail::isMotorwayClass(out_data.road_classification.road_class) && - angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < best) - { - best = angularDeviation(road.turn.angle, STRAIGHT_ANGLE); - angle = road.turn.angle; - } - } - return angle; - }; - - const auto findBestContinue = [&]() { - const double continue_angle = getContinueAngle(intersection); - if (continue_angle != intersection[0].turn.angle) - return continue_angle; - else - return getMostLikelyContinue(intersection); - }; - // check whether the obvious choice is actually a through street - const auto isThroughStreet = [&intersection, this](const ConnectedRoad &connection) { - if (node_based_graph.GetEdgeData(connection.turn.eid).name_id == INVALID_NAME_ID) - return false; - for (const auto &road : intersection) - { - // a through street cannot start at our own position - if (road.turn.angle < std::numeric_limits::epsilon()) - continue; - if (angularDeviation(road.turn.angle, connection.turn.angle) > - (STRAIGHT_ANGLE - NARROW_TURN_ANGLE) && - node_based_graph.GetEdgeData(road.turn.eid).name_id == - node_based_graph.GetEdgeData(connection.turn.eid).name_id) - return true; - } - return false; - }; - - // find continue angle - const double continue_angle = findBestContinue(); - // highway does not continue and has no obvious choice - if (continue_angle == intersection[0].turn.angle) - { - if (intersection.size() == 2) - { - // do not announce ramps at the end of a highway - intersection[1].turn.instruction = {TurnType::NoTurn, - getTurnDirection(intersection[1].turn.angle)}; - } - else if (intersection.size() == 3) - { - // splitting ramp at the end of a highway - if (intersection[1].entry_allowed && intersection[2].entry_allowed) - { - assignFork(via_edge, intersection[2], intersection[1]); - } - else - { - // ending in a passing ramp - if (intersection[1].entry_allowed) - intersection[1].turn.instruction = { - TurnType::NoTurn, getTurnDirection(intersection[1].turn.angle)}; - else - intersection[2].turn.instruction = { - TurnType::NoTurn, getTurnDirection(intersection[2].turn.angle)}; - } - } - else if (intersection.size() == 4 && - detail::roadClass(intersection[1], node_based_graph) == - detail::roadClass(intersection[2], node_based_graph) && - detail::roadClass(intersection[2], node_based_graph) == - detail::roadClass(intersection[3], node_based_graph)) - { - // tripple fork at the end - assignFork(via_edge, intersection[3], intersection[2], intersection[1]); - } - else if (countValid(intersection) > 0) // check whether turns exist at all - { - // FALLBACK, this should hopefully never be reached - auto coord = localizer(node_based_graph.GetTarget(via_edge)); - util::SimpleLogger().Write(logWARNING) - << "Fallback reached from motorway at " << std::setprecision(12) - << toFloating(coord.lat) << " " << toFloating(coord.lon) << ", no continue angle, " - << intersection.size() << " roads, " << countValid(intersection) << " valid ones."; - fallbackTurnAssignmentMotorway(intersection); - } - } - else - { - const unsigned exiting_motorways = countExitingMotorways(intersection); - - if (exiting_motorways == 0) - { - // Ending in Ramp - for (auto &road : intersection) - { - if (road.entry_allowed) - { - BOOST_ASSERT(detail::isRampClass(road.turn.eid, node_based_graph)); - road.turn.instruction = - TurnInstruction::SUPPRESSED(getTurnDirection(road.turn.angle)); - } - } - } - else if (exiting_motorways == 1) - { - // normal motorway passing some ramps or mering onto another motorway - if (intersection.size() == 2) - { - BOOST_ASSERT(!detail::isRampClass(intersection[1].turn.eid, node_based_graph)); - - intersection[1].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, - isThroughStreet(intersection[1]), intersection[1]); - } - else - { - // Normal Highway exit or merge - for (auto &road : intersection) - { - // ignore invalid uturns/other - if (!road.entry_allowed) - continue; - - if (road.turn.angle == continue_angle) - { - road.turn.instruction = getInstructionForObvious( - intersection.size(), via_edge, isThroughStreet(road), road); - } - else if (road.turn.angle < continue_angle) - { - road.turn.instruction = { - detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp - : TurnType::Turn, - (road.turn.angle < 145) ? DirectionModifier::Right - : DirectionModifier::SlightRight}; - } - else if (road.turn.angle > continue_angle) - { - road.turn.instruction = { - detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp - : TurnType::Turn, - (road.turn.angle > 215) ? DirectionModifier::Left - : DirectionModifier::SlightLeft}; - } - } - } - } - // handle motorway forks - else if (exiting_motorways > 1) - { - if (exiting_motorways == 2 && intersection.size() == 2) - { - intersection[1].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, - isThroughStreet(intersection[1]), intersection[1]); - util::SimpleLogger().Write(logWARNING) - << "Disabled U-Turn on a freeway at " - << localizer(node_based_graph.GetTarget(via_edge)); - intersection[0].entry_allowed = false; // UTURN on the freeway - } - else if (exiting_motorways == 2) - { - // standard fork - std::size_t first_valid = std::numeric_limits::max(), - second_valid = std::numeric_limits::max(); - for (std::size_t i = 0; i < intersection.size(); ++i) - { - if (intersection[i].entry_allowed && - detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph)) - { - if (first_valid < intersection.size()) - { - second_valid = i; - break; - } - else - { - first_valid = i; - } - } - } - assignFork(via_edge, intersection[second_valid], intersection[first_valid]); - } - else if (exiting_motorways == 3) - { - // triple fork - std::size_t first_valid = std::numeric_limits::max(), - second_valid = std::numeric_limits::max(), - third_valid = std::numeric_limits::max(); - for (std::size_t i = 0; i < intersection.size(); ++i) - { - if (intersection[i].entry_allowed && - detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph)) - { - if (second_valid < intersection.size()) - { - third_valid = i; - break; - } - else if (first_valid < intersection.size()) - { - second_valid = i; - } - else - { - first_valid = i; - } - } - } - assignFork(via_edge, intersection[third_valid], intersection[second_valid], - intersection[first_valid]); - } - else - { - auto coord = localizer(node_based_graph.GetTarget(via_edge)); - util::SimpleLogger().Write(logWARNING) - << "Found motorway junction with more than " - "2 exiting motorways or additional ramps at " - << std::setprecision(12) << toFloating(coord.lat) << " " - << toFloating(coord.lon); - fallbackTurnAssignmentMotorway(intersection); - } - } // done for more than one highway exit - } - return intersection; -} - -std::vector -TurnAnalysis::handleMotorwayRamp(const EdgeID via_edge, - std::vector intersection) const -{ - auto num_valid_turns = countValid(intersection); - const auto isThroughStreet = [&intersection, this](const ConnectedRoad &connection) { - if (node_based_graph.GetEdgeData(connection.turn.eid).name_id == INVALID_NAME_ID) - return false; - for (const auto &road : intersection) - { - // a through street cannot start at our own position - if (road.turn.angle < std::numeric_limits::epsilon()) - continue; - if (angularDeviation(road.turn.angle, connection.turn.angle) > - (STRAIGHT_ANGLE - NARROW_TURN_ANGLE) && - node_based_graph.GetEdgeData(road.turn.eid).name_id == - node_based_graph.GetEdgeData(connection.turn.eid).name_id) - return true; - } - return false; - }; - // ramp straight into a motorway/ramp - if (intersection.size() == 2 && num_valid_turns == 1) - { - BOOST_ASSERT(!intersection[0].entry_allowed); - BOOST_ASSERT(detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph)); - - intersection[1].turn.instruction = getInstructionForObvious( - intersection.size(), via_edge, isThroughStreet(intersection[1]), intersection[1]); - } - else if (intersection.size() == 3) - { - // merging onto a passing highway / or two ramps merging onto the same highway - if (num_valid_turns == 1) - { - BOOST_ASSERT(!intersection[0].entry_allowed); - // check order of highways - // 4 - // 5 3 - // - // 6 2 - // - // 7 1 - // 0 - if (intersection[1].entry_allowed) - { - if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) && - node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id != - INVALID_NAME_ID && - node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id == - node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id) - { - // circular order indicates a merge to the left (0-3 onto 4 - if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < - NARROW_TURN_ANGLE) - intersection[1].turn.instruction = {TurnType::Merge, - DirectionModifier::SlightLeft}; - else // fallback - intersection[1].turn.instruction = { - TurnType::Merge, getTurnDirection(intersection[1].turn.angle)}; - } - else // passing by the end of a motorway - { - intersection[1].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, - isThroughStreet(intersection[1]), intersection[1]); - } - } - else - { - BOOST_ASSERT(intersection[2].entry_allowed); - if (detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph) && - node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id != - INVALID_NAME_ID && - node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id == - node_based_graph.GetEdgeData(intersection[0].turn.eid).name_id) - { - // circular order (5-0) onto 4 - if (angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) < - NARROW_TURN_ANGLE) - intersection[2].turn.instruction = {TurnType::Merge, - DirectionModifier::SlightRight}; - else // fallback - intersection[2].turn.instruction = { - TurnType::Merge, getTurnDirection(intersection[2].turn.angle)}; - } - else // passing the end of a highway - { - intersection[2].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, - isThroughStreet(intersection[2]), intersection[2]); - } - } - } - else - { - BOOST_ASSERT(num_valid_turns == 2); - // UTurn on ramps is not possible - BOOST_ASSERT(!intersection[0].entry_allowed); - BOOST_ASSERT(intersection[1].entry_allowed); - BOOST_ASSERT(intersection[2].entry_allowed); - // two motorways starting at end of ramp (fork) - // M M - // \ / - // | - // R - if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) && - detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph)) - { - assignFork(via_edge, intersection[2], intersection[1]); - } - else - { - // continued ramp passing motorway entry - // M R - // M R - // | / - // R - if (detail::isMotorwayClass(node_based_graph.GetEdgeData(intersection[1].turn.eid) - .road_classification.road_class)) - { - intersection[1].turn.instruction = {TurnType::Turn, - DirectionModifier::SlightRight}; - intersection[2].turn.instruction = {TurnType::Continue, - DirectionModifier::SlightLeft}; - } - else - { - assignFork(via_edge, intersection[2], intersection[1]); - } - } - } - } - // On - Off Ramp on passing Motorway, Ramp onto Fork(?) - else if (intersection.size() == 4) - { - bool passed_highway_entry = false; - for (auto &road : intersection) - { - const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid); - if (!road.entry_allowed && - detail::isMotorwayClass(edge_data.road_classification.road_class)) - { - passed_highway_entry = true; - } - else if (detail::isMotorwayClass(edge_data.road_classification.road_class)) - { - road.turn.instruction = {TurnType::Merge, passed_highway_entry - ? DirectionModifier::SlightRight - : DirectionModifier::SlightLeft}; - } - else - { - BOOST_ASSERT(isRampClass(edge_data.road_classification.road_class)); - road.turn.instruction = {TurnType::Ramp, getTurnDirection(road.turn.angle)}; - } - } - } - else - { // FALLBACK, hopefully this should never been reached - util::SimpleLogger().Write(logWARNING) << "Reached fallback on motorway ramp with " - << intersection.size() << " roads and " - << countValid(intersection) << " valid turns."; - fallbackTurnAssignmentMotorway(intersection); - } - return intersection; -} - -std::vector -TurnAnalysis::handleMotorwayJunction(const EdgeID via_edge, - std::vector intersection) const -{ - // BOOST_ASSERT(!intersection[0].entry_allowed); //This fails due to @themarex handling of dead - // end - // streets - const auto &in_data = node_based_graph.GetEdgeData(via_edge); - - // coming from motorway - if (detail::isMotorwayClass(in_data.road_classification.road_class)) - { - return handleFromMotorway(via_edge, std::move(intersection)); - } - else // coming from a ramp - { - return handleMotorwayRamp(via_edge, std::move(intersection)); - // ramp merging straight onto motorway - } -} - -bool TurnAnalysis::isMotorwayJunction(const EdgeID via_edge, - const std::vector &intersection) const -{ - bool has_motorway = false; - bool has_normal_roads = false; - - for (const auto &road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - // not merging or forking? - if (road.entry_allowed && angularDeviation(road.turn.angle, STRAIGHT_ANGLE) > 60) - return false; - else if (out_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY || - out_data.road_classification.road_class == FunctionalRoadClass::TRUNK) - { - if (road.entry_allowed) - has_motorway = true; - } - else if (!isRampClass(out_data.road_classification.road_class)) - has_normal_roads = true; - } - - if (has_normal_roads) - return false; - - const auto &in_data = node_based_graph.GetEdgeData(via_edge); - return has_motorway || - in_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY || - in_data.road_classification.road_class == FunctionalRoadClass::TRUNK; -} - -TurnType TurnAnalysis::findBasicTurnType(const EdgeID via_edge, const ConnectedRoad &road) const -{ - - const auto &in_data = node_based_graph.GetEdgeData(via_edge); - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - - bool on_ramp = isRampClass(in_data.road_classification.road_class); - - bool onto_ramp = isRampClass(out_data.road_classification.road_class); - - if (!on_ramp && onto_ramp) - return TurnType::Ramp; - - if (in_data.name_id == out_data.name_id && in_data.name_id != INVALID_NAME_ID) - { - return TurnType::Continue; - } - - return TurnType::Turn; -} - -TurnInstruction TurnAnalysis::getInstructionForObvious(const std::size_t num_roads, - const EdgeID via_edge, - const bool through_street, - const ConnectedRoad &road) const -{ - const auto type = findBasicTurnType(via_edge, road); - // handle travel modes: - const auto in_mode = node_based_graph.GetEdgeData(via_edge).travel_mode; - const auto out_mode = node_based_graph.GetEdgeData(road.turn.eid).travel_mode; - if (type == TurnType::Ramp) - { - return {TurnType::Ramp, getTurnDirection(road.turn.angle)}; - } - - if (angularDeviation(road.turn.angle, 0) < 0.01) - { - return {TurnType::Turn, DirectionModifier::UTurn}; - } - if (type == TurnType::Turn) - { - const auto &in_data = node_based_graph.GetEdgeData(via_edge); - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - if (in_data.name_id != out_data.name_id && - requiresNameAnnounced(name_table.GetNameForID(in_data.name_id), - name_table.GetNameForID(out_data.name_id))) - { - // obvious turn onto a through street is a merge - if (through_street) - { - return {TurnType::Merge, road.turn.angle > STRAIGHT_ANGLE - ? DirectionModifier::SlightRight - : DirectionModifier::SlightLeft}; - } - else - { - return {TurnType::NewName, getTurnDirection(road.turn.angle)}; - } - } - else - { - if (in_mode == out_mode) - return {TurnType::Suppressed, getTurnDirection(road.turn.angle)}; - else - return {TurnType::Notification, getTurnDirection(road.turn.angle)}; - } - } - BOOST_ASSERT(type == TurnType::Continue); - if (in_mode != out_mode) - { - return {TurnType::Notification, getTurnDirection(road.turn.angle)}; - } - if (num_roads > 2) - { - return {TurnType::Suppressed, getTurnDirection(road.turn.angle)}; - } - else - { - return {TurnType::NoTurn, getTurnDirection(road.turn.angle)}; - } -} - -std::vector -TurnAnalysis::handleOneWayTurn(std::vector intersection) const -{ - BOOST_ASSERT(intersection[0].turn.angle < 0.001); - return intersection; -} - -std::vector -TurnAnalysis::handleTwoWayTurn(const EdgeID via_edge, std::vector intersection) const -{ - BOOST_ASSERT(intersection[0].turn.angle < 0.001); - intersection[1].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); - - if (intersection[1].turn.instruction.type == TurnType::Suppressed) - intersection[1].turn.instruction.type = TurnType::NoTurn; - - return intersection; -} - -std::vector -TurnAnalysis::handleThreeWayTurn(const EdgeID via_edge, - std::vector intersection) const -{ - BOOST_ASSERT(intersection[0].turn.angle < 0.001); - const auto isObviousOfTwo = [this](const ConnectedRoad road, const ConnectedRoad other) { - const auto first_class = - node_based_graph.GetEdgeData(road.turn.eid).road_classification.road_class; - const bool is_ramp = isRampClass(first_class); - const auto second_class = - node_based_graph.GetEdgeData(other.turn.eid).road_classification.road_class; - const bool is_narrow_turn = - angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE; - const bool other_turn_is_at_least_orthogonal = - angularDeviation(other.turn.angle, STRAIGHT_ANGLE) > 85; - const bool turn_is_perfectly_straight = angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < - std::numeric_limits::epsilon(); - const bool is_obvious_by_road_class = - (!is_ramp && (2 * getPriority(first_class) < getPriority(second_class))) || - (!isLowPriorityRoadClass(first_class) && isLowPriorityRoadClass(second_class)); - const bool is_much_narrower_than_other = - angularDeviation(other.turn.angle, STRAIGHT_ANGLE) / - angularDeviation(road.turn.angle, STRAIGHT_ANGLE) > - INCREASES_BY_FOURTY_PERCENT; - return (is_narrow_turn && other_turn_is_at_least_orthogonal) || - turn_is_perfectly_straight || is_much_narrower_than_other || - is_obvious_by_road_class; - }; - - /* Two nearly straight turns -> FORK - OOOOOOO - / - IIIIII - \ - OOOOOOO - */ - if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE && - angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE) - { - if (intersection[1].entry_allowed && intersection[2].entry_allowed) - { - const auto left_class = node_based_graph.GetEdgeData(intersection[2].turn.eid) - .road_classification.road_class; - const auto right_class = node_based_graph.GetEdgeData(intersection[1].turn.eid) - .road_classification.road_class; - if (canBeSeenAsFork(left_class, right_class)) - { - assignFork(via_edge, intersection[2], intersection[1]); - } - else if (isObviousOfTwo(intersection[1], intersection[2])) - { - intersection[1].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - DirectionModifier::SlightLeft}; - } - else if (isObviousOfTwo(intersection[2], intersection[1])) - { - intersection[2].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]); - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - DirectionModifier::SlightRight}; - } - else - { - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - DirectionModifier::SlightRight}; - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - DirectionModifier::SlightLeft}; - } - } - else - { - if (intersection[1].entry_allowed) - intersection[1].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); - if (intersection[2].entry_allowed) - intersection[2].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]); - } - } - /* T Intersection - - OOOOOOO T OOOOOOOO - I - I - I - */ - else if (angularDeviation(intersection[1].turn.angle, 90) < NARROW_TURN_ANGLE && - angularDeviation(intersection[2].turn.angle, 270) < NARROW_TURN_ANGLE && - angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) > - NARROW_TURN_ANGLE) - { - if (intersection[1].entry_allowed) - { - if (TurnType::Ramp != findBasicTurnType(via_edge, intersection[1])) - intersection[1].turn.instruction = {TurnType::EndOfRoad, DirectionModifier::Right}; - else - intersection[1].turn.instruction = {TurnType::Ramp, DirectionModifier::Right}; - } - if (intersection[2].entry_allowed) - { - if (TurnType::Ramp != findBasicTurnType(via_edge, intersection[2])) - - intersection[2].turn.instruction = {TurnType::EndOfRoad, DirectionModifier::Left}; - else - intersection[2].turn.instruction = {TurnType::Ramp, DirectionModifier::Left}; - } - } - /* T Intersection, Cross left - O - O - O - IIIIIIII - OOOOOOOOOO - */ - else if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE && - angularDeviation(intersection[2].turn.angle, 270) < NARROW_TURN_ANGLE && - angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) > - NARROW_TURN_ANGLE) - { - if (intersection[1].entry_allowed) - { - if (TurnType::Ramp != findBasicTurnType(via_edge, intersection[1])) - intersection[1].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); - else - intersection[1].turn.instruction = {TurnType::Ramp, DirectionModifier::Straight}; - } - if (intersection[2].entry_allowed) - { - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - DirectionModifier::Left}; - } - } - /* T Intersection, Cross right - - IIIIIIII T OOOOOOOOOO - O - O - O - */ - else if (angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE && - angularDeviation(intersection[1].turn.angle, 90) < NARROW_TURN_ANGLE && - angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) > - NARROW_TURN_ANGLE) - { - if (intersection[2].entry_allowed) - intersection[2].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]); - if (intersection[1].entry_allowed) - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - DirectionModifier::Right}; - } - // merge onto a through street - else if (INVALID_NAME_ID != node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id && - node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id == - node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id) - { - const auto findTurn = [isObviousOfTwo](const ConnectedRoad turn, - const ConnectedRoad other) -> TurnInstruction { - if (isObviousOfTwo(turn, other)) - { - return {TurnType::Merge, turn.turn.angle < STRAIGHT_ANGLE - ? DirectionModifier::SlightLeft - : DirectionModifier::SlightRight}; - } - else - { - return {TurnType::Turn, getTurnDirection(turn.turn.angle)}; - } - }; - intersection[1].turn.instruction = findTurn(intersection[1], intersection[2]); - intersection[2].turn.instruction = findTurn(intersection[2], intersection[1]); - } - - // other street merges from the left - else if (INVALID_NAME_ID != node_based_graph.GetEdgeData(via_edge).name_id && - node_based_graph.GetEdgeData(via_edge).name_id == - node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id) - { - if (isObviousOfTwo(intersection[1], intersection[2])) - { - intersection[1].turn.instruction = - TurnInstruction::SUPPRESSED(DirectionModifier::Straight); - } - else - { - intersection[1].turn.instruction = {TurnType::Continue, - getTurnDirection(intersection[1].turn.angle)}; - } - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - getTurnDirection(intersection[2].turn.angle)}; - } - // other street merges from the right - else if (INVALID_NAME_ID != node_based_graph.GetEdgeData(via_edge).name_id && - node_based_graph.GetEdgeData(via_edge).name_id == - node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id) - { - if (isObviousOfTwo(intersection[2], intersection[1])) - { - intersection[2].turn.instruction = - TurnInstruction::SUPPRESSED(DirectionModifier::Straight); - } - else - { - intersection[2].turn.instruction = {TurnType::Continue, - getTurnDirection(intersection[2].turn.angle)}; - } - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - getTurnDirection(intersection[1].turn.angle)}; - } - else - { - if (isObviousOfTwo(intersection[1], intersection[2])) - { - intersection[1].turn.instruction = - getInstructionForObvious(3, via_edge, false, intersection[1]); - } - else - { - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - getTurnDirection(intersection[1].turn.angle)}; - } - - if (isObviousOfTwo(intersection[2], intersection[1])) - { - intersection[2].turn.instruction = - getInstructionForObvious(3, via_edge, false, intersection[2]); - } - else - { - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - getTurnDirection(intersection[2].turn.angle)}; - } - } - // unnamed intersections or basic three way turn - - // remain at basic turns - // TODO handle obviousness, Handle Merges - return intersection; -} - -void TurnAnalysis::handleDistinctConflict(const EdgeID via_edge, - ConnectedRoad &left, - ConnectedRoad &right) const -{ - // single turn of both is valid (don't change the valid one) - // or multiple identical angles -> bad OSM intersection - if ((!left.entry_allowed || !right.entry_allowed) || (left.turn.angle == right.turn.angle)) - { - if (left.entry_allowed) - left.turn.instruction = {findBasicTurnType(via_edge, left), - getTurnDirection(left.turn.angle)}; - if (right.entry_allowed) - right.turn.instruction = {findBasicTurnType(via_edge, right), - getTurnDirection(right.turn.angle)}; - return; - } - - if (getTurnDirection(left.turn.angle) == DirectionModifier::Straight || - getTurnDirection(left.turn.angle) == DirectionModifier::SlightLeft || - getTurnDirection(right.turn.angle) == DirectionModifier::SlightRight) - { - const auto left_class = - node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class; - const auto right_class = - node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class; - if (canBeSeenAsFork(left_class, right_class)) - assignFork(via_edge, left, right); - else if (getPriority(left_class) > getPriority(right_class)) - { - // FIXME this should possibly know about the actual roads? - // here we don't know about the intersection size. To be on the save side, we declare it - // as complex (at least size 4) - right.turn.instruction = getInstructionForObvious(4, via_edge, false, right); - left.turn.instruction = {findBasicTurnType(via_edge, left), - DirectionModifier::SlightLeft}; - } - else - { - // FIXME this should possibly know about the actual roads? - // here we don't know about the intersection size. To be on the save side, we declare it - // as complex (at least size 4) - left.turn.instruction = getInstructionForObvious(4, via_edge, false, left); - right.turn.instruction = {findBasicTurnType(via_edge, right), - DirectionModifier::SlightRight}; - } - } - - const auto left_type = findBasicTurnType(via_edge, left); - const auto right_type = findBasicTurnType(via_edge, right); - // Two Right Turns - if (angularDeviation(left.turn.angle, 90) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) - { - // Keep left perfect, shift right - left.turn.instruction = {left_type, DirectionModifier::Right}; - right.turn.instruction = {right_type, DirectionModifier::SharpRight}; - return; - } - if (angularDeviation(right.turn.angle, 90) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) - { - // Keep Right perfect, shift left - left.turn.instruction = {left_type, DirectionModifier::SlightRight}; - right.turn.instruction = {right_type, DirectionModifier::Right}; - return; - } - // Two Right Turns - if (angularDeviation(left.turn.angle, 270) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) - { - // Keep left perfect, shift right - left.turn.instruction = {left_type, DirectionModifier::Left}; - right.turn.instruction = {right_type, DirectionModifier::SlightLeft}; - return; - } - if (angularDeviation(right.turn.angle, 270) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) - { - // Keep Right perfect, shift left - left.turn.instruction = {left_type, DirectionModifier::SharpLeft}; - right.turn.instruction = {right_type, DirectionModifier::Left}; - return; - } - // Both turns? - if (TurnType::Ramp != left_type && TurnType::Ramp != right_type) - { - if (left.turn.angle < STRAIGHT_ANGLE) - { - left.turn.instruction = {TurnType::FirstTurn, getTurnDirection(left.turn.angle)}; - right.turn.instruction = {TurnType::SecondTurn, getTurnDirection(right.turn.angle)}; - } - else - { - left.turn.instruction = {TurnType::SecondTurn, getTurnDirection(left.turn.angle)}; - right.turn.instruction = {TurnType::FirstTurn, getTurnDirection(right.turn.angle)}; - } - return; - } - // Shift the lesser penalty - if (getTurnDirection(left.turn.angle) == DirectionModifier::SharpLeft) - { - left.turn.instruction = {left_type, DirectionModifier::SharpLeft}; - right.turn.instruction = {right_type, DirectionModifier::Left}; - return; - } - if (getTurnDirection(right.turn.angle) == DirectionModifier::SharpRight) - { - left.turn.instruction = {left_type, DirectionModifier::Right}; - right.turn.instruction = {right_type, DirectionModifier::SharpRight}; - return; - } - - if (getTurnDirection(left.turn.angle) == DirectionModifier::Right) - { - if (angularDeviation(left.turn.angle, 90) > angularDeviation(right.turn.angle, 90)) - { - left.turn.instruction = {left_type, DirectionModifier::SlightRight}; - right.turn.instruction = {right_type, DirectionModifier::Right}; - } - else - { - left.turn.instruction = {left_type, DirectionModifier::Right}; - right.turn.instruction = {right_type, DirectionModifier::SharpRight}; - } - } - else - { - if (angularDeviation(left.turn.angle, 270) > angularDeviation(right.turn.angle, 270)) - { - left.turn.instruction = {left_type, DirectionModifier::SharpLeft}; - right.turn.instruction = {right_type, DirectionModifier::Left}; - } - else - { - left.turn.instruction = {left_type, DirectionModifier::Left}; - right.turn.instruction = {right_type, DirectionModifier::SlightLeft}; - } - } -} - -std::vector -TurnAnalysis::handleComplexTurn(const EdgeID via_edge, - std::vector intersection) const -{ - static int fallback_count = 0; - const std::size_t obvious_index = findObviousTurn(via_edge, intersection); - const auto fork_range = findFork(intersection); - std::size_t straightmost_turn = 0; - double straightmost_deviation = 180; - for (std::size_t i = 0; i < intersection.size(); ++i) - { - const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE); - if (deviation < straightmost_deviation) - { - straightmost_deviation = deviation; - straightmost_turn = i; - } - } - - // check whether the obvious choice is actually a through street - const auto isThroughStreet = [&intersection, this](const std::size_t index) { - if (node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id == INVALID_NAME_ID) - return false; - for (const auto &road : intersection) - { - // a through street cannot start at our own position - if (road.turn.angle < std::numeric_limits::epsilon()) - continue; - if (angularDeviation(road.turn.angle, intersection[index].turn.angle) > - (STRAIGHT_ANGLE - NARROW_TURN_ANGLE) && - node_based_graph.GetEdgeData(road.turn.eid).name_id == - node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id) - return true; - } - return false; - }; - - if (obvious_index != 0) - { - intersection[obvious_index].turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, isThroughStreet(obvious_index), - intersection[obvious_index]); - - // assign left/right turns - intersection = assignLeftTurns(via_edge, std::move(intersection), obvious_index + 1); - intersection = assignRightTurns(via_edge, std::move(intersection), obvious_index); - } - else if (fork_range.first != 0 && fork_range.second - fork_range.first <= 2) // found fork - { - if (fork_range.second - fork_range.first == 1) - { - auto &left = intersection[fork_range.second]; - auto &right = intersection[fork_range.first]; - const auto left_class = - node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class; - const auto right_class = - node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class; - if (canBeSeenAsFork(left_class, right_class)) - assignFork(via_edge, left, right); - else if (getPriority(left_class) > getPriority(right_class)) - { - right.turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, right); - left.turn.instruction = {findBasicTurnType(via_edge, left), - DirectionModifier::SlightLeft}; - } - else - { - left.turn.instruction = - getInstructionForObvious(intersection.size(), via_edge, false, left); - right.turn.instruction = {findBasicTurnType(via_edge, right), - DirectionModifier::SlightRight}; - } - } - else if (fork_range.second - fork_range.second == 2) - { - assignFork(via_edge, intersection[fork_range.second], - intersection[fork_range.first + 1], intersection[fork_range.first]); - } - // assign left/right turns - intersection = assignLeftTurns(via_edge, std::move(intersection), fork_range.second + 1); - intersection = assignRightTurns(via_edge, std::move(intersection), fork_range.first); - } - else if (straightmost_deviation < FUZZY_ANGLE_DIFFERENCE && - !intersection[straightmost_turn].entry_allowed) - { - // invalid straight turn - intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_turn + 1); - intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_turn); - } - // no straight turn - else if (intersection[straightmost_turn].turn.angle > 180) - { - // at most three turns on either side - intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_turn); - intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_turn); - } - else if (intersection[straightmost_turn].turn.angle < 180) - { - intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_turn + 1); - intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_turn + 1); - } - else - { - if (fallback_count++ < 10) - { - const auto coord = localizer(node_based_graph.GetTarget(via_edge)); - util::SimpleLogger().Write(logWARNING) - << "Resolved to keep fallback on complex turn assignment at " - << std::setprecision(12) << toFloating(coord.lat) << " " << toFloating(coord.lon) - << "Straightmost: " << straightmost_turn; - ; - for (const auto &road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - util::SimpleLogger().Write(logWARNING) - << "road: " << road.toString() << " Name: " << out_data.name_id - << " Road Class: " << (int)out_data.road_classification.road_class - << " At: " << localizer(node_based_graph.GetTarget(road.turn.eid)); - } - } - } - return intersection; -} - // Sets basic turn types as fallback for otherwise unhandled turns -std::vector TurnAnalysis::setTurnTypes(const NodeID from, - const EdgeID via_edge, - std::vector intersection) const +Intersection TurnAnalysis::setTurnTypes(const NodeID from_nid, + const EdgeID, + Intersection intersection) const { for (auto &road : intersection) { @@ -1484,1044 +86,11 @@ std::vector TurnAnalysis::setTurnTypes(const NodeID from, continue; const EdgeID onto_edge = road.turn.eid; - const NodeID to_node = node_based_graph.GetTarget(onto_edge); + const NodeID to_nid = node_based_graph.GetTarget(onto_edge); - road.turn.instruction = (from == to_node) - ? TurnInstruction{TurnType::Turn, DirectionModifier::UTurn} - : TurnInstruction{findBasicTurnType(via_edge, road), - getTurnDirection(road.turn.angle)}; - } - return intersection; -} - -// a -// | -// | -// v -// For an intersection from_node --via_edi--> turn_node ----> c -// ^ -// | -// | -// b -// This functions returns _all_ turns as if the graph was undirected. -// That means we not only get (from_node, turn_node, c) in the above example -// but also (from_node, turn_node, a), (from_node, turn_node, b). These turns are -// marked as invalid and only needed for intersection classification. -std::vector TurnAnalysis::getConnectedRoads(const NodeID from_node, - const EdgeID via_eid) const -{ - std::vector intersection; - const NodeID turn_node = node_based_graph.GetTarget(via_eid); - const NodeID only_restriction_to_node = - restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node); - const bool is_barrier_node = barrier_nodes.find(turn_node) != barrier_nodes.end(); - - bool has_uturn_edge = false; - for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) - { - BOOST_ASSERT(onto_edge != SPECIAL_EDGEID); - const NodeID to_node = node_based_graph.GetTarget(onto_edge); - - bool turn_is_valid = - // reverse edges are never valid turns because the resulting turn would look like this: - // from_node --via_edge--> turn_node <--onto_edge-- to_node - // however we need this for capture intersection shape for incoming one-ways - !node_based_graph.GetEdgeData(onto_edge).reversed && - // we are not turning over a barrier - (!is_barrier_node || from_node == to_node) && - // We are at an only_-restriction but not at the right turn. - (only_restriction_to_node == SPECIAL_NODEID || to_node == only_restriction_to_node) && - // the turn is not restricted - !restriction_map.CheckIfTurnIsRestricted(from_node, turn_node, to_node); - - auto angle = 0.; - if (from_node == to_node) - { - if (turn_is_valid && !is_barrier_node) - { - // we only add u-turns for dead-end streets. - if (node_based_graph.GetOutDegree(turn_node) > 1) - { - auto number_of_emmiting_bidirectional_edges = 0; - for (auto edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) - { - auto target = node_based_graph.GetTarget(edge); - auto reverse_edge = node_based_graph.FindEdge(target, turn_node); - BOOST_ASSERT(reverse_edge != SPECIAL_EDGEID); - if (!node_based_graph.GetEdgeData(reverse_edge).reversed) - { - ++number_of_emmiting_bidirectional_edges; - } - } - // is a dead-end - turn_is_valid = number_of_emmiting_bidirectional_edges <= 1; - } - } - has_uturn_edge = true; - BOOST_ASSERT(angle >= 0. && angle < std::numeric_limits::epsilon()); - } - else - { - // unpack first node of second segment if packed - const auto first_coordinate = getRepresentativeCoordinate( - from_node, turn_node, via_eid, INVERT, compressed_edge_container, node_info_list); - const auto third_coordinate = getRepresentativeCoordinate( - turn_node, to_node, onto_edge, !INVERT, compressed_edge_container, node_info_list); - angle = util::coordinate_calculation::computeAngle( - first_coordinate, node_info_list[turn_node], third_coordinate); - if (angle < std::numeric_limits::epsilon()) - has_uturn_edge = true; - } - - intersection.push_back(ConnectedRoad( - TurnOperation{onto_edge, angle, {TurnType::Invalid, DirectionModifier::UTurn}}, - turn_is_valid)); - } - - // We hit the case of a street leading into nothing-ness. Since the code here assumes that this - // will - // never happen we add an artificial invalid uturn in this case. - if (!has_uturn_edge) - { - intersection.push_back( - {TurnOperation{via_eid, 0., {TurnType::Invalid, DirectionModifier::UTurn}}, false}); - } - - const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) { - return first.turn.angle < second.turn.angle; - }; - std::sort(std::begin(intersection), std::end(intersection), ByAngle); - - BOOST_ASSERT(intersection[0].turn.angle >= 0. && - intersection[0].turn.angle < std::numeric_limits::epsilon()); - - return mergeSegregatedRoads(std::move(intersection)); -} - -/* - * Segregated Roads often merge onto a single intersection. - * While technically representing different roads, they are - * often looked at as a single road. - * Due to the merging, turn Angles seem off, wenn we compute them from the - * initial positions. - * - * bb>b>b(2)>b>b>b - * - * Would be seen as a slight turn going fro a to (2). A Sharp turn going from - * (1) to (2). - * - * In cases like these, we megre this segregated roads into a single road to - * end up with a case like: - * - * aaaaa-bbbbbb - * - * for the turn representation. - * Anything containing the first u-turn in a merge affects all other angles - * and is handled separately from all others. - */ -std::vector -TurnAnalysis::mergeSegregatedRoads(std::vector intersection) const -{ - const auto getRight = [&](std::size_t index) { - return (index + intersection.size() - 1) % intersection.size(); - }; - - const auto mergable = [&](std::size_t first, std::size_t second) -> bool { - const auto &first_data = node_based_graph.GetEdgeData(intersection[first].turn.eid); - const auto &second_data = node_based_graph.GetEdgeData(intersection[second].turn.eid); - - return first_data.name_id != INVALID_NAME_ID && first_data.name_id == second_data.name_id && - !first_data.roundabout && !second_data.roundabout && - first_data.travel_mode == second_data.travel_mode && - first_data.road_classification == second_data.road_classification && - // compatible threshold - angularDeviation(intersection[first].turn.angle, intersection[second].turn.angle) < - 60 && - first_data.reversed != second_data.reversed; - }; - - const auto merge = [](const ConnectedRoad &first, - const ConnectedRoad &second) -> ConnectedRoad { - if (!first.entry_allowed) - { - ConnectedRoad result = second; - result.turn.angle = (first.turn.angle + second.turn.angle) / 2; - if (first.turn.angle - second.turn.angle > 180) - result.turn.angle += 180; - if (result.turn.angle > 360) - result.turn.angle -= 360; - - return result; - } - else - { - BOOST_ASSERT(!second.entry_allowed); - ConnectedRoad result = first; - result.turn.angle = (first.turn.angle + second.turn.angle) / 2; - - if (first.turn.angle - second.turn.angle > 180) - result.turn.angle += 180; - if (result.turn.angle > 360) - result.turn.angle -= 360; - - return result; - } - }; - if (intersection.size() == 1) - return intersection; - - // check for merges including the basic u-turn - // these result in an adjustment of all other angles - if (mergable(0, intersection.size() - 1)) - { - const double correction_factor = - (360 - intersection[intersection.size() - 1].turn.angle) / 2; - for (std::size_t i = 1; i + 1 < intersection.size(); ++i) - intersection[i].turn.angle += correction_factor; - intersection[0] = merge(intersection.front(), intersection.back()); - intersection[0].turn.angle = 0; - intersection.pop_back(); - } - else if (mergable(0, 1)) - { - const double correction_factor = (intersection[1].turn.angle) / 2; - for (std::size_t i = 2; i < intersection.size(); ++i) - intersection[i].turn.angle += correction_factor; - intersection[0] = merge(intersection[0], intersection[1]); - intersection[0].turn.angle = 0; - intersection.erase(intersection.begin() + 1); - } - - // a merge including the first u-turn requres an adjustment of the turn angles - // therefore these are handled prior to this step - for (std::size_t index = 2; index < intersection.size(); ++index) - { - if (mergable(index, getRight(index))) - { - intersection[getRight(index)] = - merge(intersection[getRight(index)], intersection[index]); - intersection.erase(intersection.begin() + index); - --index; - } - } - - const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) { - return first.turn.angle < second.turn.angle; - }; - std::sort(std::begin(intersection), std::end(intersection), ByAngle); - return intersection; -} - -void TurnAnalysis::assignFork(const EdgeID via_edge, - ConnectedRoad &left, - ConnectedRoad &right) const -{ - const auto &in_data = node_based_graph.GetEdgeData(via_edge); - const bool low_priority_left = isLowPriorityRoadClass( - node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class); - const bool low_priority_right = isLowPriorityRoadClass( - node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class); - if ((angularDeviation(left.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION && - angularDeviation(right.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)) - { - // left side is actually straight - const auto &out_data = node_based_graph.GetEdgeData(left.turn.eid); - if (requiresAnnouncement(in_data, out_data)) - { - if (low_priority_right && !low_priority_left) - { - left.turn.instruction = getInstructionForObvious(3, via_edge, false, left); - right.turn.instruction = {findBasicTurnType(via_edge, right), - DirectionModifier::SlightRight}; - } - else - { - if (low_priority_left && !low_priority_right) - { - left.turn.instruction = {findBasicTurnType(via_edge, left), - DirectionModifier::SlightLeft}; - right.turn.instruction = {findBasicTurnType(via_edge, right), - DirectionModifier::SlightRight}; - } - else - { - left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; - right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; - } - } - } - else - { - left.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; - right.turn.instruction = {findBasicTurnType(via_edge, right), - DirectionModifier::SlightRight}; - } - } - else if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) < - MAXIMAL_ALLOWED_NO_TURN_DEVIATION && - angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) - { - // right side is actually straight - const auto &out_data = node_based_graph.GetEdgeData(right.turn.eid); - if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) < - MAXIMAL_ALLOWED_NO_TURN_DEVIATION && - angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) - { - if (requiresAnnouncement(in_data, out_data)) - { - if (low_priority_left && !low_priority_right) - { - left.turn.instruction = {findBasicTurnType(via_edge, left), - DirectionModifier::SlightLeft}; - right.turn.instruction = getInstructionForObvious(3, via_edge, false, right); - } - else - { - if (low_priority_right && !low_priority_left) - { - left.turn.instruction = {findBasicTurnType(via_edge, left), - DirectionModifier::SlightLeft}; - right.turn.instruction = {findBasicTurnType(via_edge, right), - DirectionModifier::SlightRight}; - } - else - { - right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; - left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; - } - } - } - else - { - right.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; - left.turn.instruction = {findBasicTurnType(via_edge, left), - DirectionModifier::SlightLeft}; - } - } - } - else - { - // left side of fork - if (low_priority_right && !low_priority_left) - left.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft}; - else - { - if (low_priority_left && !low_priority_right) - left.turn.instruction = {TurnType::Turn, DirectionModifier::SlightLeft}; - else - left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; - } - - // right side of fork - if (low_priority_left && !low_priority_right) - right.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft}; - else - { - if (low_priority_right && !low_priority_left) - right.turn.instruction = {TurnType::Turn, DirectionModifier::SlightRight}; - else - right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; - } - } -} - -void TurnAnalysis::assignFork(const EdgeID via_edge, - ConnectedRoad &left, - ConnectedRoad ¢er, - ConnectedRoad &right) const -{ - // TODO handle low priority road classes in a reasonable way - if (left.entry_allowed && center.entry_allowed && right.entry_allowed) - { - left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; - if (angularDeviation(center.turn.angle, 180) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) - { - const auto &in_data = node_based_graph.GetEdgeData(via_edge); - const auto &out_data = node_based_graph.GetEdgeData(center.turn.eid); - if (requiresAnnouncement(in_data, out_data)) - { - center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight}; - } - else - { - center.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; - } - } - else - { - center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight}; - } - right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; - } - else if (left.entry_allowed) - { - if (right.entry_allowed) - assignFork(via_edge, left, right); - else if (center.entry_allowed) - assignFork(via_edge, left, center); - else - left.turn.instruction = {findBasicTurnType(via_edge, left), - getTurnDirection(left.turn.angle)}; - } - else if (right.entry_allowed) - { - if (center.entry_allowed) - assignFork(via_edge, center, right); - else - right.turn.instruction = {findBasicTurnType(via_edge, right), - getTurnDirection(right.turn.angle)}; - } - else - { - if (center.entry_allowed) - center.turn.instruction = {findBasicTurnType(via_edge, center), - getTurnDirection(center.turn.angle)}; - } -} - -std::size_t TurnAnalysis::findObviousTurn(const EdgeID via_edge, - const std::vector &intersection) const -{ - // no obvious road - if (intersection.size() == 1) - return 0; - - // a single non u-turn is obvious - if (intersection.size() == 2) - return 1; - - // at least three roads - std::size_t best = 0; - double best_deviation = 180; - - std::size_t best_continue = 0; - double best_continue_deviation = 180; - - const EdgeData &in_data = node_based_graph.GetEdgeData(via_edge); - for (std::size_t i = 1; i < intersection.size(); ++i) - { - const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE); - if (intersection[i].entry_allowed && deviation < best_deviation) - { - best_deviation = deviation; - best = i; - } - - const auto out_data = node_based_graph.GetEdgeData(intersection[i].turn.eid); - if (intersection[i].entry_allowed && out_data.name_id == in_data.name_id && - deviation < best_continue_deviation) - { - best_continue_deviation = deviation; - best_continue = i; - } - } - - if (best == 0) - return 0; - - if (best_deviation >= 2 * NARROW_TURN_ANGLE) - return 0; - - // TODO incorporate road class in decision - if (best != 0 && best_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) - { - return best; - } - - // has no obvious continued road - if (best_continue == 0 || true) - { - // Find left/right deviation - const double left_deviation = angularDeviation( - intersection[(best + 1) % intersection.size()].turn.angle, STRAIGHT_ANGLE); - const double right_deviation = - angularDeviation(intersection[best - 1].turn.angle, STRAIGHT_ANGLE); - - if (best_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION && - std::min(left_deviation, right_deviation) > FUZZY_ANGLE_DIFFERENCE) - return best; - - // other narrow turns? - if (angularDeviation(intersection[best - 1].turn.angle, STRAIGHT_ANGLE) <= - FUZZY_ANGLE_DIFFERENCE) - return 0; - if (angularDeviation(intersection[(best + 1) % intersection.size()].turn.angle, - STRAIGHT_ANGLE) <= FUZZY_ANGLE_DIFFERENCE) - return 0; - - // Well distinct turn that is nearly straight - if (left_deviation / best_deviation >= DISTINCTION_RATIO && - right_deviation / best_deviation >= DISTINCTION_RATIO) - { - return best; - } - } - - return 0; // no obvious turn -} - -std::pair -TurnAnalysis::findFork(const std::vector &intersection) const -{ - - std::size_t best = 0; - double best_deviation = 180; - - // TODO handle road classes - for (std::size_t i = 1; i < intersection.size(); ++i) - { - const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE); - if (intersection[i].entry_allowed && deviation < best_deviation) - { - best_deviation = deviation; - best = i; - } - } - if (best_deviation <= NARROW_TURN_ANGLE) - { - std::size_t left = best, right = best; - if (intersection[left].turn.angle >= 180) - { - // due to best > 1, we can safely decrement right - --right; - if (angularDeviation(intersection[right].turn.angle, STRAIGHT_ANGLE) > - NARROW_TURN_ANGLE) - return std::make_pair(std::size_t{0}, std::size_t{0}); - } - else - { - ++left; - if (left >= intersection.size() || - angularDeviation(intersection[left].turn.angle, STRAIGHT_ANGLE) > NARROW_TURN_ANGLE) - return std::make_pair(std::size_t{0}, std::size_t{0}); - } - while (left + 1 < intersection.size() && - angularDeviation(intersection[left].turn.angle, intersection[left + 1].turn.angle) < - NARROW_TURN_ANGLE) - ++left; - while (right > 1 && - angularDeviation(intersection[right].turn.angle, - intersection[right - 1].turn.angle) < NARROW_TURN_ANGLE) - --right; - - // TODO check whether 2*NARROW_TURN is too large - if (right < left && - angularDeviation(intersection[left].turn.angle, - intersection[(left + 1) % intersection.size()].turn.angle) >= - 2 * NARROW_TURN_ANGLE && - angularDeviation(intersection[right].turn.angle, intersection[right - 1].turn.angle) >= - 2 * NARROW_TURN_ANGLE) - return std::make_pair(right, left); - } - return std::make_pair(std::size_t{0}, std::size_t{0}); -} - -// Can only assign three turns -std::vector TurnAnalysis::assignLeftTurns(const EdgeID via_edge, - std::vector intersection, - const std::size_t starting_at) const -{ - const auto count_valid = [&intersection, starting_at]() { - std::size_t count = 0; - for (std::size_t i = starting_at; i < intersection.size(); ++i) - if (intersection[i].entry_allowed) - ++count; - return count; - }; - if (starting_at == intersection.size() || count_valid() == 0) - return intersection; - // handle single turn - if (intersection.size() - starting_at == 1) - { - if (!intersection[starting_at].entry_allowed) - return intersection; - - if (angularDeviation(intersection[starting_at].turn.angle, STRAIGHT_ANGLE) > - NARROW_TURN_ANGLE && - angularDeviation(intersection[starting_at].turn.angle, 0) > NARROW_TURN_ANGLE) - { - // assign left turn - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), DirectionModifier::Left}; - } - else if (angularDeviation(intersection[starting_at].turn.angle, STRAIGHT_ANGLE) <= - NARROW_TURN_ANGLE) - { - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), - DirectionModifier::SlightLeft}; - } - else - { - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), - DirectionModifier::SharpLeft}; - } - } - // two turns on at the side - else if (intersection.size() - starting_at == 2) - { - const auto first_direction = getTurnDirection(intersection[starting_at].turn.angle); - const auto second_direction = getTurnDirection(intersection[starting_at + 1].turn.angle); - if (first_direction == second_direction) - { - // conflict - handleDistinctConflict(via_edge, intersection[starting_at + 1], - intersection[starting_at]); - } - else - { - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; - intersection[starting_at + 1].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 1]), second_direction}; - } - } - else if (intersection.size() - starting_at == 3) - { - const auto first_direction = getTurnDirection(intersection[starting_at].turn.angle); - const auto second_direction = getTurnDirection(intersection[starting_at + 1].turn.angle); - const auto third_direction = getTurnDirection(intersection[starting_at + 2].turn.angle); - if (first_direction != second_direction && second_direction != third_direction) - { - // implies first != third, based on the angles and clockwise order - if (intersection[starting_at].entry_allowed) - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; - if (intersection[starting_at + 1].entry_allowed) - intersection[starting_at + 1].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 1]), second_direction}; - if (intersection[starting_at + 2].entry_allowed) - intersection[starting_at + 2].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 2]), third_direction}; - } - else if (2 >= (intersection[starting_at].entry_allowed + - intersection[starting_at + 1].entry_allowed + - intersection[starting_at + 2].entry_allowed)) - { - // at least one invalid turn - if (!intersection[starting_at].entry_allowed) - { - handleDistinctConflict(via_edge, intersection[starting_at + 2], - intersection[starting_at + 1]); - } - else if (!intersection[starting_at + 1].entry_allowed) - { - handleDistinctConflict(via_edge, intersection[starting_at + 2], - intersection[starting_at]); - } - else - { - handleDistinctConflict(via_edge, intersection[starting_at + 1], - intersection[starting_at]); - } - } - else if (intersection[starting_at].entry_allowed && - intersection[starting_at + 1].entry_allowed && - intersection[starting_at + 2].entry_allowed && - angularDeviation(intersection[starting_at].turn.angle, - intersection[starting_at + 1].turn.angle) >= NARROW_TURN_ANGLE && - angularDeviation(intersection[starting_at + 1].turn.angle, - intersection[starting_at + 2].turn.angle) >= NARROW_TURN_ANGLE) - { - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), - DirectionModifier::SlightLeft}; - intersection[starting_at + 1].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 1]), - DirectionModifier::Left}; - intersection[starting_at + 2].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 2]), - DirectionModifier::SharpLeft}; - } - else if (intersection[starting_at].entry_allowed && - intersection[starting_at + 1].entry_allowed && - intersection[starting_at + 2].entry_allowed && - ((first_direction == second_direction && second_direction == third_direction) || - (third_direction == second_direction && - angularDeviation(intersection[starting_at].turn.angle, - intersection[starting_at + 1].turn.angle) < GROUP_ANGLE) || - (second_direction == first_direction && - angularDeviation(intersection[starting_at + 1].turn.angle, - intersection[starting_at + 2].turn.angle) < GROUP_ANGLE))) - { - intersection[starting_at].turn.instruction = { - detail::isRampClass(intersection[starting_at].turn.eid, node_based_graph) - ? FirstRamp - : FirstTurn, - second_direction}; - intersection[starting_at + 1].turn.instruction = { - detail::isRampClass(intersection[starting_at + 1].turn.eid, node_based_graph) - ? SecondRamp - : SecondTurn, - second_direction}; - intersection[starting_at + 2].turn.instruction = { - detail::isRampClass(intersection[starting_at + 2].turn.eid, node_based_graph) - ? ThirdRamp - : ThirdTurn, - second_direction}; - } - else if (intersection[starting_at].entry_allowed && - intersection[starting_at + 1].entry_allowed && - intersection[starting_at + 2].entry_allowed && - ((third_direction == second_direction && - angularDeviation(intersection[starting_at].turn.angle, - intersection[starting_at + 1].turn.angle) >= GROUP_ANGLE) || - (second_direction == first_direction && - angularDeviation(intersection[starting_at + 1].turn.angle, - intersection[starting_at + 2].turn.angle) >= GROUP_ANGLE))) - { - // conflict one side with an additional very sharp turn - if (angularDeviation(intersection[starting_at + 1].turn.angle, - intersection[starting_at + 2].turn.angle) >= GROUP_ANGLE) - { - handleDistinctConflict(via_edge, intersection[starting_at + 1], - intersection[starting_at]); - intersection[starting_at + 2].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 2]), third_direction}; - } - else - { - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; - handleDistinctConflict(via_edge, intersection[starting_at + 2], - intersection[starting_at + 1]); - } - } - - else if ((first_direction == second_direction && - intersection[starting_at].entry_allowed != - intersection[starting_at + 1].entry_allowed) || - (second_direction == third_direction && - intersection[starting_at + 1].entry_allowed != - intersection[starting_at + 2].entry_allowed)) - { - // no conflict, due to conflict being restricted to valid/invalid - if (intersection[starting_at].entry_allowed) - intersection[starting_at].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; - if (intersection[starting_at + 1].entry_allowed) - intersection[starting_at + 1].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 1]), second_direction}; - if (intersection[starting_at + 2].entry_allowed) - intersection[starting_at + 2].turn.instruction = { - findBasicTurnType(via_edge, intersection[starting_at + 2]), third_direction}; - } - else - { - auto coord = localizer(node_based_graph.GetTarget(via_edge)); - util::SimpleLogger().Write(logWARNING) - << "Reached fallback for left turns, size 3: " << std::setprecision(12) - << toFloating(coord.lat) << " " << toFloating(coord.lon); - for (const auto road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - util::SimpleLogger().Write(logWARNING) - << "\troad: " << road.toString() << " Name: " << out_data.name_id - << " Road Class: " << (int)out_data.road_classification.road_class - << " At: " << localizer(node_based_graph.GetTarget(road.turn.eid)); - } - - for (std::size_t i = starting_at; i < intersection.size(); ++i) - if (intersection[i].entry_allowed) - intersection[i].turn.instruction = { - findBasicTurnType(via_edge, intersection[i]), - getTurnDirection(intersection[i].turn.angle)}; - } - } - else if (intersection.size() - starting_at == 4) - { - if (intersection[starting_at].entry_allowed) - intersection[starting_at].turn.instruction = { - detail::isRampClass(intersection[starting_at].turn.eid, node_based_graph) - ? FirstRamp - : FirstTurn, - DirectionModifier::Left}; - if (intersection[starting_at + 1].entry_allowed) - intersection[starting_at + 1].turn.instruction = { - detail::isRampClass(intersection[starting_at + 1].turn.eid, node_based_graph) - ? SecondRamp - : SecondTurn, - DirectionModifier::Left}; - if (intersection[starting_at + 2].entry_allowed) - intersection[starting_at + 2].turn.instruction = { - detail::isRampClass(intersection[starting_at + 2].turn.eid, node_based_graph) - ? ThirdRamp - : ThirdTurn, - DirectionModifier::Left}; - if (intersection[starting_at + 3].entry_allowed) - intersection[starting_at + 3].turn.instruction = { - detail::isRampClass(intersection[starting_at + 3].turn.eid, node_based_graph) - ? FourthRamp - : FourthTurn, - DirectionModifier::Left}; - } - else - { - for (auto &road : intersection) - { - if (!road.entry_allowed) - continue; - road.turn.instruction = {detail::isRampClass(road.turn.eid, node_based_graph) ? Ramp - : Turn, - getTurnDirection(road.turn.angle)}; - } - /* - auto coord = localizer(node_based_graph.GetTarget(via_edge)); - util::SimpleLogger().Write(logWARNING) - << "Reached fallback for left turns (" << starting_at << ") " << std::setprecision(12) - << toFloating(coord.lat) << " " << toFloating(coord.lon); - for (const auto road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - util::SimpleLogger().Write(logWARNING) - << "\troad: " << road.toString() << " Name: " << out_data.name_id - << " Road Class: " << (int)out_data.road_classification.road_class - << " At: " << localizer(node_based_graph.GetTarget(road.turn.eid)); - } - */ - } - return intersection; -} - -// can only assign three turns -std::vector TurnAnalysis::assignRightTurns(const EdgeID via_edge, - std::vector intersection, - const std::size_t up_to) const -{ - BOOST_ASSERT(up_to <= intersection.size()); - const auto count_valid = [&intersection, up_to]() { - std::size_t count = 0; - for (std::size_t i = 1; i < up_to; ++i) - if (intersection[i].entry_allowed) - ++count; - return count; - }; - if (up_to <= 1 || count_valid() == 0) - return intersection; - // handle single turn - if (up_to == 2) - { - if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) > NARROW_TURN_ANGLE && - angularDeviation(intersection[1].turn.angle, 0) > NARROW_TURN_ANGLE) - { - // assign left turn - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - DirectionModifier::Right}; - } - else if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) <= NARROW_TURN_ANGLE) - { - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - DirectionModifier::SlightRight}; - } - else - { - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - DirectionModifier::SharpRight}; - } - } - else if (up_to == 3) - { - const auto first_direction = getTurnDirection(intersection[1].turn.angle); - const auto second_direction = getTurnDirection(intersection[2].turn.angle); - if (first_direction == second_direction) - { - // conflict - handleDistinctConflict(via_edge, intersection[2], intersection[1]); - } - else - { - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - first_direction}; - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - second_direction}; - } - } - else if (up_to == 4) - { - const auto first_direction = getTurnDirection(intersection[1].turn.angle); - const auto second_direction = getTurnDirection(intersection[2].turn.angle); - const auto third_direction = getTurnDirection(intersection[3].turn.angle); - if (first_direction != second_direction && second_direction != third_direction) - { - if (intersection[1].entry_allowed) - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - first_direction}; - if (intersection[2].entry_allowed) - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - second_direction}; - if (intersection[3].entry_allowed) - intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), - third_direction}; - } - else if (2 >= (intersection[1].entry_allowed + intersection[2].entry_allowed + - intersection[3].entry_allowed)) - { - // at least a single invalid - if (!intersection[3].entry_allowed) - { - handleDistinctConflict(via_edge, intersection[2], intersection[1]); - } - else if (!intersection[1].entry_allowed) - { - handleDistinctConflict(via_edge, intersection[3], intersection[2]); - } - else // handles one-valid as well as two valid (1,3) - { - handleDistinctConflict(via_edge, intersection[3], intersection[1]); - } - } - - else if (intersection[1].entry_allowed && intersection[2].entry_allowed && - intersection[3].entry_allowed && - angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) >= - NARROW_TURN_ANGLE && - angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) >= - NARROW_TURN_ANGLE) - { - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - DirectionModifier::SharpRight}; - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - DirectionModifier::Right}; - intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), - DirectionModifier::SlightRight}; - } - else if (intersection[1].entry_allowed && intersection[2].entry_allowed && - intersection[3].entry_allowed && - ((first_direction == second_direction && second_direction == third_direction) || - (first_direction == second_direction && - angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) < - GROUP_ANGLE) || - (second_direction == third_direction && - angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) < - GROUP_ANGLE))) - { - intersection[1].turn.instruction = { - detail::isRampClass(intersection[1].turn.eid, node_based_graph) ? ThirdRamp - : ThirdTurn, - second_direction}; - intersection[2].turn.instruction = { - detail::isRampClass(intersection[2].turn.eid, node_based_graph) ? SecondRamp - : SecondTurn, - second_direction}; - intersection[3].turn.instruction = { - detail::isRampClass(intersection[3].turn.eid, node_based_graph) ? FirstRamp - : FirstTurn, - second_direction}; - } - else if (intersection[1].entry_allowed && intersection[2].entry_allowed && - intersection[3].entry_allowed && - ((first_direction == second_direction && - angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) >= - GROUP_ANGLE) || - (second_direction == third_direction && - angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) >= - GROUP_ANGLE))) - { - if (angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) >= - GROUP_ANGLE) - { - handleDistinctConflict(via_edge, intersection[2], intersection[1]); - intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), - third_direction}; - } - else - { - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - first_direction}; - handleDistinctConflict(via_edge, intersection[3], intersection[2]); - } - } - else if ((first_direction == second_direction && - intersection[1].entry_allowed != intersection[2].entry_allowed) || - (second_direction == third_direction && - intersection[2].entry_allowed != intersection[3].entry_allowed)) - { - if (intersection[1].entry_allowed) - intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), - first_direction}; - if (intersection[2].entry_allowed) - intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), - second_direction}; - if (intersection[3].entry_allowed) - intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), - third_direction}; - } - else - { - auto coord = localizer(node_based_graph.GetTarget(via_edge)); - util::SimpleLogger().Write(logWARNING) - << "Reached fallback for right turns, size 3: " << std::setprecision(12) - << toFloating(coord.lat) << " " << toFloating(coord.lon) - << " Valids: " << (intersection[1].entry_allowed + intersection[2].entry_allowed + - intersection[3].entry_allowed); - for (const auto road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - util::SimpleLogger().Write(logWARNING) - << "\troad: " << road.toString() << " Name: " << out_data.name_id - << " Road Class: " << (int)out_data.road_classification.road_class - << " At: " << localizer(node_based_graph.GetTarget(road.turn.eid)); - } - - for (std::size_t i = 1; i < up_to; ++i) - if (intersection[i].entry_allowed) - intersection[i].turn.instruction = { - findBasicTurnType(via_edge, intersection[i]), - getTurnDirection(intersection[i].turn.angle)}; - } - } - else if (up_to == 5) - { - if (intersection[4].entry_allowed) - intersection[4].turn.instruction = { - detail::isRampClass(intersection[4].turn.eid, node_based_graph) ? FirstRamp - : FirstTurn, - DirectionModifier::Right}; - if (intersection[3].entry_allowed) - intersection[3].turn.instruction = { - detail::isRampClass(intersection[3].turn.eid, node_based_graph) ? SecondRamp - : SecondTurn, - DirectionModifier::Right}; - if (intersection[2].entry_allowed) - intersection[2].turn.instruction = { - detail::isRampClass(intersection[2].turn.eid, node_based_graph) ? ThirdRamp - : ThirdTurn, - DirectionModifier::Right}; - if (intersection[1].entry_allowed) - intersection[1].turn.instruction = { - detail::isRampClass(intersection[1].turn.eid, node_based_graph) ? FourthRamp - : FourthTurn, - DirectionModifier::Right}; - } - else - { - for (std::size_t i = 1; i < up_to; ++i) - { - auto &road = intersection[i]; - if (!road.entry_allowed) - continue; - road.turn.instruction = {detail::isRampClass(road.turn.eid, node_based_graph) ? Ramp - : Turn, - getTurnDirection(road.turn.angle)}; - } - - /* - auto coord = localizer(node_based_graph.GetTarget(via_edge)); - util::SimpleLogger().Write(logWARNING) - << "Reached fallback for right turns (" << up_to << ") " << std::setprecision(12) - << toFloating(coord.lat) << " " << toFloating(coord.lon); - for (const auto road : intersection) - { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - util::SimpleLogger().Write(logWARNING) - << "\troad: " << road.toString() << " Name: " << out_data.name_id - << " Road Class: " << (int)out_data.road_classification.road_class - << " At: " << localizer(node_based_graph.GetTarget(road.turn.eid)); - } - */ + road.turn.instruction = {TurnType::Turn, (from_nid == to_nid) + ? DirectionModifier::UTurn + : getTurnDirection(road.turn.angle)}; } return intersection; } diff --git a/src/extractor/guidance/turn_handler.cpp b/src/extractor/guidance/turn_handler.cpp new file mode 100644 index 000000000..57a29d681 --- /dev/null +++ b/src/extractor/guidance/turn_handler.cpp @@ -0,0 +1,1171 @@ +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/toolkit.hpp" +#include "extractor/guidance/turn_handler.hpp" + +#include "util/simple_logger.hpp" + +#include +#include + +#include + +using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData; + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace detail +{ +inline FunctionalRoadClass roadClass(const ConnectedRoad &road, + const util::NodeBasedDynamicGraph &graph) +{ + return graph.GetEdgeData(road.turn.eid).road_classification.road_class; +} + +inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph) +{ + return isRampClass(node_based_graph.GetEdgeData(eid).road_classification.road_class); +} +} + +TurnHandler::TurnHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_info_list, + const util::NameTable &name_table) + : IntersectionHandler(node_based_graph, node_info_list, name_table) +{ +} + +TurnHandler::~TurnHandler() {} + +bool TurnHandler::canProcess(const NodeID, const EdgeID, const Intersection &) const +{ + return true; +} + +Intersection TurnHandler:: +operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const +{ + if (intersection.size() == 1) + return handleOneWayTurn(std::move(intersection)); + + if (intersection[0].entry_allowed) + { + intersection[0].turn.instruction = {findBasicTurnType(via_eid, intersection[0]), + DirectionModifier::UTurn}; + } + + if (intersection.size() == 2) + return handleTwoWayTurn(via_eid, std::move(intersection)); + + if (intersection.size() == 3) + return handleThreeWayTurn(via_eid, std::move(intersection)); + + return handleComplexTurn(via_eid, std::move(intersection)); +} + +Intersection TurnHandler::handleOneWayTurn(Intersection intersection) const +{ + BOOST_ASSERT(intersection[0].turn.angle < 0.001); + return intersection; +} + +Intersection TurnHandler::handleTwoWayTurn(const EdgeID via_edge, Intersection intersection) const +{ + BOOST_ASSERT(intersection[0].turn.angle < 0.001); + intersection[1].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); + + if (intersection[1].turn.instruction.type == TurnType::Suppressed) + intersection[1].turn.instruction.type = TurnType::NoTurn; + + return intersection; +} + +Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection intersection) const +{ + BOOST_ASSERT(intersection[0].turn.angle < 0.001); + const auto isObviousOfTwo = [this](const ConnectedRoad road, const ConnectedRoad other) { + const auto first_class = + node_based_graph.GetEdgeData(road.turn.eid).road_classification.road_class; + const bool is_ramp = isRampClass(first_class); + const auto second_class = + node_based_graph.GetEdgeData(other.turn.eid).road_classification.road_class; + const bool is_narrow_turn = + angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE; + const bool other_turn_is_at_least_orthogonal = + angularDeviation(other.turn.angle, STRAIGHT_ANGLE) > 85; + const bool turn_is_perfectly_straight = angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < + std::numeric_limits::epsilon(); + const bool is_obvious_by_road_class = + (!is_ramp && (2 * getPriority(first_class) < getPriority(second_class))) || + (!isLowPriorityRoadClass(first_class) && isLowPriorityRoadClass(second_class)); + const bool is_much_narrower_than_other = + angularDeviation(other.turn.angle, STRAIGHT_ANGLE) / + angularDeviation(road.turn.angle, STRAIGHT_ANGLE) > + INCREASES_BY_FOURTY_PERCENT; + return (is_narrow_turn && other_turn_is_at_least_orthogonal) || + turn_is_perfectly_straight || is_much_narrower_than_other || + is_obvious_by_road_class; + }; + + /* Two nearly straight turns -> FORK + OOOOOOO + / + IIIIII + \ + OOOOOOO + */ + if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE && + angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE) + { + if (intersection[1].entry_allowed && intersection[2].entry_allowed) + { + const auto left_class = node_based_graph.GetEdgeData(intersection[2].turn.eid) + .road_classification.road_class; + const auto right_class = node_based_graph.GetEdgeData(intersection[1].turn.eid) + .road_classification.road_class; + if (canBeSeenAsFork(left_class, right_class)) + { + assignFork(via_edge, intersection[2], intersection[1]); + } + else if (isObviousOfTwo(intersection[1], intersection[2])) + { + intersection[1].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + DirectionModifier::SlightLeft}; + } + else if (isObviousOfTwo(intersection[2], intersection[1])) + { + intersection[2].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]); + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + DirectionModifier::SlightRight}; + } + else + { + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + DirectionModifier::SlightRight}; + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + DirectionModifier::SlightLeft}; + } + } + else + { + if (intersection[1].entry_allowed) + intersection[1].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); + if (intersection[2].entry_allowed) + intersection[2].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]); + } + } + /* T Intersection + + OOOOOOO T OOOOOOOO + I + I + I + */ + else if (angularDeviation(intersection[1].turn.angle, 90) < NARROW_TURN_ANGLE && + angularDeviation(intersection[2].turn.angle, 270) < NARROW_TURN_ANGLE && + angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) > + NARROW_TURN_ANGLE) + { + if (intersection[1].entry_allowed) + { + if (TurnType::Ramp != findBasicTurnType(via_edge, intersection[1])) + intersection[1].turn.instruction = {TurnType::EndOfRoad, DirectionModifier::Right}; + else + intersection[1].turn.instruction = {TurnType::Ramp, DirectionModifier::Right}; + } + if (intersection[2].entry_allowed) + { + if (TurnType::Ramp != findBasicTurnType(via_edge, intersection[2])) + + intersection[2].turn.instruction = {TurnType::EndOfRoad, DirectionModifier::Left}; + else + intersection[2].turn.instruction = {TurnType::Ramp, DirectionModifier::Left}; + } + } + /* T Intersection, Cross left + O + O + O + IIIIIIII - OOOOOOOOOO + */ + else if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE && + angularDeviation(intersection[2].turn.angle, 270) < NARROW_TURN_ANGLE && + angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) > + NARROW_TURN_ANGLE) + { + if (intersection[1].entry_allowed) + { + if (TurnType::Ramp != findBasicTurnType(via_edge, intersection[1])) + intersection[1].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); + else + intersection[1].turn.instruction = {TurnType::Ramp, DirectionModifier::Straight}; + } + if (intersection[2].entry_allowed) + { + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + DirectionModifier::Left}; + } + } + /* T Intersection, Cross right + + IIIIIIII T OOOOOOOOOO + O + O + O + */ + else if (angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE && + angularDeviation(intersection[1].turn.angle, 90) < NARROW_TURN_ANGLE && + angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) > + NARROW_TURN_ANGLE) + { + if (intersection[2].entry_allowed) + intersection[2].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, intersection[2]); + if (intersection[1].entry_allowed) + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + DirectionModifier::Right}; + } + // merge onto a through street + else if (INVALID_NAME_ID != node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id && + node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id == + node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id) + { + const auto findTurn = [isObviousOfTwo](const ConnectedRoad turn, + const ConnectedRoad other) -> TurnInstruction { + if (isObviousOfTwo(turn, other)) + { + return {TurnType::Merge, turn.turn.angle < STRAIGHT_ANGLE + ? DirectionModifier::SlightLeft + : DirectionModifier::SlightRight}; + } + else + { + return {TurnType::Turn, getTurnDirection(turn.turn.angle)}; + } + }; + intersection[1].turn.instruction = findTurn(intersection[1], intersection[2]); + intersection[2].turn.instruction = findTurn(intersection[2], intersection[1]); + } + // other street merges from the left + else if (INVALID_NAME_ID != node_based_graph.GetEdgeData(via_edge).name_id && + node_based_graph.GetEdgeData(via_edge).name_id == + node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id) + { + if (isObviousOfTwo(intersection[1], intersection[2])) + { + intersection[1].turn.instruction = + TurnInstruction::SUPPRESSED(DirectionModifier::Straight); + } + else + { + intersection[1].turn.instruction = {TurnType::Continue, + getTurnDirection(intersection[1].turn.angle)}; + } + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + getTurnDirection(intersection[2].turn.angle)}; + } + // other street merges from the right + else if (INVALID_NAME_ID != node_based_graph.GetEdgeData(via_edge).name_id && + node_based_graph.GetEdgeData(via_edge).name_id == + node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id) + { + if (isObviousOfTwo(intersection[2], intersection[1])) + { + intersection[2].turn.instruction = + TurnInstruction::SUPPRESSED(DirectionModifier::Straight); + } + else + { + intersection[2].turn.instruction = {TurnType::Continue, + getTurnDirection(intersection[2].turn.angle)}; + } + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + getTurnDirection(intersection[1].turn.angle)}; + } + else + { + if (isObviousOfTwo(intersection[1], intersection[2])) + { + intersection[1].turn.instruction = + getInstructionForObvious(3, via_edge, false, intersection[1]); + } + else + { + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + getTurnDirection(intersection[1].turn.angle)}; + } + + if (isObviousOfTwo(intersection[2], intersection[1])) + { + intersection[2].turn.instruction = + getInstructionForObvious(3, via_edge, false, intersection[2]); + } + else + { + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + getTurnDirection(intersection[2].turn.angle)}; + } + } + return intersection; +} + +Intersection TurnHandler::handleComplexTurn(const EdgeID via_edge, Intersection intersection) const +{ + static int fallback_count = 0; + const std::size_t obvious_index = findObviousTurn(via_edge, intersection); + const auto fork_range = findFork(intersection); + std::size_t straightmost_turn = 0; + double straightmost_deviation = 180; + for (std::size_t i = 0; i < intersection.size(); ++i) + { + const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE); + if (deviation < straightmost_deviation) + { + straightmost_deviation = deviation; + straightmost_turn = i; + } + } + + // check whether the obvious choice is actually a through street + if (obvious_index != 0) + { + intersection[obvious_index].turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, isThroughStreet(obvious_index,intersection), + intersection[obvious_index]); + + // assign left/right turns + intersection = assignLeftTurns(via_edge, std::move(intersection), obvious_index + 1); + intersection = assignRightTurns(via_edge, std::move(intersection), obvious_index); + } + else if (fork_range.first != 0 && fork_range.second - fork_range.first <= 2) // found fork + { + if (fork_range.second - fork_range.first == 1) + { + auto &left = intersection[fork_range.second]; + auto &right = intersection[fork_range.first]; + const auto left_class = + node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class; + const auto right_class = + node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class; + if (canBeSeenAsFork(left_class, right_class)) + assignFork(via_edge, left, right); + else if (getPriority(left_class) > getPriority(right_class)) + { + right.turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, right); + left.turn.instruction = {findBasicTurnType(via_edge, left), + DirectionModifier::SlightLeft}; + } + else + { + left.turn.instruction = + getInstructionForObvious(intersection.size(), via_edge, false, left); + right.turn.instruction = {findBasicTurnType(via_edge, right), + DirectionModifier::SlightRight}; + } + } + else if (fork_range.second - fork_range.second == 2) + { + assignFork(via_edge, intersection[fork_range.second], + intersection[fork_range.first + 1], intersection[fork_range.first]); + } + // assign left/right turns + intersection = assignLeftTurns(via_edge, std::move(intersection), fork_range.second + 1); + intersection = assignRightTurns(via_edge, std::move(intersection), fork_range.first); + } + else if (straightmost_deviation < FUZZY_ANGLE_DIFFERENCE && + !intersection[straightmost_turn].entry_allowed) + { + // invalid straight turn + intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_turn + 1); + intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_turn); + } + // no straight turn + else if (intersection[straightmost_turn].turn.angle > 180) + { + // at most three turns on either side + intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_turn); + intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_turn); + } + else if (intersection[straightmost_turn].turn.angle < 180) + { + intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_turn + 1); + intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_turn + 1); + } + else + { + if (fallback_count++ < 10) + { + util::SimpleLogger().Write(logWARNING) + << "Resolved to keep fallback on complex turn assignment" + << "Straightmost: " << straightmost_turn; + ; + for (const auto &road : intersection) + { + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + util::SimpleLogger().Write(logWARNING) + << "road: " << toString(road) << " Name: " << out_data.name_id + << " Road Class: " << (int)out_data.road_classification.road_class; + } + } + } + return intersection; +} + +std::size_t TurnHandler::findObviousTurn(const EdgeID via_edge, + const Intersection &intersection) const +{ + // no obvious road + if (intersection.size() == 1) + return 0; + + // a single non u-turn is obvious + if (intersection.size() == 2) + return 1; + + // at least three roads + std::size_t best = 0; + double best_deviation = 180; + + std::size_t best_continue = 0; + double best_continue_deviation = 180; + + const EdgeData &in_data = node_based_graph.GetEdgeData(via_edge); + for (std::size_t i = 1; i < intersection.size(); ++i) + { + const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE); + if (intersection[i].entry_allowed && deviation < best_deviation) + { + best_deviation = deviation; + best = i; + } + + const auto out_data = node_based_graph.GetEdgeData(intersection[i].turn.eid); + if (intersection[i].entry_allowed && out_data.name_id == in_data.name_id && + deviation < best_continue_deviation) + { + best_continue_deviation = deviation; + best_continue = i; + } + } + + if (best == 0) + return 0; + + if (best_deviation >= 2 * NARROW_TURN_ANGLE) + return 0; + + // TODO incorporate road class in decision + if (best != 0 && best_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) + { + return best; + } + + // has no obvious continued road + if (best_continue == 0 || true) + { + // Find left/right deviation + const double left_deviation = angularDeviation( + intersection[(best + 1) % intersection.size()].turn.angle, STRAIGHT_ANGLE); + const double right_deviation = + angularDeviation(intersection[best - 1].turn.angle, STRAIGHT_ANGLE); + + if (best_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION && + std::min(left_deviation, right_deviation) > FUZZY_ANGLE_DIFFERENCE) + return best; + + // other narrow turns? + if (angularDeviation(intersection[best - 1].turn.angle, STRAIGHT_ANGLE) <= + FUZZY_ANGLE_DIFFERENCE) + return 0; + if (angularDeviation(intersection[(best + 1) % intersection.size()].turn.angle, + STRAIGHT_ANGLE) <= FUZZY_ANGLE_DIFFERENCE) + return 0; + + // Well distinct turn that is nearly straight + if (left_deviation / best_deviation >= DISTINCTION_RATIO && + right_deviation / best_deviation >= DISTINCTION_RATIO) + { + return best; + } + } + + return 0; // no obvious turn +} + +// Can only assign three turns +Intersection TurnHandler::assignLeftTurns(const EdgeID via_edge, + Intersection intersection, + const std::size_t starting_at) const +{ + const auto count_valid = [&intersection, starting_at]() { + std::size_t count = 0; + for (std::size_t i = starting_at; i < intersection.size(); ++i) + if (intersection[i].entry_allowed) + ++count; + return count; + }; + if (starting_at == intersection.size() || count_valid() == 0) + return intersection; + // handle single turn + if (intersection.size() - starting_at == 1) + { + if (!intersection[starting_at].entry_allowed) + return intersection; + + if (angularDeviation(intersection[starting_at].turn.angle, STRAIGHT_ANGLE) > + NARROW_TURN_ANGLE && + angularDeviation(intersection[starting_at].turn.angle, 0) > NARROW_TURN_ANGLE) + { + // assign left turn + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), DirectionModifier::Left}; + } + else if (angularDeviation(intersection[starting_at].turn.angle, STRAIGHT_ANGLE) <= + NARROW_TURN_ANGLE) + { + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), + DirectionModifier::SlightLeft}; + } + else + { + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), + DirectionModifier::SharpLeft}; + } + } + // two turns on at the side + else if (intersection.size() - starting_at == 2) + { + const auto first_direction = getTurnDirection(intersection[starting_at].turn.angle); + const auto second_direction = getTurnDirection(intersection[starting_at + 1].turn.angle); + if (first_direction == second_direction) + { + // conflict + handleDistinctConflict(via_edge, intersection[starting_at + 1], + intersection[starting_at]); + } + else + { + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; + intersection[starting_at + 1].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 1]), second_direction}; + } + } + else if (intersection.size() - starting_at == 3) + { + const auto first_direction = getTurnDirection(intersection[starting_at].turn.angle); + const auto second_direction = getTurnDirection(intersection[starting_at + 1].turn.angle); + const auto third_direction = getTurnDirection(intersection[starting_at + 2].turn.angle); + if (first_direction != second_direction && second_direction != third_direction) + { + // implies first != third, based on the angles and clockwise order + if (intersection[starting_at].entry_allowed) + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; + if (intersection[starting_at + 1].entry_allowed) + intersection[starting_at + 1].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 1]), second_direction}; + if (intersection[starting_at + 2].entry_allowed) + intersection[starting_at + 2].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 2]), third_direction}; + } + else if (2 >= (intersection[starting_at].entry_allowed + + intersection[starting_at + 1].entry_allowed + + intersection[starting_at + 2].entry_allowed)) + { + + // at least one invalid turn + if (!intersection[starting_at].entry_allowed) + { + handleDistinctConflict(via_edge, intersection[starting_at + 2], + intersection[starting_at + 1]); + } + else if (!intersection[starting_at + 1].entry_allowed) + { + handleDistinctConflict(via_edge, intersection[starting_at + 2], + intersection[starting_at]); + } + else + { + handleDistinctConflict(via_edge, intersection[starting_at + 1], + intersection[starting_at]); + } + } + else if (intersection[starting_at].entry_allowed && + intersection[starting_at + 1].entry_allowed && + intersection[starting_at + 2].entry_allowed && + angularDeviation(intersection[starting_at].turn.angle, + intersection[starting_at + 1].turn.angle) >= NARROW_TURN_ANGLE && + angularDeviation(intersection[starting_at + 1].turn.angle, + intersection[starting_at + 2].turn.angle) >= NARROW_TURN_ANGLE) + { + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), + DirectionModifier::SlightLeft}; + intersection[starting_at + 1].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 1]), + DirectionModifier::Left}; + intersection[starting_at + 2].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 2]), + DirectionModifier::SharpLeft}; + } + else if (intersection[starting_at].entry_allowed && + intersection[starting_at + 1].entry_allowed && + intersection[starting_at + 2].entry_allowed && + ((first_direction == second_direction && second_direction == third_direction) || + (third_direction == second_direction && + angularDeviation(intersection[starting_at].turn.angle, + intersection[starting_at + 1].turn.angle) < GROUP_ANGLE) || + (second_direction == first_direction && + angularDeviation(intersection[starting_at + 1].turn.angle, + intersection[starting_at + 2].turn.angle) < GROUP_ANGLE))) + { + intersection[starting_at].turn.instruction = { + detail::isRampClass(intersection[starting_at].turn.eid, node_based_graph) + ? FirstRamp + : FirstTurn, + second_direction}; + intersection[starting_at + 1].turn.instruction = { + detail::isRampClass(intersection[starting_at + 1].turn.eid, node_based_graph) + ? SecondRamp + : SecondTurn, + second_direction}; + intersection[starting_at + 2].turn.instruction = { + detail::isRampClass(intersection[starting_at + 2].turn.eid, node_based_graph) + ? ThirdRamp + : ThirdTurn, + second_direction}; + } + else if (intersection[starting_at].entry_allowed && + intersection[starting_at + 1].entry_allowed && + intersection[starting_at + 2].entry_allowed && + ((third_direction == second_direction && + angularDeviation(intersection[starting_at].turn.angle, + intersection[starting_at + 1].turn.angle) >= GROUP_ANGLE) || + (second_direction == first_direction && + angularDeviation(intersection[starting_at + 1].turn.angle, + intersection[starting_at + 2].turn.angle) >= GROUP_ANGLE))) + { + // conflict one side with an additional very sharp turn + if (angularDeviation(intersection[starting_at + 1].turn.angle, + intersection[starting_at + 2].turn.angle) >= GROUP_ANGLE) + { + handleDistinctConflict(via_edge, intersection[starting_at + 1], + intersection[starting_at]); + intersection[starting_at + 2].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 2]), third_direction}; + } + else + { + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; + handleDistinctConflict(via_edge, intersection[starting_at + 2], + intersection[starting_at + 1]); + } + } + else if ((first_direction == second_direction && + intersection[starting_at].entry_allowed != + intersection[starting_at + 1].entry_allowed) || + (second_direction == third_direction && + intersection[starting_at + 1].entry_allowed != + intersection[starting_at + 2].entry_allowed)) + { + // no conflict, due to conflict being restricted to valid/invalid + if (intersection[starting_at].entry_allowed) + intersection[starting_at].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at]), first_direction}; + if (intersection[starting_at + 1].entry_allowed) + intersection[starting_at + 1].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 1]), second_direction}; + if (intersection[starting_at + 2].entry_allowed) + intersection[starting_at + 2].turn.instruction = { + findBasicTurnType(via_edge, intersection[starting_at + 2]), third_direction}; + } + else + { + util::SimpleLogger().Write(logWARNING) << "Reached fallback for left turns, size 3"; + for (const auto road : intersection) + { + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + util::SimpleLogger().Write(logWARNING) + << "\troad: " << toString(road) << " Name: " << out_data.name_id + << " Road Class: " << (int)out_data.road_classification.road_class; + } + + for (std::size_t i = starting_at; i < intersection.size(); ++i) + if (intersection[i].entry_allowed) + intersection[i].turn.instruction = { + findBasicTurnType(via_edge, intersection[i]), + getTurnDirection(intersection[i].turn.angle)}; + } + } + else if (intersection.size() - starting_at == 4) + { + if (intersection[starting_at].entry_allowed) + intersection[starting_at].turn.instruction = { + detail::isRampClass(intersection[starting_at].turn.eid, node_based_graph) + ? FirstRamp + : FirstTurn, + DirectionModifier::Left}; + if (intersection[starting_at + 1].entry_allowed) + intersection[starting_at + 1].turn.instruction = { + detail::isRampClass(intersection[starting_at + 1].turn.eid, node_based_graph) + ? SecondRamp + : SecondTurn, + DirectionModifier::Left}; + if (intersection[starting_at + 2].entry_allowed) + intersection[starting_at + 2].turn.instruction = { + detail::isRampClass(intersection[starting_at + 2].turn.eid, node_based_graph) + ? ThirdRamp + : ThirdTurn, + DirectionModifier::Left}; + if (intersection[starting_at + 3].entry_allowed) + intersection[starting_at + 3].turn.instruction = { + detail::isRampClass(intersection[starting_at + 3].turn.eid, node_based_graph) + ? FourthRamp + : FourthTurn, + DirectionModifier::Left}; + } + else + { + for (auto &road : intersection) + { + if (!road.entry_allowed) + continue; + road.turn.instruction = {detail::isRampClass(road.turn.eid, node_based_graph) ? Ramp + : Turn, + getTurnDirection(road.turn.angle)}; + } + } + return intersection; +} + +// can only assign three turns +Intersection TurnHandler::assignRightTurns(const EdgeID via_edge, + Intersection intersection, + const std::size_t up_to) const +{ + BOOST_ASSERT(up_to <= intersection.size()); + const auto count_valid = [&intersection, up_to]() { + std::size_t count = 0; + for (std::size_t i = 1; i < up_to; ++i) + if (intersection[i].entry_allowed) + ++count; + return count; + }; + if (up_to <= 1 || count_valid() == 0) + return intersection; + // handle single turn + if (up_to == 2) + { + if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) > NARROW_TURN_ANGLE && + angularDeviation(intersection[1].turn.angle, 0) > NARROW_TURN_ANGLE) + { + // assign left turn + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + DirectionModifier::Right}; + } + else if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) <= NARROW_TURN_ANGLE) + { + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + DirectionModifier::SlightRight}; + } + else + { + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + DirectionModifier::SharpRight}; + } + } + else if (up_to == 3) + { + const auto first_direction = getTurnDirection(intersection[1].turn.angle); + const auto second_direction = getTurnDirection(intersection[2].turn.angle); + if (first_direction == second_direction) + { + // conflict + handleDistinctConflict(via_edge, intersection[2], intersection[1]); + } + else + { + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + first_direction}; + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + second_direction}; + } + } + else if (up_to == 4) + { + const auto first_direction = getTurnDirection(intersection[1].turn.angle); + const auto second_direction = getTurnDirection(intersection[2].turn.angle); + const auto third_direction = getTurnDirection(intersection[3].turn.angle); + if (first_direction != second_direction && second_direction != third_direction) + { + if (intersection[1].entry_allowed) + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + first_direction}; + if (intersection[2].entry_allowed) + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + second_direction}; + if (intersection[3].entry_allowed) + intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), + third_direction}; + } + else if (2 >= (intersection[1].entry_allowed + intersection[2].entry_allowed + + intersection[3].entry_allowed)) + { + // at least a single invalid + if (!intersection[3].entry_allowed) + { + handleDistinctConflict(via_edge, intersection[2], intersection[1]); + } + else if (!intersection[1].entry_allowed) + { + handleDistinctConflict(via_edge, intersection[3], intersection[2]); + } + else // handles one-valid as well as two valid (1,3) + { + handleDistinctConflict(via_edge, intersection[3], intersection[1]); + } + } + else if (intersection[1].entry_allowed && intersection[2].entry_allowed && + intersection[3].entry_allowed && + angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) >= + NARROW_TURN_ANGLE && + angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) >= + NARROW_TURN_ANGLE) + { + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + DirectionModifier::SharpRight}; + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + DirectionModifier::Right}; + intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), + DirectionModifier::SlightRight}; + } + else if (intersection[1].entry_allowed && intersection[2].entry_allowed && + intersection[3].entry_allowed && + ((first_direction == second_direction && second_direction == third_direction) || + (first_direction == second_direction && + angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) < + GROUP_ANGLE) || + (second_direction == third_direction && + angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) < + GROUP_ANGLE))) + { + intersection[1].turn.instruction = { + detail::isRampClass(intersection[1].turn.eid, node_based_graph) ? ThirdRamp + : ThirdTurn, + second_direction}; + intersection[2].turn.instruction = { + detail::isRampClass(intersection[2].turn.eid, node_based_graph) ? SecondRamp + : SecondTurn, + second_direction}; + intersection[3].turn.instruction = { + detail::isRampClass(intersection[3].turn.eid, node_based_graph) ? FirstRamp + : FirstTurn, + second_direction}; + } + else if (intersection[1].entry_allowed && intersection[2].entry_allowed && + intersection[3].entry_allowed && + ((first_direction == second_direction && + angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) >= + GROUP_ANGLE) || + (second_direction == third_direction && + angularDeviation(intersection[1].turn.angle, intersection[2].turn.angle) >= + GROUP_ANGLE))) + { + if (angularDeviation(intersection[2].turn.angle, intersection[3].turn.angle) >= + GROUP_ANGLE) + { + handleDistinctConflict(via_edge, intersection[2], intersection[1]); + intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), + third_direction}; + } + else + { + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + first_direction}; + handleDistinctConflict(via_edge, intersection[3], intersection[2]); + } + } + else if ((first_direction == second_direction && + intersection[1].entry_allowed != intersection[2].entry_allowed) || + (second_direction == third_direction && + intersection[2].entry_allowed != intersection[3].entry_allowed)) + { + if (intersection[1].entry_allowed) + intersection[1].turn.instruction = {findBasicTurnType(via_edge, intersection[1]), + first_direction}; + if (intersection[2].entry_allowed) + intersection[2].turn.instruction = {findBasicTurnType(via_edge, intersection[2]), + second_direction}; + if (intersection[3].entry_allowed) + intersection[3].turn.instruction = {findBasicTurnType(via_edge, intersection[3]), + third_direction}; + } + else + { + util::SimpleLogger().Write(logWARNING) + << "Reached fallback for right turns, size 3 " + << " Valids: " << (intersection[1].entry_allowed + intersection[2].entry_allowed + + intersection[3].entry_allowed); + for (const auto road : intersection) + { + const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); + util::SimpleLogger().Write(logWARNING) + << "\troad: " << toString(road) << " Name: " << out_data.name_id + << " Road Class: " << (int)out_data.road_classification.road_class; + } + + for (std::size_t i = 1; i < up_to; ++i) + if (intersection[i].entry_allowed) + intersection[i].turn.instruction = { + findBasicTurnType(via_edge, intersection[i]), + getTurnDirection(intersection[i].turn.angle)}; + } + } + else if (up_to == 5) + { + if (intersection[4].entry_allowed) + intersection[4].turn.instruction = { + detail::isRampClass(intersection[4].turn.eid, node_based_graph) ? FirstRamp + : FirstTurn, + DirectionModifier::Right}; + if (intersection[3].entry_allowed) + intersection[3].turn.instruction = { + detail::isRampClass(intersection[3].turn.eid, node_based_graph) ? SecondRamp + : SecondTurn, + DirectionModifier::Right}; + if (intersection[2].entry_allowed) + intersection[2].turn.instruction = { + detail::isRampClass(intersection[2].turn.eid, node_based_graph) ? ThirdRamp + : ThirdTurn, + DirectionModifier::Right}; + if (intersection[1].entry_allowed) + intersection[1].turn.instruction = { + detail::isRampClass(intersection[1].turn.eid, node_based_graph) ? FourthRamp + : FourthTurn, + DirectionModifier::Right}; + } + else + { + for (std::size_t i = 1; i < up_to; ++i) + { + auto &road = intersection[i]; + if (!road.entry_allowed) + continue; + road.turn.instruction = {detail::isRampClass(road.turn.eid, node_based_graph) ? Ramp + : Turn, + getTurnDirection(road.turn.angle)}; + } + } + return intersection; +} + +std::pair TurnHandler::findFork(const Intersection &intersection) const +{ + + std::size_t best = 0; + double best_deviation = 180; + + // TODO handle road classes + for (std::size_t i = 1; i < intersection.size(); ++i) + { + const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE); + if (intersection[i].entry_allowed && deviation < best_deviation) + { + best_deviation = deviation; + best = i; + } + } + if (best_deviation <= NARROW_TURN_ANGLE) + { + std::size_t left = best, right = best; + if (intersection[left].turn.angle >= 180) + { + // due to best > 1, we can safely decrement right + --right; + if (angularDeviation(intersection[right].turn.angle, STRAIGHT_ANGLE) > + NARROW_TURN_ANGLE) + return std::make_pair(std::size_t{0}, std::size_t{0}); + } + else + { + ++left; + if (left >= intersection.size() || + angularDeviation(intersection[left].turn.angle, STRAIGHT_ANGLE) > NARROW_TURN_ANGLE) + return std::make_pair(std::size_t{0}, std::size_t{0}); + } + while (left + 1 < intersection.size() && + angularDeviation(intersection[left].turn.angle, intersection[left + 1].turn.angle) < + NARROW_TURN_ANGLE) + ++left; + while (right > 1 && + angularDeviation(intersection[right].turn.angle, + intersection[right - 1].turn.angle) < NARROW_TURN_ANGLE) + --right; + + // TODO check whether 2*NARROW_TURN is too large + if (right < left && + angularDeviation(intersection[left].turn.angle, + intersection[(left + 1) % intersection.size()].turn.angle) >= + 2 * NARROW_TURN_ANGLE && + angularDeviation(intersection[right].turn.angle, intersection[right - 1].turn.angle) >= + 2 * NARROW_TURN_ANGLE) + return std::make_pair(right, left); + } + return std::make_pair(std::size_t{0}, std::size_t{0}); +} + +void TurnHandler::handleDistinctConflict(const EdgeID via_edge, + ConnectedRoad &left, + ConnectedRoad &right) const +{ + // single turn of both is valid (don't change the valid one) + // or multiple identical angles -> bad OSM intersection + if ((!left.entry_allowed || !right.entry_allowed) || (left.turn.angle == right.turn.angle)) + { + if (left.entry_allowed) + left.turn.instruction = {findBasicTurnType(via_edge, left), + getTurnDirection(left.turn.angle)}; + if (right.entry_allowed) + right.turn.instruction = {findBasicTurnType(via_edge, right), + getTurnDirection(right.turn.angle)}; + return; + } + + if (getTurnDirection(left.turn.angle) == DirectionModifier::Straight || + getTurnDirection(left.turn.angle) == DirectionModifier::SlightLeft || + getTurnDirection(right.turn.angle) == DirectionModifier::SlightRight) + { + const auto left_class = + node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class; + const auto right_class = + node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class; + if (canBeSeenAsFork(left_class, right_class)) + assignFork(via_edge, left, right); + else if (getPriority(left_class) > getPriority(right_class)) + { + // FIXME this should possibly know about the actual roads? + // here we don't know about the intersection size. To be on the save side, + // we declare it + // as complex (at least size 4) + right.turn.instruction = getInstructionForObvious(4, via_edge, false, right); + left.turn.instruction = {findBasicTurnType(via_edge, left), + DirectionModifier::SlightLeft}; + } + else + { + // FIXME this should possibly know about the actual roads? + // here we don't know about the intersection size. To be on the save side, + // we declare it + // as complex (at least size 4) + left.turn.instruction = getInstructionForObvious(4, via_edge, false, left); + right.turn.instruction = {findBasicTurnType(via_edge, right), + DirectionModifier::SlightRight}; + } + } + const auto left_type = findBasicTurnType(via_edge, left); + const auto right_type = findBasicTurnType(via_edge, right); + // Two Right Turns + if (angularDeviation(left.turn.angle, 90) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) + { + // Keep left perfect, shift right + left.turn.instruction = {left_type, DirectionModifier::Right}; + right.turn.instruction = {right_type, DirectionModifier::SharpRight}; + return; + } + if (angularDeviation(right.turn.angle, 90) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) + { + // Keep Right perfect, shift left + left.turn.instruction = {left_type, DirectionModifier::SlightRight}; + right.turn.instruction = {right_type, DirectionModifier::Right}; + return; + } + // Two Right Turns + if (angularDeviation(left.turn.angle, 270) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) + { + // Keep left perfect, shift right + left.turn.instruction = {left_type, DirectionModifier::Left}; + right.turn.instruction = {right_type, DirectionModifier::SlightLeft}; + return; + } + if (angularDeviation(right.turn.angle, 270) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) + { + // Keep Right perfect, shift left + left.turn.instruction = {left_type, DirectionModifier::SharpLeft}; + right.turn.instruction = {right_type, DirectionModifier::Left}; + return; + } + // Both turns? + if (TurnType::Ramp != left_type && TurnType::Ramp != right_type) + { + if (left.turn.angle < STRAIGHT_ANGLE) + { + left.turn.instruction = {TurnType::FirstTurn, getTurnDirection(left.turn.angle)}; + right.turn.instruction = {TurnType::SecondTurn, getTurnDirection(right.turn.angle)}; + } + else + { + left.turn.instruction = {TurnType::SecondTurn, getTurnDirection(left.turn.angle)}; + right.turn.instruction = {TurnType::FirstTurn, getTurnDirection(right.turn.angle)}; + } + return; + } + // Shift the lesser penalty + if (getTurnDirection(left.turn.angle) == DirectionModifier::SharpLeft) + { + left.turn.instruction = {left_type, DirectionModifier::SharpLeft}; + right.turn.instruction = {right_type, DirectionModifier::Left}; + return; + } + if (getTurnDirection(right.turn.angle) == DirectionModifier::SharpRight) + { + left.turn.instruction = {left_type, DirectionModifier::Right}; + right.turn.instruction = {right_type, DirectionModifier::SharpRight}; + return; + } + + if (getTurnDirection(left.turn.angle) == DirectionModifier::Right) + { + if (angularDeviation(left.turn.angle, 90) > angularDeviation(right.turn.angle, 90)) + { + left.turn.instruction = {left_type, DirectionModifier::SlightRight}; + right.turn.instruction = {right_type, DirectionModifier::Right}; + } + else + { + left.turn.instruction = {left_type, DirectionModifier::Right}; + right.turn.instruction = {right_type, DirectionModifier::SharpRight}; + } + } + else + { + if (angularDeviation(left.turn.angle, 270) > angularDeviation(right.turn.angle, 270)) + { + left.turn.instruction = {left_type, DirectionModifier::SharpLeft}; + right.turn.instruction = {right_type, DirectionModifier::Left}; + } + else + { + left.turn.instruction = {left_type, DirectionModifier::Left}; + right.turn.instruction = {right_type, DirectionModifier::SlightLeft}; + } + } +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm