From 1544a08ea2aa23af19ed210ab538d851bd20753a Mon Sep 17 00:00:00 2001 From: Moritz Kobitzsch Date: Mon, 18 Apr 2016 13:41:19 +0200 Subject: [PATCH] introduce roundabout-turns into instruction set --- CHANGELOG.md | 8 + features/guidance/roundabout-turn.feature | 369 ++++++++++++++++++ features/support/route.js | 2 + include/engine/guidance/toolkit.hpp | 13 +- include/extractor/guidance/constants.hpp | 1 + .../extractor/guidance/roundabout_handler.hpp | 14 +- .../extractor/guidance/roundabout_type.hpp | 21 + include/extractor/guidance/toolkit.hpp | 33 +- .../extractor/guidance/turn_instruction.hpp | 82 ++-- include/util/guidance/toolkit.hpp | 48 +++ src/engine/api/json_factory.cpp | 8 +- src/engine/guidance/post_processing.cpp | 130 ++++-- .../guidance/intersection_generator.cpp | 3 +- .../guidance/intersection_handler.cpp | 2 + .../intersection_scenario_three_way.cpp | 4 + src/extractor/guidance/motorway_handler.cpp | 4 + src/extractor/guidance/roundabout_handler.cpp | 182 +++++++-- src/extractor/guidance/turn_analysis.cpp | 5 +- src/extractor/guidance/turn_handler.cpp | 3 + 19 files changed, 791 insertions(+), 141 deletions(-) create mode 100644 features/guidance/roundabout-turn.feature create mode 100644 include/extractor/guidance/roundabout_type.hpp create mode 100644 include/util/guidance/toolkit.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index b324d1575..37da17d4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 5.1.0 + - API: + - added roundabout-turn instruction. The instruction indicates a small roundabout that is treated as an intersection + (turn right at the roundabout for first exit, go straight at the roundabout...) + + - Infrastructure + - BREAKING: reordered internal instruction types. This breaks the data format + # 5.0.0 Changes with regard 5.0.0 RC2: - API: diff --git a/features/guidance/roundabout-turn.feature b/features/guidance/roundabout-turn.feature new file mode 100644 index 000000000..8ee7b2d2f --- /dev/null +++ b/features/guidance/roundabout-turn.feature @@ -0,0 +1,369 @@ +@routing @guidance +Feature: Basic Roundabout + + Background: + Given the profile "car" + Given a grid size of 3 meters + + Scenario: Enter and Exit + Given the node map + | | | a | | | + | | | b | | | + | h | g | | c | d | + | | | e | | | + | | | f | | | + + And the ways + | nodes | junction | + | ab | | + | cd | | + | ef | | + | gh | | + | bgecb | roundabout | + + When I route I should get + | waypoints | route | turns | + | a,d | ab,cd,cd | depart,roundabout_turn left exit-3,arrive | + | a,f | ab,ef,ef | depart,roundabout_turn straight exit-2,arrive | + | a,h | ab,gh,gh | depart,roundabout_turn right exit-1,arrive | + | d,f | cd,ef,ef | depart,roundabout_turn left exit-3,arrive | + | d,h | cd,gh,gh | depart,roundabout_turn straight exit-2,arrive | + | d,a | cd,ab,ab | depart,roundabout_turn right exit-1,arrive | + | f,h | ef,gh,gh | depart,roundabout_turn left exit-3,arrive | + | f,a | ef,ab,ab | depart,roundabout_turn straight exit-2,arrive | + | f,d | ef,cd,cd | depart,roundabout_turn right exit-1,arrive | + | h,a | gh,ab,ab | depart,roundabout_turn left exit-3,arrive | + | h,d | gh,cd,cd | depart,roundabout_turn straight exit-2,arrive | + | h,f | gh,ef,ef | depart,roundabout_turn right exit-1,arrive | + + Scenario: Enter and Exit - Rotated + Given the node map + | a | | | d | + | | b | c | | + | | g | e | | + | h | | | f | + + And the ways + | nodes | junction | + | ab | | + | cd | | + | ef | | + | gh | | + | bgecb | roundabout | + + When I route I should get + | waypoints | route | turns | + | a,d | ab,cd,cd | depart,roundabout_turn left exit-3,arrive | + | a,f | ab,ef,ef | depart,roundabout_turn straight exit-2,arrive | + | a,h | ab,gh,gh | depart,roundabout_turn right exit-1,arrive | + | d,f | cd,ef,ef | depart,roundabout_turn left exit-3,arrive | + | d,h | cd,gh,gh | depart,roundabout_turn straight exit-2,arrive | + | d,a | cd,ab,ab | depart,roundabout_turn right exit-1,arrive | + | f,h | ef,gh,gh | depart,roundabout_turn left exit-3,arrive | + | f,a | ef,ab,ab | depart,roundabout_turn straight exit-2,arrive | + | f,d | ef,cd,cd | depart,roundabout_turn right exit-1,arrive | + | h,a | gh,ab,ab | depart,roundabout_turn left exit-3,arrive | + | h,d | gh,cd,cd | depart,roundabout_turn straight exit-2,arrive | + | h,f | gh,ef,ef | depart,roundabout_turn right exit-1,arrive | + + Scenario: Only Enter + Given the node map + | | | a | | | + | | | b | | | + | d | c | | g | h | + | | | e | | | + | | | f | | | + + And the ways + | nodes | junction | + | ab | | + | cd | | + | ef | | + | gh | | + | bcegb | roundabout | + + When I route I should get + | waypoints | route | turns | + | a,c | ab,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | a,e | ab,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | a,g | ab,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | d,e | cd,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | d,g | cd,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | d,b | cd,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | f,g | ef,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | f,b | ef,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | f,c | ef,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | h,b | gh,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | h,c | gh,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + | h,e | gh,bcegb,bcegb | depart,roundabout-exit-undefined,arrive | + + Scenario: Only Exit + Given the node map + | | | a | | | + | | | b | | | + | d | c | | g | h | + | | | e | | | + | | | f | | | + + And the ways + | nodes | junction | + | ab | | + | cd | | + | ef | | + | gh | | + | bcegb | roundabout | + + When I route I should get + | waypoints | route | turns | + | b,d | bcegb,cd,cd | depart,roundabout-exit-1,arrive | + | b,f | bcegb,ef,ef | depart,roundabout-exit-2,arrive | + | b,h | bcegb,gh,gh | depart,roundabout-exit-3,arrive | + | c,f | bcegb,ef,ef | depart,roundabout-exit-1,arrive | + | c,h | bcegb,gh,gh | depart,roundabout-exit-2,arrive | + | c,a | bcegb,ab,ab | depart,roundabout-exit-3,arrive | + | e,h | bcegb,gh,gh | depart,roundabout-exit-1,arrive | + | e,a | bcegb,ab,ab | depart,roundabout-exit-2,arrive | + | e,d | bcegb,cd,cd | depart,roundabout-exit-3,arrive | + | g,a | bcegb,ab,ab | depart,roundabout-exit-1,arrive | + | g,d | bcegb,cd,cd | depart,roundabout-exit-2,arrive | + | g,f | bcegb,ef,ef | depart,roundabout-exit-3,arrive | + #phantom node snapping can result in a full round-trip here, therefore we cannot test b->a and the other direct exits + + Scenario: Drive Around + Given the node map + | | | a | | | + | | | b | | | + | d | c | | g | h | + | | | e | | | + | | | f | | | + + And the ways + | nodes | junction | + | ab | | + | cd | | + | ef | | + | gh | | + | bcegb | roundabout | + + When I route I should get + | waypoints | route | turns | + | b,c | bcegb,bcegb | depart,arrive | + | b,e | bcegb,bcegb | depart,arrive | + | b,g | bcegb,bcegb | depart,arrive | + | c,e | bcegb,bcegb | depart,arrive | + | c,g | bcegb,bcegb | depart,arrive | + | c,b | bcegb,bcegb | depart,arrive | + | e,g | bcegb,bcegb | depart,arrive | + | e,b | bcegb,bcegb | depart,arrive | + | e,c | bcegb,bcegb | depart,arrive | + | g,b | bcegb,bcegb | depart,arrive | + | g,c | bcegb,bcegb | depart,arrive | + | g,e | bcegb,bcegb | depart,arrive | + + Scenario: Mixed Entry and Exit - Not an Intersection + Given the node map + | | c | | a | | + | j | | b | | f | + | | k | | e | | + | l | | h | | d | + | | g | | i | | + + And the ways + | nodes | junction | oneway | + | abc | | yes | + | def | | yes | + | ghi | | yes | + | jkl | | yes | + | bkheb | roundabout | yes | + + When I route I should get + | waypoints | route | turns | + | a,c | abc,abc,abc | depart,roundabout-exit-1,arrive | + | a,l | abc,jkl,jkl | depart,roundabout-exit-2,arrive | + | a,i | abc,ghi,ghi | depart,roundabout-exit-3,arrive | + | a,f | abc,def,def | depart,roundabout-exit-4,arrive | + | d,f | def,def,def | depart,roundabout-exit-1,arrive | + | d,c | def,abc,abc | depart,roundabout-exit-2,arrive | + | d,l | def,jkl,jkl | depart,roundabout-exit-3,arrive | + | d,i | def,ghi,ghi | depart,roundabout-exit-4,arrive | + | g,i | ghi,ghi,ghi | depart,roundabout-exit-1,arrive | + | g,f | ghi,def,def | depart,roundabout-exit-2,arrive | + | g,c | ghi,abc,abc | depart,roundabout-exit-3,arrive | + | g,l | ghi,jkl,jkl | depart,roundabout-exit-4,arrive | + | j,l | jkl,jkl,jkl | depart,roundabout-exit-1,arrive | + | j,i | jkl,ghi,ghi | depart,roundabout-exit-2,arrive | + | j,f | jkl,def,def | depart,roundabout-exit-3,arrive | + | j,c | jkl,abc,abc | depart,roundabout-exit-4,arrive | + + Scenario: Segregated roads - Not an intersection + Given the node map + | | a | | c | | + | l | | b | | d | + | | k | | e | | + | j | | h | | f | + | | i | | g | | + + And the ways + | nodes | junction | oneway | + | abc | | yes | + | def | | yes | + | ghi | | yes | + | jkl | | yes | + | bkheb | roundabout | yes | + + When I route I should get + | waypoints | route | turns | + | a,c | abc,abc,abc | depart,roundabout-exit-4,arrive | + | a,l | abc,jkl,jkl | depart,roundabout-exit-1,arrive | + | a,i | abc,ghi,ghi | depart,roundabout-exit-2,arrive | + | a,f | abc,def,def | depart,roundabout-exit-3,arrive | + | d,f | def,def,def | depart,roundabout-exit-4,arrive | + | d,c | def,abc,abc | depart,roundabout-exit-1,arrive | + | d,l | def,jkl,jkl | depart,roundabout-exit-2,arrive | + | d,i | def,ghi,ghi | depart,roundabout-exit-3,arrive | + | g,i | ghi,ghi,ghi | depart,roundabout-exit-4,arrive | + | g,f | ghi,def,def | depart,roundabout-exit-1,arrive | + | g,c | ghi,abc,abc | depart,roundabout-exit-2,arrive | + | g,l | ghi,jkl,jkl | depart,roundabout-exit-3,arrive | + | j,l | jkl,jkl,jkl | depart,roundabout-exit-4,arrive | + | j,i | jkl,ghi,ghi | depart,roundabout-exit-1,arrive | + | j,f | jkl,def,def | depart,roundabout-exit-2,arrive | + | j,c | jkl,abc,abc | depart,roundabout-exit-3,arrive | + + Scenario: Collinear in X + Given the node map + | a | b | c | d | f | + | | | e | | | + + And the ways + | nodes | junction | + | ab | | + | bcdb | roundabout | + | ce | | + | df | | + + When I route I should get + | waypoints | route | turns | + | a,e | ab,ce,ce | depart,roundabout-exit-1,arrive | + | a,f | ab,df,df | depart,roundabout-exit-2,arrive | + + Scenario: Collinear in X,Y + Given the node map + | a | | | + | b | | | + | c | d | f | + | e | | | + + And the ways + | nodes | junction | + | ab | | + | bcdb | roundabout | + | ce | | + | df | | + + When I route I should get + | waypoints | route | turns | + | a,e | ab,ce,ce | depart,roundabout_turn straight exit-1,arrive | + | a,f | ab,df,df | depart,roundabout_turn left exit-2,arrive | + + Scenario: Collinear in X,Y + Given the node map + | a | | | + | d | | | + | b | c | f | + | e | | | + + And the ways + | nodes | junction | + | ad | | + | bcdb | roundabout | + | be | | + | cf | | + + When I route I should get + | waypoints | route | turns | + | a,e | ad,be,be | depart,roundabout_turn straight exit-1,arrive | + | a,f | ad,cf,cf | depart,roundabout_turn left exit-2,arrive | + + Scenario: Collinear in X,Y + Given the node map + | a | | | + | c | | | + | d | b | f | + | e | | | + + And the ways + | nodes | junction | + | ac | | + | bcdb | roundabout | + | de | | + | bf | | + + When I route I should get + | waypoints | route | turns | + | a,e | ac,de,de | depart,roundabout_turn straight exit-1,arrive | + | a,f | ac,bf,bf | depart,roundabout_turn left exit-2,arrive | + + Scenario: Enter and Exit -- too complex + Given the node map + | j | | a | | | + | | i | b | | | + | | g | | c | d | + | h | | e | | | + | | | f | | | + + And the ways + | nodes | junction | + | ab | | + | ij | | + | cd | | + | ef | | + | gh | | + | bigecb | roundabout | + + When I route I should get + | waypoints | route | turns | + | a,d | ab,cd,cd | depart,roundabout-exit-4,arrive | + | a,f | ab,ef,ef | depart,roundabout-exit-3,arrive | + | a,h | ab,gh,gh | depart,roundabout-exit-2,arrive | + | d,f | cd,ef,ef | depart,roundabout-exit-4,arrive | + | d,h | cd,gh,gh | depart,roundabout-exit-3,arrive | + | d,a | cd,ab,ab | depart,roundabout-exit-1,arrive | + | f,h | ef,gh,gh | depart,roundabout-exit-4,arrive | + | f,a | ef,ab,ab | depart,roundabout-exit-2,arrive | + | f,d | ef,cd,cd | depart,roundabout-exit-1,arrive | + | h,a | gh,ab,ab | depart,roundabout-exit-3,arrive | + | h,d | gh,cd,cd | depart,roundabout-exit-2,arrive | + | h,f | gh,ef,ef | depart,roundabout-exit-1,arrive | + + Scenario: Enter and Exit -- Non-Distinct + Given the node map + | | | a | | | + | | | b | | | + | | g | | c | d | + | | | e | | | + | h | | f | | | + + And the ways + | nodes | junction | + | ab | | + | cd | | + | ef | | + | gh | | + | bgecb | roundabout | + + When I route I should get + | waypoints | route | turns | + | a,d | ab,cd,cd | depart,roundabout-exit-3,arrive | + | a,f | ab,ef,ef | depart,roundabout-exit-2,arrive | + | a,h | ab,gh,gh | depart,roundabout-exit-1,arrive | + | d,f | cd,ef,ef | depart,roundabout-exit-3,arrive | + | d,h | cd,gh,gh | depart,roundabout-exit-2,arrive | + | d,a | cd,ab,ab | depart,roundabout-exit-1,arrive | + | f,h | ef,gh,gh | depart,roundabout-exit-3,arrive | + | f,a | ef,ab,ab | depart,roundabout-exit-2,arrive | + | f,d | ef,cd,cd | depart,roundabout-exit-1,arrive | + | h,a | gh,ab,ab | depart,roundabout-exit-3,arrive | + | h,d | gh,cd,cd | depart,roundabout-exit-2,arrive | + | h,f | gh,ef,ef | depart,roundabout-exit-1,arrive | + diff --git a/features/support/route.js b/features/support/route.js index 1dbce8cb9..43bf0d35e 100644 --- a/features/support/route.js +++ b/features/support/route.js @@ -149,6 +149,8 @@ module.exports = function () { return v.rotary_name + '-exit-' + v.maneuver.exit; else return 'rotary-exit-' + v.maneuver.exit; + case 'roundabout_turn': + return v.maneuver.type + ' ' + v.maneuver.modifier + ' exit-' + v.maneuver.exit; // FIXME this is a little bit over-simplistic for merge/fork instructions default: return v.maneuver.type + ' ' + v.maneuver.modifier; diff --git a/include/engine/guidance/toolkit.hpp b/include/engine/guidance/toolkit.hpp index f2fc3b4ac..43ca72ec6 100644 --- a/include/engine/guidance/toolkit.hpp +++ b/include/engine/guidance/toolkit.hpp @@ -1,5 +1,5 @@ -#ifndef OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_ -#define OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_ +#ifndef OSRM_ENGINE_GUIDANCE_TOOLKIT_HPP_ +#define OSRM_ENGINE_GUIDANCE_TOOLKIT_HPP_ #include "extractor/guidance/turn_instruction.hpp" #include "util/bearing.hpp" @@ -25,9 +25,12 @@ inline bool entersRoundabout(const extractor::guidance::TurnInstruction instruct { return (instruction.type == extractor::guidance::TurnType::EnterRoundabout || instruction.type == extractor::guidance::TurnType::EnterRotary || + instruction.type == extractor::guidance::TurnType::EnterRoundaboutIntersection || instruction.type == extractor::guidance::TurnType::EnterRoundaboutAtExit || instruction.type == extractor::guidance::TurnType::EnterRotaryAtExit || + instruction.type == extractor::guidance::TurnType::EnterRoundaboutIntersectionAtExit || instruction.type == extractor::guidance::TurnType::EnterAndExitRoundabout || + instruction.type == extractor::guidance::TurnType::EnterAndExitRotary || instruction.type == extractor::guidance::TurnType::EnterAndExitRotary); } @@ -35,8 +38,10 @@ inline bool leavesRoundabout(const extractor::guidance::TurnInstruction instruct { return (instruction.type == extractor::guidance::TurnType::ExitRoundabout || instruction.type == extractor::guidance::TurnType::ExitRotary || + instruction.type == extractor::guidance::TurnType::ExitRoundaboutIntersection || instruction.type == extractor::guidance::TurnType::EnterAndExitRoundabout || - instruction.type == extractor::guidance::TurnType::EnterAndExitRotary); + instruction.type == extractor::guidance::TurnType::EnterAndExitRotary || + instruction.type == extractor::guidance::TurnType::EnterAndExitRoundaboutIntersection); } inline bool staysOnRoundabout(const extractor::guidance::TurnInstruction instruction) @@ -68,4 +73,4 @@ inline double angularDeviation(const double angle, const double from) } // namespace engine } // namespace osrm -#endif /* OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_ */ +#endif /* OSRM_ENGINE_GUIDANCE_TOOLKIT_HPP_ */ diff --git a/include/extractor/guidance/constants.hpp b/include/extractor/guidance/constants.hpp index e27b96008..3e4ae6ded 100644 --- a/include/extractor/guidance/constants.hpp +++ b/include/extractor/guidance/constants.hpp @@ -22,6 +22,7 @@ const double constexpr FUZZY_ANGLE_DIFFERENCE = 20.; const double constexpr DISTINCTION_RATIO = 2; const unsigned constexpr INVALID_NAME_ID = 0; +const double constexpr MAX_ROUNDABOUT_INTERSECTION_RADIUS = 5; const double constexpr MAX_ROUNDABOUT_RADIUS = 15; // 30 m diameter as final distinction const double constexpr INCREASES_BY_FOURTY_PERCENT = 1.4; diff --git a/include/extractor/guidance/roundabout_handler.hpp b/include/extractor/guidance/roundabout_handler.hpp index f6d727bdc..cd6ae5a5e 100644 --- a/include/extractor/guidance/roundabout_handler.hpp +++ b/include/extractor/guidance/roundabout_handler.hpp @@ -1,14 +1,17 @@ #ifndef OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_ #define OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_ +#include "extractor/compressed_edge_container.hpp" #include "extractor/guidance/intersection.hpp" #include "extractor/guidance/intersection_handler.hpp" +#include "extractor/guidance/roundabout_type.hpp" #include "extractor/query_node.hpp" #include "util/name_table.hpp" #include "util/node_based_graph.hpp" #include "util/typedefs.hpp" +#include #include #include @@ -37,7 +40,8 @@ class RoundaboutHandler : public IntersectionHandler public: RoundaboutHandler(const util::NodeBasedDynamicGraph &node_based_graph, const std::vector &node_info_list, - const util::NameTable &name_table); + const util::NameTable &name_table, + const CompressedEdgeContainer &compressed_edge_container); ~RoundaboutHandler() override final; @@ -61,17 +65,21 @@ class RoundaboutHandler : public IntersectionHandler Intersection &intersection) const; // decide whether we lookk at a roundabout or a rotary - bool isRotary(const NodeID nid) const; + RoundaboutType getRoundaboutType(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, + Intersection handleRoundabouts(const RoundaboutType roundabout_type, const EdgeID via_edge, const bool on_roundabout, const bool can_exit_roundabout, Intersection intersection) const; + + bool qualifiesAsRoundaboutIntersection(const std::set &roundabout_nodes) const; + + const CompressedEdgeContainer &compressed_edge_container; }; } // namespace guidance diff --git a/include/extractor/guidance/roundabout_type.hpp b/include/extractor/guidance/roundabout_type.hpp new file mode 100644 index 000000000..1cbb22deb --- /dev/null +++ b/include/extractor/guidance/roundabout_type.hpp @@ -0,0 +1,21 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_TYPES_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_TYPES_HPP_ + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +enum class RoundaboutType +{ + None, // not a roundabout + Roundabout, // standard roundabout + Rotary, // traffic circle (large roundabout) with dedicated name + RoundaboutIntersection // small roundabout with distinct turns, handled as intersection +}; +} /* namespace guidance */ +} /* namespace extractor */ +} /* namespace osrm */ + +#endif /* OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_TYPES_HPP_ */ diff --git a/include/extractor/guidance/toolkit.hpp b/include/extractor/guidance/toolkit.hpp index 0576638d2..299ac4db4 100644 --- a/include/extractor/guidance/toolkit.hpp +++ b/include/extractor/guidance/toolkit.hpp @@ -4,6 +4,7 @@ #include "util/bearing.hpp" #include "util/coordinate.hpp" #include "util/coordinate_calculation.hpp" +#include "util/guidance/toolkit.hpp" #include "extractor/compressed_edge_container.hpp" #include "extractor/query_node.hpp" @@ -26,6 +27,8 @@ namespace extractor namespace guidance { +using util::guidance::angularDeviation; + namespace detail { const constexpr double DESIRED_SEGMENT_LENGTH = 10.0; @@ -246,12 +249,6 @@ inline double angleFromDiscreteAngle(const DiscreteAngle angle) return static_cast(angle) * detail::discrete_angle_step_size; } -inline double angularDeviation(const double angle, const double from) -{ - const double deviation = std::abs(angle - from); - return std::min(360 - deviation, deviation); -} - inline double getAngularPenalty(const double angle, DirectionModifier modifier) { // these are not aligned with getTurnDirection but represent an ideal center @@ -272,30 +269,6 @@ inline double getTurnConfidence(const double angle, TurnInstruction instruction) return 1.0 - (difference / max_deviation) * (difference / max_deviation); } -// Translates between angles and their human-friendly directional representation -inline DirectionModifier getTurnDirection(const double angle) -{ - // An angle of zero is a u-turn - // 180 goes perfectly straight - // 0-180 are right turns - // 180-360 are left turns - if (angle > 0 && angle < 60) - return DirectionModifier::SharpRight; - if (angle >= 60 && angle < 140) - return DirectionModifier::Right; - if (angle >= 140 && angle < 170) - return DirectionModifier::SlightRight; - if (angle >= 165 && angle <= 195) - return DirectionModifier::Straight; - if (angle > 190 && angle <= 220) - return DirectionModifier::SlightLeft; - if (angle > 220 && angle <= 300) - return DirectionModifier::Left; - if (angle > 300 && angle < 360) - return DirectionModifier::SharpLeft; - return DirectionModifier::UTurn; -} - // swaps left <-> right modifier types inline DirectionModifier mirrorDirectionModifier(const DirectionModifier modifier) { diff --git a/include/extractor/guidance/turn_instruction.hpp b/include/extractor/guidance/turn_instruction.hpp index 3d6cfae11..8758f56ee 100644 --- a/include/extractor/guidance/turn_instruction.hpp +++ b/include/extractor/guidance/turn_instruction.hpp @@ -5,6 +5,8 @@ #include +#include "extractor/guidance/roundabout_type.hpp" + namespace osrm { namespace extractor @@ -36,26 +38,30 @@ enum DirectionModifier // enum class TurnType : unsigned char enum TurnType // at the moment we can support 32 turn types, without increasing memory consumption { - Invalid, // no valid turn instruction - NoTurn, // end of segment without turn/middle of a segment - Suppressed, // location that suppresses a turn - NewName, // no turn, but name changes - Continue, // remain on a street - Turn, // basic turn - Merge, // merge onto a street - Ramp, // special turn (highway ramp exits) - Fork, // fork road splitting up - EndOfRoad, // T intersection - EnterRoundabout, // Entering a small Roundabout - EnterRoundaboutAtExit, // Entering a small Roundabout at a countable exit - EnterAndExitRoundabout, // Touching a roundabout - ExitRoundabout, // Exiting a small Roundabout - EnterRotary, // Enter a rotary - EnterRotaryAtExit, // Enter A Rotary at a countable exit - EnterAndExitRotary, // Touching a rotary - ExitRotary, // Exit a rotary - StayOnRoundabout, // Continue on Either a small or a large Roundabout - Notification // Travel Mode Changes` + Invalid, // no valid turn instruction + NewName, // no turn, but name changes + Continue, // remain on a street + Turn, // basic turn + Merge, // merge onto a street + Ramp, // special turn (highway ramp exits) + Fork, // fork road splitting up + EndOfRoad, // T intersection + Notification, // Travel Mode Changes, Restrictions apply... + EnterRoundabout, // Entering a small Roundabout + EnterAndExitRoundabout, // Touching a roundabout + EnterRotary, // Enter a rotary + EnterAndExitRotary, // Touching a rotary + EnterRoundaboutIntersection, // Entering a small Roundabout + EnterAndExitRoundaboutIntersection, // Touching a roundabout + NoTurn, // end of segment without turn/middle of a segment + Suppressed, // location that suppresses a turn + EnterRoundaboutAtExit, // Entering a small Roundabout at a countable exit + ExitRoundabout, // Exiting a small Roundabout + EnterRotaryAtExit, // Enter A Rotary at a countable exit + ExitRotary, // Exit a rotary + EnterRoundaboutIntersectionAtExit, // Entering a small Roundabout at a countable exit + ExitRoundaboutIntersection, // Exiting a small Roundabout + StayOnRoundabout // Continue on Either a small or a large Roundabout }; // turn angle in 1.40625 degree -> 128 == 180 degree @@ -80,27 +86,45 @@ struct TurnInstruction return TurnInstruction(TurnType::NoTurn, DirectionModifier::UTurn); } - static TurnInstruction REMAIN_ROUNDABOUT(bool is_rotary, const DirectionModifier modifier) + static TurnInstruction REMAIN_ROUNDABOUT(const RoundaboutType, const DirectionModifier modifier) { - (void)is_rotary; // staying does not require a different instruction at the moment return TurnInstruction(TurnType::StayOnRoundabout, modifier); } - static TurnInstruction ENTER_ROUNDABOUT(bool is_rotary, const DirectionModifier modifier) + static TurnInstruction ENTER_ROUNDABOUT(const RoundaboutType roundabout_type, + const DirectionModifier modifier) { - return {is_rotary ? TurnType::EnterRotary : TurnType::EnterRoundabout, modifier}; + const constexpr TurnType enter_instruction[] = { + TurnType::Invalid, TurnType::EnterRoundabout, TurnType::EnterRotary, + TurnType::EnterRoundaboutIntersection}; + return {enter_instruction[static_cast(roundabout_type)], modifier}; } - static TurnInstruction EXIT_ROUNDABOUT(bool is_rotary, const DirectionModifier modifier) + static TurnInstruction EXIT_ROUNDABOUT(const RoundaboutType roundabout_type, + const DirectionModifier modifier) { - return {is_rotary ? TurnType::ExitRotary : TurnType::ExitRoundabout, modifier}; + const constexpr TurnType exit_instruction[] = {TurnType::Invalid, TurnType::ExitRoundabout, + TurnType::ExitRotary, + TurnType::ExitRoundaboutIntersection}; + return {exit_instruction[static_cast(roundabout_type)], modifier}; } - static TurnInstruction ENTER_AND_EXIT_ROUNDABOUT(bool is_rotary, + static TurnInstruction ENTER_AND_EXIT_ROUNDABOUT(const RoundaboutType roundabout_type, const DirectionModifier modifier) { - return {is_rotary ? TurnType::EnterAndExitRotary : TurnType::EnterAndExitRoundabout, - modifier}; + const constexpr TurnType exit_instruction[] = { + TurnType::Invalid, TurnType::EnterAndExitRoundabout, TurnType::EnterAndExitRotary, + TurnType::EnterAndExitRoundaboutIntersection}; + return {exit_instruction[static_cast(roundabout_type)], modifier}; + } + + static TurnInstruction ENTER_ROUNDABOUT_AT_EXIT(const RoundaboutType roundabout_type, + const DirectionModifier modifier) + { + const constexpr TurnType enter_instruction[] = { + TurnType::Invalid, TurnType::EnterRoundaboutAtExit, TurnType::EnterRotaryAtExit, + TurnType::EnterRoundaboutIntersectionAtExit}; + return {enter_instruction[static_cast(roundabout_type)], modifier}; } static TurnInstruction SUPPRESSED(const DirectionModifier modifier) diff --git a/include/util/guidance/toolkit.hpp b/include/util/guidance/toolkit.hpp new file mode 100644 index 000000000..24072a85b --- /dev/null +++ b/include/util/guidance/toolkit.hpp @@ -0,0 +1,48 @@ +#ifndef OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_ +#define OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_ + +/* A set of tools required for guidance in both pre and post-processing */ + +#include "extractor/guidance/turn_instruction.hpp" + +namespace osrm +{ +namespace util +{ +namespace guidance +{ + +inline double angularDeviation(const double angle, const double from) +{ + const double deviation = std::abs(angle - from); + return std::min(360 - deviation, deviation); +} + +inline extractor::guidance::DirectionModifier getTurnDirection(const double angle) +{ + // An angle of zero is a u-turn + // 180 goes perfectly straight + // 0-180 are right turns + // 180-360 are left turns + if (angle > 0 && angle < 60) + return extractor::guidance::DirectionModifier::SharpRight; + if (angle >= 60 && angle < 140) + return extractor::guidance::DirectionModifier::Right; + if (angle >= 140 && angle < 170) + return extractor::guidance::DirectionModifier::SlightRight; + if (angle >= 165 && angle <= 195) + return extractor::guidance::DirectionModifier::Straight; + if (angle > 190 && angle <= 220) + return extractor::guidance::DirectionModifier::SlightLeft; + if (angle > 220 && angle <= 300) + return extractor::guidance::DirectionModifier::Left; + if (angle > 300 && angle < 360) + return extractor::guidance::DirectionModifier::SharpLeft; + return extractor::guidance::DirectionModifier::UTurn; +} + +} /* namespace guidance */ +} /* namespace util */ +} /* namespace osrm */ + +#endif /* OSRM_UTIL_GUIDANCE_TOOLKIT_HPP_ */ diff --git a/src/engine/api/json_factory.cpp b/src/engine/api/json_factory.cpp index 1de85ec01..c34948609 100644 --- a/src/engine/api/json_factory.cpp +++ b/src/engine/api/json_factory.cpp @@ -34,9 +34,11 @@ const constexpr char *modifier_names[] = {"uturn", "sharp right", "right", "s // translations of TurnTypes. Not all types are exposed to the outside world. // invalid types should never be returned as part of the API const constexpr char *turn_type_names[] = { - "invalid", "no turn", "invalid", "new name", "continue", "turn", "merge", - "ramp", "fork", "end of road", "roundabout", "invalid", "roundabout", "invalid", - "rotary", "invalid", "rotary", "invalid", "invalid", "notification"}; + "invalid", "new name", "continue", "turn", "merge", "ramp", + "fork", "end of road", "notification", "roundabout", "roundabout", "rotary", + "rotary", "roundabout_turn", "roundabout_turn", "invalid", "invalid", "invalid", + "invalid", "invalid", "invalid", "invalid", "invalid", "invalid"}; + const constexpr char *waypoint_type_names[] = {"invalid", "arrive", "depart"}; // Check whether to include a modifier in the result of the API diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index 5be2c8942..2c42c510e 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -4,6 +4,8 @@ #include "engine/guidance/assemble_steps.hpp" #include "engine/guidance/toolkit.hpp" +#include "util/guidance/toolkit.hpp" + #include #include @@ -17,6 +19,8 @@ using TurnInstruction = osrm::extractor::guidance::TurnInstruction; using TurnType = osrm::extractor::guidance::TurnType; using DirectionModifier = osrm::extractor::guidance::DirectionModifier; +using osrm::util::guidance::angularDeviation; +using osrm::util::guidance::getTurnDirection; namespace osrm { @@ -43,7 +47,9 @@ void print(const std::vector &steps) for (const auto &intersection : step.maneuver.intersections) std::cout << "(" << intersection.duration << " " << intersection.distance << ")"; - std::cout << "] name[" << step.name_id << "]: " << step.name << std::endl; + std::cout << "] name[" << step.name_id << "]: " << step.name + << " Bearings: " << step.maneuver.bearing_before << " " + << step.maneuver.bearing_after << std::endl; } } @@ -79,17 +85,25 @@ void fixFinalRoundabout(std::vector &steps) --propagation_index) { auto &propagation_step = steps[propagation_index]; - if (propagation_index == 0 || entersRoundabout(propagation_step.maneuver.instruction)) + if (entersRoundabout(propagation_step.maneuver.instruction)) { propagation_step.maneuver.exit = 0; propagation_step.geometry_end = steps.back().geometry_begin; + // remember the current name as rotary name in tha case we end in a rotary if (propagation_step.maneuver.instruction.type == TurnType::EnterRotary || propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit) propagation_step.rotary_name = propagation_step.name; - break; + else if (propagation_step.maneuver.instruction.type == + TurnType::EnterRoundaboutIntersection || + propagation_step.maneuver.instruction.type == + TurnType::EnterRoundaboutIntersectionAtExit) + propagation_step.maneuver.instruction.type = TurnType::EnterRoundabout; + + return; } + // accumulate turn data into the enter instructions else if (propagation_step.maneuver.instruction.type == TurnType::StayOnRoundabout) { // TODO this operates on the data that is in the instructions. @@ -109,14 +123,17 @@ bool setUpRoundabout(RouteStep &step) // Special case handling, if an entry is directly tied to an exit const auto instruction = step.maneuver.instruction; if (instruction.type == TurnType::EnterRotaryAtExit || - instruction.type == TurnType::EnterRoundaboutAtExit) + instruction.type == TurnType::EnterRoundaboutAtExit || + instruction.type == TurnType::EnterRoundaboutIntersectionAtExit) { step.maneuver.exit = 1; // prevent futher special case handling of these two. if (instruction.type == TurnType::EnterRotaryAtExit) step.maneuver.instruction.type = TurnType::EnterRotary; - else + else if (instruction.type == TurnType::EnterRoundaboutAtExit) step.maneuver.instruction.type = TurnType::EnterRoundabout; + else + step.maneuver.instruction.type = TurnType::EnterRoundaboutIntersection; } if (leavesRoundabout(instruction)) @@ -126,8 +143,10 @@ bool setUpRoundabout(RouteStep &step) // prevent futher special case handling of these two. if (instruction.type == TurnType::EnterAndExitRotary) step.maneuver.instruction.type = TurnType::EnterRotary; - else + else if (instruction.type == TurnType::EnterAndExitRoundabout) step.maneuver.instruction.type = TurnType::EnterRoundabout; + else + step.maneuver.instruction.type = TurnType::EnterRoundaboutIntersection; return false; } else @@ -145,28 +164,35 @@ void closeOffRoundabout(const bool on_roundabout, if (!on_roundabout) { - // We reached a special case that requires the addition of a special route step in - // the beginning. - // We started in a roundabout, so to announce the exit, we move use the exit - // instruction and - // move it right to the beginning to make sure to immediately announce the exit. + // We reached a special case that requires the addition of a special route step in the + // beginning. We started in a roundabout, so to announce the exit, we move use the exit + // instruction and move it right to the beginning to make sure to immediately announce the + // exit. BOOST_ASSERT(leavesRoundabout(steps[1].maneuver.instruction) || steps[1].maneuver.instruction.type == TurnType::StayOnRoundabout); steps[0].geometry_end = 1; steps[1] = detail::forwardInto(steps[1], steps[0]); steps[0].duration = 0; steps[0].distance = 0; - steps[1].maneuver.instruction.type = step.maneuver.instruction.type == TurnType::ExitRotary - ? TurnType::EnterRotary - : TurnType::EnterRoundabout; + const auto exitToEnter = [](const TurnType type) { + if (TurnType::ExitRotary == type) + return TurnType::EnterRotary; + // if we do not enter the roundabout Intersection, we cannot treat the full traversal as + // a turn. So we switch it up to the roundabout type + else if (type == TurnType::ExitRoundaboutIntersection) + return TurnType::EnterRoundabout; + else + return TurnType::EnterRoundabout; + }; + steps[1].maneuver.instruction.type = exitToEnter(step.maneuver.instruction.type); if (steps[1].maneuver.instruction.type == TurnType::EnterRotary) steps[1].rotary_name = steps[0].name; } - // Normal exit from the roundabout, or exit from a previously fixed roundabout. - // Propagate the index back to the entering - // location and - // prepare the current silent set of instructions for removal. + // Normal exit from the roundabout, or exit from a previously fixed roundabout. Propagate the + // index back to the entering location and prepare the current silent set of instructions for + // removal. + const auto exit_bearing = steps[step_index].maneuver.bearing_after; if (step_index > 1) { // The very first route-step is head, so we cannot iterate past that one @@ -177,15 +203,55 @@ void closeOffRoundabout(const bool on_roundabout, propagation_step = detail::forwardInto(propagation_step, steps[propagation_index + 1]); if (entersRoundabout(propagation_step.maneuver.instruction)) { - // TODO at this point, we can remember the additional name for a rotary - // This requires some initial thought on the data format, though - propagation_step.maneuver.exit = step.maneuver.exit; propagation_step.geometry_end = step.geometry_end; // remember rotary name if (propagation_step.maneuver.instruction.type == TurnType::EnterRotary || propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit) + { propagation_step.rotary_name = propagation_step.name; + } + else if (propagation_step.maneuver.instruction.type == + TurnType::EnterRoundaboutIntersection || + propagation_step.maneuver.instruction.type == + TurnType::EnterRoundaboutIntersectionAtExit) + { + // Compute the angle between two bearings on a normal turn circle + // + // Bearings Angles + // + // 0 180 + // 315 45 225 135 + // + // 270 x 90 270 x 90 + // + // 225 135 315 45 + // 180 0 + // + // A turn from north to north-east offerst bearing 0 and 45 has to be translated + // into a turn of 135 degrees. The same holdes for 90 - 135 (east to south + // east). + // For north, the transformation works by angle = 540 (360 + 180) - exit_bearing + // % 360; + // All other cases are handled by first rotating both bearings to an + // entry_bearing of 0. + const double angle = [](const double entry_bearing, const double exit_bearing) { + const double offset = 360 - entry_bearing; + const double rotated_exit = [](double bearing, const double offset) { + bearing += offset; + return bearing > 360 ? bearing - 360 : bearing; + }(exit_bearing, offset); + + const auto angle = 540 - rotated_exit; + return angle > 360 ? angle - 360 : angle; + }(propagation_step.maneuver.bearing_before, exit_bearing); + + std::cout << "Step: " << propagation_step.maneuver.bearing_before << " " + << exit_bearing << " result: " << angle << std::endl; + + propagation_step.maneuver.instruction.direction_modifier = + ::osrm::util::guidance::getTurnDirection(angle); + } propagation_step.name = step.name; propagation_step.name_id = step.name_id; @@ -508,8 +574,8 @@ void trimShortSegments(std::vector &steps, LegGeometry &geometry) { // Doing this step in post-processing provides a few challenges we cannot overcome. // The removal of an initial step imposes some copy overhead in the steps, moving all later - // steps to the front. - // In addition, we cannot reduce the travel time that is accumulated at a different location. + // steps to the front. In addition, we cannot reduce the travel time that is accumulated at a + // different location. // As a direct implication, we have to keep the time of the initial/final turns (which adds a // few seconds of inaccuracy at both ends. This is acceptable, however, since the turn should // usually not be as relevant. @@ -517,14 +583,16 @@ void trimShortSegments(std::vector &steps, LegGeometry &geometry) if (steps.size() < 2 || geometry.locations.size() <= 2) return; - // if phantom node is located at the connection of two segments, either one can be selected as + // if phantom node is located at the connection of two segments, either one can be selected + // as // turn // // a --- b // | // c // - // If a route from b to c is requested, both a--b and b--c could be selected as start segment. + // If a route from b to c is requested, both a--b and b--c could be selected as start + // segment. // In case of a--b, we end up with an unwanted turn saying turn-right onto b-c. // These cases start off with an initial segment which is of zero length. // We have to be careful though, since routing that starts in a roundabout has a valid. @@ -558,12 +626,12 @@ void trimShortSegments(std::vector &steps, LegGeometry &geometry) const auto ¤t_depart = steps.front(); auto &designated_depart = *(steps.begin() + 1); - // FIXME this is required to be consistent with the route durations. The initial turn is - // not actually part of the route, though + // FIXME this is required to be consistent with the route durations. The initial + // turn is not actually part of the route, though designated_depart.duration += current_depart.duration; - // update initial turn direction/bearings. Due to the duplicated first coordinate, the - // initial bearing is invalid + // update initial turn direction/bearings. Due to the duplicated first coordinate, + // the initial bearing is invalid designated_depart.maneuver = detail::stepManeuverFromGeometry( TurnInstruction::NO_TURN(), WaypointType::Depart, geometry); @@ -595,8 +663,8 @@ void trimShortSegments(std::vector &steps, LegGeometry &geometry) BOOST_ASSERT(geometry.locations.size() >= steps.size()); auto &next_to_last_step = *(steps.end() - 2); - // in the end, the situation with the roundabout cannot occur. As a result, we can remove all - // zero-length instructions + // in the end, the situation with the roundabout cannot occur. As a result, we can remove + // all zero-length instructions if (next_to_last_step.distance <= 1) { geometry.locations.pop_back(); diff --git a/src/extractor/guidance/intersection_generator.cpp b/src/extractor/guidance/intersection_generator.cpp index 1314943ad..38259757c 100644 --- a/src/extractor/guidance/intersection_generator.cpp +++ b/src/extractor/guidance/intersection_generator.cpp @@ -205,7 +205,7 @@ Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersecti return result; } }; - if (intersection.size() == 1) + if (intersection.size() <= 1) return intersection; const bool is_connected_to_roundabout = [this,&intersection]() { @@ -245,7 +245,6 @@ Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersecti intersection.pop_back(); } - else if (mergable(0, 1)) { const double correction_factor = (intersection[1].turn.angle) / 2; diff --git a/src/extractor/guidance/intersection_handler.cpp b/src/extractor/guidance/intersection_handler.cpp index a327d52ec..913da7842 100644 --- a/src/extractor/guidance/intersection_handler.cpp +++ b/src/extractor/guidance/intersection_handler.cpp @@ -2,11 +2,13 @@ #include "extractor/guidance/intersection_handler.hpp" #include "extractor/guidance/toolkit.hpp" +#include "util/guidance/toolkit.hpp" #include "util/simple_logger.hpp" #include using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData; +using osrm::util::guidance::getTurnDirection; namespace osrm { diff --git a/src/extractor/guidance/intersection_scenario_three_way.cpp b/src/extractor/guidance/intersection_scenario_three_way.cpp index d005c7cf7..ae28688e3 100644 --- a/src/extractor/guidance/intersection_scenario_three_way.cpp +++ b/src/extractor/guidance/intersection_scenario_three_way.cpp @@ -2,6 +2,10 @@ #include "extractor/guidance/intersection_scenario_three_way.hpp" #include "extractor/guidance/toolkit.hpp" +#include "util/guidance/toolkit.hpp" + +using osrm::util::guidance::angularDeviation; + namespace osrm { namespace extractor diff --git a/src/extractor/guidance/motorway_handler.cpp b/src/extractor/guidance/motorway_handler.cpp index 3cd675bc7..0488a7283 100644 --- a/src/extractor/guidance/motorway_handler.cpp +++ b/src/extractor/guidance/motorway_handler.cpp @@ -3,12 +3,16 @@ #include "extractor/guidance/toolkit.hpp" #include "util/simple_logger.hpp" +#include "util/guidance/toolkit.hpp" #include #include #include +using osrm::util::guidance::angularDeviation; +using osrm::util::guidance::getTurnDirection; + namespace osrm { namespace extractor diff --git a/src/extractor/guidance/roundabout_handler.cpp b/src/extractor/guidance/roundabout_handler.cpp index 5403a9315..ac409633e 100644 --- a/src/extractor/guidance/roundabout_handler.cpp +++ b/src/extractor/guidance/roundabout_handler.cpp @@ -2,14 +2,18 @@ #include "extractor/guidance/roundabout_handler.hpp" #include "extractor/guidance/toolkit.hpp" +#include "util/coordinate_calculation.hpp" +#include "util/guidance/toolkit.hpp" #include "util/simple_logger.hpp" +#include #include -#include #include #include +using osrm::util::guidance::getTurnDirection; + namespace osrm { namespace extractor @@ -19,8 +23,10 @@ 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) + const util::NameTable &name_table, + const CompressedEdgeContainer &compressed_edge_container) + : IntersectionHandler(node_based_graph, node_info_list, name_table), + compressed_edge_container(compressed_edge_container) { } @@ -31,7 +37,11 @@ bool RoundaboutHandler::canProcess(const NodeID from_nid, const Intersection &intersection) const { const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection); - return flags.on_roundabout || flags.can_enter; + if (!flags.on_roundabout && !flags.can_enter) + return false; + + const auto roundabout_type = getRoundaboutType(node_based_graph.GetTarget(via_eid)); + return roundabout_type != RoundaboutType::None; } Intersection RoundaboutHandler:: @@ -39,10 +49,10 @@ operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersectio { invalidateExitAgainstDirection(from_nid, via_eid, intersection); const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection); - const bool is_rotary = isRotary(node_based_graph.GetTarget(via_eid)); + const auto roundabout_type = getRoundaboutType(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)); + return handleRoundabouts(roundabout_type, via_eid, flags.on_roundabout, + flags.can_exit_separately, std::move(intersection)); } detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags( @@ -56,7 +66,7 @@ detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags( { const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid); // only check actual outgoing edges - if (edge_data.reversed || !road.entry_allowed ) + if (edge_data.reversed || !road.entry_allowed) continue; if (edge_data.roundabout) @@ -85,7 +95,7 @@ void RoundaboutHandler::invalidateExitAgainstDirection(const NodeID from_nid, Intersection &intersection) const { const auto &in_edge_data = node_based_graph.GetEdgeData(via_eid); - if( in_edge_data.roundabout ) + if (in_edge_data.roundabout) return; bool past_roundabout_angle = false; @@ -116,7 +126,74 @@ void RoundaboutHandler::invalidateExitAgainstDirection(const NodeID from_nid, } } -bool RoundaboutHandler::isRotary(const NodeID nid) const +// If we want to see a roundabout as a turn, the exits have to be distinct enough to be seen a +// dedicated turns. We are limiting it to four-way intersections with well distinct bearings. +// All entry/roads and exit roads have to be simple. Not segregated roads. +// Processing segregated roads would technically require an angle of the turn to be available +// in postprocessing since we correct the turn-angle in turn-generaion. +bool RoundaboutHandler::qualifiesAsRoundaboutIntersection( + const std::set &roundabout_nodes) 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); + }; + const bool has_limited_size = roundabout_nodes.size() <= 4; + if (!has_limited_size) + return false; + + const bool simple_exits = !std::find_if( roundabout_nodes.begin(), roundabout_nodes.end(), [this]( const NodeID node ){ + return (node_based_graph.GetOutDegree(node) > 3); + }); + + if (!simple_exits) + return false; + + // Find all exit bearings. Only if they are well distinct (at least 60 degrees between + // them), we allow a roundabout turn + + const auto exit_bearings = [this, &roundabout_nodes, getCoordinate]() { + std::vector result; + for (const auto node : roundabout_nodes) + { + // given the reverse edge and the forward edge on a roundabout, a simple entry/exit + // can only contain a single further road + for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node)) + { + const auto edge_data = node_based_graph.GetEdgeData(edge); + if (edge_data.roundabout) + continue; + + // there is a single non-roundabout edge + const auto src_coordinate = getCoordinate(node); + const auto next_coordinate = getRepresentativeCoordinate( + node, node_based_graph.GetTarget(edge), edge, edge_data.reversed, + compressed_edge_container, node_info_list); + result.push_back( + util::coordinate_calculation::bearing(src_coordinate, next_coordinate)); + break; + } + } + std::sort(result.begin(), result.end()); + return result; + }(); + + const bool well_distinct_bearings = [](const std::vector &bearings) { + for (std::size_t bearing_index = 0; bearing_index < bearings.size(); ++bearing_index) + { + const double difference = + std::abs(bearings[(bearing_index + 1) % bearings.size()] - bearings[bearing_index]); + // we assume non-narrow turns as well distinct + if (difference <= NARROW_TURN_ANGLE) + return false; + } + return true; + }(exit_bearings); + + return well_distinct_bearings; +} + +RoundaboutType RoundaboutHandler::getRoundaboutType(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) { @@ -168,31 +245,33 @@ bool RoundaboutHandler::isRotary(const NodeID nid) const NodeID last_node = nid; while (0 == roundabout_nodes.count(last_node)) { - roundabout_nodes.insert(last_node); + // only count exits/entry locations + if (node_based_graph.GetOutDegree(last_node) > 2) + 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; + return RoundaboutType::None; } last_node = node_based_graph.GetTarget(eid); + + if (last_node == nid) + break; } - // 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)) + // a roundabout that cannot be entered or exited should not get here + if (roundabout_nodes.size() == 0) + return RoundaboutType::None; + + // More a traffic loop than anything else, currently treated as roundabout turn + if (roundabout_nodes.size() == 1) { - return false; + return RoundaboutType::RoundaboutIntersection; } - 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). @@ -217,12 +296,42 @@ bool RoundaboutHandler::isRotary(const NodeID nid) const // 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 RoundaboutType::Roundabout; - return radius > MAX_ROUNDABOUT_RADIUS; + // not within the dedicated radii for special roundabouts + if (radius > MAX_ROUNDABOUT_INTERSECTION_RADIUS && radius <= MAX_ROUNDABOUT_RADIUS) + return RoundaboutType::Roundabout; + + if (radius > MAX_ROUNDABOUT_RADIUS) + { + // 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 (0 != roundabout_name_id && 0 == connected_names.count(roundabout_name_id)) + return RoundaboutType::Rotary; + else + return RoundaboutType::Roundabout; + } + + if (radius <= MAX_ROUNDABOUT_INTERSECTION_RADIUS) + { + const bool qualifies_as_roundabout_nitersection = + qualifiesAsRoundaboutIntersection(roundabout_nodes); + if (qualifies_as_roundabout_nitersection) + { + return RoundaboutType::RoundaboutIntersection; + } + else + { + return RoundaboutType::Roundabout; + } + } + return RoundaboutType::Roundabout; } -Intersection RoundaboutHandler::handleRoundabouts(const bool is_rotary, +Intersection RoundaboutHandler::handleRoundabouts(const RoundaboutType roundabout_type, const EdgeID via_eid, const bool on_roundabout, const bool can_exit_roundabout_separately, @@ -248,14 +357,14 @@ Intersection RoundaboutHandler::handleRoundabouts(const bool is_rotary, } else { - turn.instruction = - TurnInstruction::REMAIN_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); + turn.instruction = TurnInstruction::REMAIN_ROUNDABOUT( + roundabout_type, getTurnDirection(turn.angle)); } } else { turn.instruction = - TurnInstruction::EXIT_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle)); + TurnInstruction::EXIT_ROUNDABOUT(roundabout_type, getTurnDirection(turn.angle)); } } return intersection; @@ -269,20 +378,17 @@ Intersection RoundaboutHandler::handleRoundabouts(const bool is_rotary, 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; - } + turn.instruction = TurnInstruction::ENTER_ROUNDABOUT_AT_EXIT( + roundabout_type, getTurnDirection(turn.angle)); + else + turn.instruction = TurnInstruction::ENTER_ROUNDABOUT( + roundabout_type, getTurnDirection(turn.angle)); } else { turn.instruction = TurnInstruction::ENTER_AND_EXIT_ROUNDABOUT( - is_rotary, getTurnDirection(turn.angle)); + roundabout_type, getTurnDirection(turn.angle)); } } return intersection; diff --git a/src/extractor/guidance/turn_analysis.cpp b/src/extractor/guidance/turn_analysis.cpp index 983a7b857..12ea6abb9 100644 --- a/src/extractor/guidance/turn_analysis.cpp +++ b/src/extractor/guidance/turn_analysis.cpp @@ -3,6 +3,7 @@ #include "util/coordinate.hpp" #include "util/coordinate_calculation.hpp" +#include "util/guidance/toolkit.hpp" #include "util/simple_logger.hpp" #include @@ -11,6 +12,8 @@ #include #include +using osrm::util::guidance::getTurnDirection; + namespace osrm { namespace extractor @@ -36,7 +39,7 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph, barrier_nodes, node_info_list, compressed_edge_container), - roundabout_handler(node_based_graph, node_info_list, name_table), + roundabout_handler(node_based_graph, node_info_list, name_table, compressed_edge_container), motorway_handler(node_based_graph, node_info_list, name_table), turn_handler(node_based_graph, node_info_list, name_table) { diff --git a/src/extractor/guidance/turn_handler.cpp b/src/extractor/guidance/turn_handler.cpp index 1aafc473f..dd55d44b5 100644 --- a/src/extractor/guidance/turn_handler.cpp +++ b/src/extractor/guidance/turn_handler.cpp @@ -4,6 +4,7 @@ #include "extractor/guidance/turn_handler.hpp" #include "util/simple_logger.hpp" +#include "util/guidance/toolkit.hpp" #include #include @@ -11,6 +12,8 @@ #include using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData; +using osrm::util::guidance::getTurnDirection; +using osrm::util::guidance::angularDeviation; namespace osrm {