diff --git a/features/bicycle/mode.feature b/features/bicycle/mode.feature index 2bfa4de5b..47618894b 100644 --- a/features/bicycle/mode.feature +++ b/features/bicycle/mode.feature @@ -62,13 +62,13 @@ Feature: Bike - Mode flag | cd | primary | | When I route I should get - | from | to | route | turns | modes | - | a | d | ab,bc,cd | head,right,left,destination | 1,1,1 | - | d | a | cd,bc,ab | head,right,left,destination | 1,2,1 | - | c | a | bc,ab | head,left,destination | 2,1 | - | d | b | cd,bc | head,right,destination | 1,2 | - | a | c | ab,bc | head,right,destination | 1,1 | - | b | d | bc,cd | head,left,destination | 1,1 | + | from | to | route | turns | modes | + | a | d | ab,bc,cd | head,straight,straight,destination | 1,1,1 | + | d | a | cd,bc,ab | head,right,left,destination | 1,2,1 | + | c | a | bc,ab | head,left,destination | 2,1 | + | d | b | cd,bc | head,right,destination | 1,2 | + | a | c | ab,bc | head,straight,destination | 1,1 | + | b | d | bc,cd | head,straight,destination | 1,1 | Scenario: Bike - Mode when pushing on pedestrain streets Given the node map diff --git a/features/bicycle/pushing.feature b/features/bicycle/pushing.feature index 5741fb1c0..56b79b611 100644 --- a/features/bicycle/pushing.feature +++ b/features/bicycle/pushing.feature @@ -98,11 +98,11 @@ Feature: Bike - Accessability of different way types | cd | primary | | When I route I should get - | from | to | route | turns | - | a | d | ab,bc,cd | head,right,left,destination | - | d | a | cd,bc,ab | head,right,left,destination | - | c | a | bc,ab | head,left,destination | - | d | b | cd,bc | head,right,destination | + | from | to | route | turns | + | a | d | ab,bc,cd | head,straight,straight,destination | + | d | a | cd,bc,ab | head,right,left,destination | + | c | a | bc,ab | head,left,destination | + | d | b | cd,bc | head,right,destination | @todo Scenario: Bike - Instructions when pushing bike on footway/pedestrian, etc. diff --git a/features/support/route.rb b/features/support/route.rb index 935c5be38..a0deacb8f 100644 --- a/features/support/route.rb +++ b/features/support/route.rb @@ -155,8 +155,9 @@ def turn_list instructions 13 => :stay_roundabout, 14 => :start_end_of_street, 15 => :destination, - 16 => :enter_contraflow, - 17 => :leave_contraflow + 16 => :name_changes, + 17 => :enter_contraflow, + 18 => :leave_contraflow } # replace instructions codes with strings # "11-3" (enter roundabout and leave a 3rd exit) gets converted to "enter_roundabout-3" diff --git a/features/testbot/graph.feature b/features/testbot/graph.feature index adff99868..cbbec0c18 100644 --- a/features/testbot/graph.feature +++ b/features/testbot/graph.feature @@ -36,5 +36,5 @@ Feature: Basic Routing | fy | last | When I route I should get - | from | to | route | turns | - | x | y | first,compr,last | head,right,left,destination | + | from | to | route | turns | + | x | y | first,compr,last | head,straight,straight,destination | diff --git a/features/testbot/loop.feature b/features/testbot/loop.feature index c69e7a251..2d581b46b 100644 --- a/features/testbot/loop.feature +++ b/features/testbot/loop.feature @@ -92,6 +92,6 @@ Feature: Avoid weird loops caused by rounding errors | cf | primary | When I route I should get - | waypoints | route | turns | - | a,2,d | ab,be,ef,ef,cf,cd | head,left,right,via,right,left,destination | - | a,1,d | ab,be,ef,ef,cf,cd | head,left,right,via,right,left,destination | + | waypoints | route | turns | + | a,2,d | ab,be,ef,ef,cf,cd | head,left,straight,via,straight,left,destination | + | a,1,d | ab,be,ef,ef,cf,cd | head,left,straight,via,straight,left,destination | diff --git a/features/testbot/matching_turns.feature b/features/testbot/matching_turns.feature index 8bca4a274..f807ca954 100644 --- a/features/testbot/matching_turns.feature +++ b/features/testbot/matching_turns.feature @@ -17,21 +17,13 @@ Feature: Turn directions/codes And the ways | nodes | | xa | - | xb | | xc | - | xd | | xe | - | xf | | xg | - | xh | | xi | - | xj | | xk | - | xl | | xm | - | xn | | xo | - | xp | When I match I should get | trace | route | turns | matchings | @@ -40,7 +32,7 @@ Feature: Turn directions/codes | ia | xi,xa | head,straight,destination | ia | | ic | xi,xc | head,slight_right,destination | ic | | ie | xi,xe | head,right,destination | ie | - + | ko | xk,xo | head,left,destination | ko | | ka | xk,xa | head,slight_left,destination | ka | | kc | xk,xc | head,straight,destination | kc | @@ -96,21 +88,13 @@ Feature: Turn directions/codes And the ways | nodes | | xa | - | xb | | xc | - | xd | | xe | - | xf | | xg | - | xh | | xi | - | xj | | xk | - | xl | | xm | - | xn | | xo | - | xp | When I match I should get | trace | route | turns | matchings | duration | diff --git a/features/testbot/mode.feature b/features/testbot/mode.feature index ef8cd257e..e055799d9 100644 --- a/features/testbot/mode.feature +++ b/features/testbot/mode.feature @@ -174,14 +174,14 @@ Feature: Testbot - Travel mode | ef | primary | | | When I route I should get - | from | to | route | turns | modes | - | a | d | ab,bc,cd | head,right,left,destination | 1,2,1 | - | d | a | cd,bc,ab | head,right,left,destination | 1,2,1 | - | c | a | bc,ab | head,left,destination | 2,1 | - | d | b | cd,bc | head,right,destination | 1,2 | - | a | c | ab,bc | head,right,destination | 1,2 | - | b | d | bc,cd | head,left,destination | 2,1 | - | a | f | ab,bc,cd,de,ef | head,right,left,straight,straight,destination | 1,2,1,1,1 | + | from | to | route | turns | modes | + | a | d | ab,bc,cd | head,right,left,destination | 1,2,1 | + | d | a | cd,bc,ab | head,right,left,destination | 1,2,1 | + | c | a | bc,ab | head,left,destination | 2,1 | + | d | b | cd,bc | head,right,destination | 1,2 | + | a | c | ab,bc | head,right,destination | 1,2 | + | b | d | bc,cd | head,left,destination | 2,1 | + | a | f | ab,bc,cd,de,ef | head,right,left,straight,straight,destination | 1,2,1,1,1 | Scenario: Testbot - Modes, triangle map Given the node map diff --git a/features/testbot/post.feature b/features/testbot/post.feature index ac53177c5..656e26d90 100644 --- a/features/testbot/post.feature +++ b/features/testbot/post.feature @@ -21,13 +21,13 @@ Feature: POST request | bc | | xy | | yz | - + When I route I should get - | from | to | route | turns | - | a | c | ab,bc | head,left,destination | - | c | a | bc,ab | head,right,destination | - | x | z | xy,yz | head,right,destination | - | z | x | yz,xy | head,left,destination | + | from | to | route | turns | + | a | c | ab,bc | head,straight,destination | + | c | a | bc,ab | head,straight,destination | + | x | z | xy,yz | head,straight,destination | + | z | x | yz,xy | head,straight,destination | Scenario: Testbot - match POST request Given a grid size of 10 meters diff --git a/features/testbot/turn_angles.feature b/features/testbot/turn_angles.feature new file mode 100644 index 000000000..ce1400369 --- /dev/null +++ b/features/testbot/turn_angles.feature @@ -0,0 +1,74 @@ +@routing @testbot @via +Feature: Via points + + Background: + Given the profile "testbot" + + And a grid size of 4 meters + + Scenario: Basic Right Turn + Given the node map + | a | b | c | d | e | f | g | + | | | | | | h | | + | | | | | | i | | + | | | | | | j | | + | | | | | | k | | + + And the ways + | nodes | oneway | + | abcdefg | yes | + | ehijk | yes | + + When I route I should get + | from | to | route | distance | turns | + | a | k | abcdefg,ehijk | 34m +-1 | head,right,destination | + + Scenario: Slight Turn + Given the node map + | a | b | c | d | e | f | g | | + | | | | | | h | i | | + | | | | | | | | j | + | | | | | | | | k | + + And the ways + | nodes | oneway | + | abcdefg | yes | + | ehijk | yes | + + When I route I should get + | from | to | route | distance | turns | + | a | k | abcdefg,ehijk | 34m +-1 | head,slight_right,destination | + + Scenario: Nearly Slight Turn + Given the node map + | a | b | c | d | e | f | g | | + | | | | | | h | | | + | | | | | | | i | | + | | | | | | | | j | + | | | | | | | | k | + + And the ways + | nodes | oneway | + | abcdefg | yes | + | ehijk | yes | + + When I route I should get + | from | to | route | distance | turns | + | a | k | abcdefg,ehijk | 37m +-1 | head,right,destination | + + Scenario: Nearly Slight Turn (Variation) + Given the node map + | a | b | c | d | e | f | g | | + | | | | | | h | | | + | | | | | | | i | | + | | | | | | | j | | + | | | | | | | | k | + + And the ways + | nodes | oneway | + | abcdefg | yes | + | ehijk | yes | + + When I route I should get + | from | to | route | distance | turns | + | a | k | abcdefg,ehijk | 37m +-1 | head,right,destination | diff --git a/features/testbot/turns.feature b/features/testbot/turns.feature index 6eba4f9e9..0e7382f3c 100644 --- a/features/testbot/turns.feature +++ b/features/testbot/turns.feature @@ -14,22 +14,14 @@ Feature: Turn directions/codes And the ways | nodes | - | xa | - | xb | - | xc | - | xd | - | xe | - | xf | - | xg | - | xh | | xi | - | xj | | xk | - | xl | | xm | - | xn | | xo | - | xp | + | xa | + | xc | + | xe | + | xg | When I route I should get | from | to | route | turns | @@ -116,8 +108,8 @@ Feature: Turn directions/codes | yz | When I route I should get - | from | to | route | turns | - | a | c | ab,bc | head,left,destination | - | c | a | bc,ab | head,right,destination | - | x | z | xy,yz | head,right,destination | - | z | x | yz,xy | head,left,destination | + | from | to | route | turns | + | a | c | ab,bc | head,straight,destination | + | c | a | bc,ab | head,straight,destination | + | x | z | xy,yz | head,straight,destination | + | z | x | yz,xy | head,straight,destination | diff --git a/features/testbot/uturn.feature b/features/testbot/uturn.feature index 032bec685..47ff6a87d 100644 --- a/features/testbot/uturn.feature +++ b/features/testbot/uturn.feature @@ -20,8 +20,8 @@ Feature: U-turns at via points | fg | When I route I should get - | waypoints | route | turns | - | a,e,c | ab,be,be,ef,fg,dg,cd | head,right,via,left,straight,left,left,destination | + | waypoints | route | turns | + | a,e,c | ab,be,be,ef,fg,dg,cd | head,right,via,straight,straight,straight,straight,destination | Scenario: Query param to allow U-turns at all via points Given the node map diff --git a/features/testbot/via.feature b/features/testbot/via.feature index 3af38a9d6..bfed93e3d 100644 --- a/features/testbot/via.feature +++ b/features/testbot/via.feature @@ -86,12 +86,12 @@ Feature: Via points | fa | yes | When I route I should get - | waypoints | route | distance | turns | - | 1,3 | ab,bc,cd | 400m +-1 | head,straight,straight,destination | - | 3,1 | cd,de,ef,fa,ab | 1000m +-1 | head,right,right,right,right,destination | - | 1,2,3 | ab,bc,bc,cd | 400m +-1 | head,straight,via,straight,destination | - | 1,3,2 | ab,bc,cd,cd,de,ef,fa,ab,bc | 1600m +-1 | head,straight,straight,via,right,right,right,right,straight,destination | - | 3,2,1 | cd,de,ef,fa,ab,bc,bc,cd,de,ef,fa,ab | 2400m +-1 | head,right,right,right,right,straight,via,straight,right,right,right,right,destination | + | waypoints | route | distance | turns | + | 1,3 | ab,bc,cd | 400m +-1 | head,straight,straight,destination | + | 3,1 | cd,de,ef,fa,ab | 1000m +-1 | head,straight,straight,straight,right,destination | + | 1,2,3 | ab,bc,bc,cd | 400m +-1 | head,straight,via,straight,destination | + | 1,3,2 | ab,bc,cd,cd,de,ef,fa,ab,bc | 1600m +-1 | head,straight,straight,via,straight,straight,straight,right,straight,destination | + | 3,2,1 | cd,de,ef,fa,ab,bc,bc,cd,de,ef,fa,ab | 2400m +-1 | head,straight,straight,straight,right,straight,via,straight,straight,straight,straight,right,destination | Scenario: Via points on ring on the same oneway # xa it to avoid only having a single ring, which cna trigger edge cases @@ -109,12 +109,12 @@ Feature: Via points | da | yes | When I route I should get - | waypoints | route | distance | turns | - | 1,3 | ab | 200m +-1 | head,destination | - | 3,1 | ab,bc,cd,da,ab | 800m +-1 | head,right,right,right,right,destination | - | 1,2,3 | ab,ab | 200m +-1 | head,via,destination | - | 1,3,2 | ab,ab,bc,cd,da,ab | 1100m +-1 | head,via,right,right,right,right,destination | - | 3,2,1 | ab,bc,cd,da,ab,ab,bc,cd,da,ab | 1800m | head,right,right,right,right,via,right,right,right,right,destination | + | waypoints | route | distance | turns | + | 1,3 | ab | 200m +-1 | head,destination | + | 3,1 | ab,bc,cd,da,ab | 800m +-1 | head,straight,straight,straight,right,destination | + | 1,2,3 | ab,ab | 200m +-1 | head,via,destination | + | 1,3,2 | ab,ab,bc,cd,da,ab | 1100m +-1 | head,via,straight,straight,straight,right,destination | + | 3,2,1 | ab,bc,cd,da,ab,ab,bc,cd,da,ab | 1800m | head,straight,straight,straight,right,via,straight,straight,straight,right,destination | # See issue #1896 Scenario: Via point at a dead end with oneway @@ -175,11 +175,11 @@ Feature: Via points | da | yes | When I route I should get - | waypoints | route | distance | turns | - | 2,1 | ab,bc,cd,da,ab | 1100m +-1 | head,right,right,right,right,destination | - | 4,3 | bc,cd,da,ab,bc | 1100m +-1 | head,right,right,right,right,destination | - | 6,5 | cd,da,ab,bc,cd | 1100m +-1 | head,right,right,right,right,destination | - | 8,7 | da,ab,bc,cd,da | 1100m +-1 | head,right,right,right,right,destination | + | waypoints | route | distance | turns | + | 2,1 | ab,bc,cd,da,ab | 1100m +-1 | head,straight,straight,straight,straight,destination | + | 4,3 | bc,cd,da,ab,bc | 1100m +-1 | head,straight,straight,straight,straight,destination | + | 6,5 | cd,da,ab,bc,cd | 1100m +-1 | head,straight,straight,straight,straight,destination | + | 8,7 | da,ab,bc,cd,da | 1100m +-1 | head,straight,straight,straight,straight,destination | Scenario: Multiple Via points on ring on the same oneway, forces one of the vertices to be top node Given the node map @@ -197,7 +197,7 @@ Feature: Via points | da | yes | When I route I should get - | waypoints | route | distance | turns | - | 3,2,1 | ab,bc,cd,da,ab,ab,bc,cd,da,ab | 3000m +-1 | head,right,right,right,right,via,right,right,right,right,destination | - | 6,5,4 | bc,cd,da,ab,bc,bc,cd,da,ab,bc | 3000m +-1 | head,right,right,right,right,via,right,right,right,right,destination | - | 9,8,7 | cd,da,ab,bc,cd,cd,da,ab,bc,cd | 3000m +-1 | head,right,right,right,right,via,right,right,right,right,destination | + | waypoints | route | distance | turns | + | 3,2,1 | ab,bc,cd,da,ab,ab,bc,cd,da,ab | 3000m +-1 | head,straight,straight,straight,straight,via,straight,straight,straight,straight,destination | + | 6,5,4 | bc,cd,da,ab,bc,bc,cd,da,ab,bc | 3000m +-1 | head,straight,straight,straight,straight,via,straight,straight,straight,straight,destination | + | 9,8,7 | cd,da,ab,bc,cd,cd,da,ab,bc,cd | 3000m +-1 | head,straight,straight,straight,straight,via,straight,straight,straight,straight,destination | diff --git a/include/engine/guidance/segment_list.hpp b/include/engine/guidance/segment_list.hpp index 011b31d8a..b9be5ad2e 100644 --- a/include/engine/guidance/segment_list.hpp +++ b/include/engine/guidance/segment_list.hpp @@ -106,7 +106,8 @@ SegmentList::SegmentList(const InternalRouteResult &raw_route, { const auto &source_phantom = raw_route.segment_end_coordinates[raw_index].target_phantom; - if (raw_route.target_traversed_in_reverse[raw_index] != raw_route.source_traversed_in_reverse[raw_index+1]) + if (raw_route.target_traversed_in_reverse[raw_index] != + raw_route.source_traversed_in_reverse[raw_index + 1]) { bool traversed_in_reverse = raw_route.target_traversed_in_reverse[raw_index]; const extractor::TravelMode travel_mode = @@ -264,6 +265,12 @@ void SegmentList::Finalize(const bool extract_alternative, segment_length = 0; segment_duration = 0; segment_start_index = i; + + if (segments[i].turn_instruction == extractor::TurnInstruction::NameChanges) + { + segments[i].turn_instruction = + extractor::TurnInstruction::GoStraight; // to not break the api + } } } diff --git a/include/engine/plugins/viaroute.hpp b/include/engine/plugins/viaroute.hpp index 6789e5ac0..1d3e6bef2 100644 --- a/include/engine/plugins/viaroute.hpp +++ b/include/engine/plugins/viaroute.hpp @@ -8,7 +8,6 @@ #include "engine/search_engine.hpp" #include "util/for_each_pair.hpp" #include "util/integer_range.hpp" -#include "util/json_renderer.hpp" #include "util/make_unique.hpp" #include "util/simple_logger.hpp" #include "util/timing_util.hpp" @@ -166,7 +165,6 @@ template class ViaRoutePlugin final : public BasePlugin return Status::Error; } } - return Status::Ok; } }; diff --git a/include/extractor/edge_based_graph_factory.hpp b/include/extractor/edge_based_graph_factory.hpp index 7957cb60b..e6335123e 100644 --- a/include/extractor/edge_based_graph_factory.hpp +++ b/include/extractor/edge_based_graph_factory.hpp @@ -16,6 +16,8 @@ #include "extractor/restriction_map.hpp" #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include #include @@ -70,10 +73,18 @@ class EdgeBasedGraphFactory unsigned GetHighestEdgeID(); - TurnInstruction - AnalyzeTurn(const NodeID u, const NodeID v, const NodeID w, const double angle) const; + // Basic analysis of a turn (u --(e1)-- v --(e2)-- w) + // with known angle. + // Handles special cases like u-turns and roundabouts + // For basic turns, the turn based on the angle-classification is returned + TurnInstruction AnalyzeTurn(const NodeID u, + const EdgeID e1, + const NodeID v, + const EdgeID e2, + const NodeID w, + const double angle) const; - int GetTurnPenalty(double angle, lua_State *lua_state) const; + std::int32_t GetTurnPenalty(double angle, lua_State *lua_state) const; private: using EdgeData = util::NodeBasedDynamicGraph::EdgeData; @@ -123,8 +134,54 @@ class EdgeBasedGraphFactory void FlushVectorToStream(std::ofstream &edge_data_file, std::vector &original_edge_data_vector) const; + + struct TurnCandidate + { + EdgeID eid; // the id of the arc + bool valid; // a turn may be relevant to good instructions, even if we cannot take the road + double angle; // the approximated angle of the turn + TurnInstruction instruction; // a proposed instruction + double confidence; // how close to the border is the turn? + + std::string toString() const + { + std::string result = "[turn] "; + result += std::to_string(eid); + result += " valid: "; + result += std::to_string(valid); + result += " angle: "; + result += std::to_string(angle); + result += " instruction: "; + result += std::to_string(static_cast(instruction)); + result += " confidence: "; + result += std::to_string(confidence); + return result; + } + }; + + // Use In Order to generate base turns + + // cannot be const due to the counters... + std::vector getTurnCandidates(NodeID from, EdgeID via_edge); + std::vector optimizeCandidates(NodeID via_edge, + std::vector turn_candidates) const; + std::vector suppressTurns(EdgeID via_edge, + std::vector turn_candidates) const; + + QueryNode getRepresentativeCoordinate(const NodeID src, + const NodeID tgt, + const EdgeID via_eid, + bool INVERTED) const; + + bool isObviousChoice(EdgeID coming_from_eid, + std::size_t turn_index, + const std::vector &turn_candidates) const; + + std::size_t restricted_turns_counter; + std::size_t skipped_uturns_counter; + std::size_t skipped_barrier_turns_counter; }; -} -} +} // namespace extractor +} // namespace osrm #endif /* EDGE_BASED_GRAPH_FACTORY_HPP_ */ diff --git a/include/extractor/query_node.hpp b/include/extractor/query_node.hpp index c97a986be..f62dd33b3 100644 --- a/include/extractor/query_node.hpp +++ b/include/extractor/query_node.hpp @@ -17,7 +17,7 @@ struct QueryNode using key_type = OSMNodeID; // type of NodeID using value_type = int; // type of lat,lons - explicit QueryNode(int lat, int lon, OSMNodeID node_id) + explicit QueryNode(int lat, int lon, key_type node_id) : lat(lat), lon(lon), node_id(std::move(node_id)) { } @@ -29,7 +29,7 @@ struct QueryNode int lat; int lon; - OSMNodeID node_id; + key_type node_id; static QueryNode min_value() { diff --git a/include/extractor/turn_instructions.hpp b/include/extractor/turn_instructions.hpp index 9a08c97da..7cca8d78e 100644 --- a/include/extractor/turn_instructions.hpp +++ b/include/extractor/turn_instructions.hpp @@ -1,6 +1,11 @@ #ifndef TURN_INSTRUCTIONS_HPP #define TURN_INSTRUCTIONS_HPP +#include +#include + +#include + namespace osrm { namespace extractor @@ -24,6 +29,7 @@ enum class TurnInstruction : unsigned char StayOnRoundAbout, StartAtEndOfStreet, ReachedYourDestination, + NameChanges, EnterAgainstAllowedDirection, LeaveAgainstAllowedDirection, InverseAccessRestrictionFlag = 127, @@ -31,37 +37,87 @@ enum class TurnInstruction : unsigned char AccessRestrictionPenalty = 129 }; +// shiftable turns to left and right +const constexpr bool shiftable_left[] = {false, false, true, true, true, false, false, true, true}; +const constexpr bool shiftable_right[] = {false, false, true, true, false, false, true, true, true}; + +inline TurnInstruction shiftTurnToLeft(TurnInstruction turn) +{ + BOOST_ASSERT_MSG(static_cast(turn) < 9, + "Shift turn only supports basic turn instructions"); + if (turn > TurnInstruction::TurnSlightLeft) + return turn; + else + return shiftable_left[static_cast(turn)] + ? (static_cast(static_cast(turn) - 1)) + : turn; +} + +inline TurnInstruction shiftTurnToRight(TurnInstruction turn) +{ + BOOST_ASSERT_MSG(static_cast(turn) < 9, + "Shift turn only supports basic turn instructions"); + if (turn > TurnInstruction::TurnSlightLeft) + return turn; + else + return shiftable_right[static_cast(turn)] + ? (static_cast(static_cast(turn) + 1)) + : turn; +} + +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, TurnInstruction instruction) +{ + BOOST_ASSERT_MSG(static_cast(instruction) < 9, + "Angular penalty only supports basic turn instructions"); + const double center[] = {180, 180, 135, 90, 45, + 0, 315, 270, 225}; // centers of turns from getTurnDirection + return angularDeviation(center[static_cast(instruction)], angle); +} + +inline double getTurnConfidence(const double angle, TurnInstruction instruction) +{ + + // special handling of U-Turns and Roundabout + if (instruction >= TurnInstruction::HeadOn || instruction == TurnInstruction::UTurn || + instruction == TurnInstruction::NoTurn || instruction == TurnInstruction::EnterRoundAbout || + instruction == TurnInstruction::StayOnRoundAbout || instruction == TurnInstruction::LeaveRoundAbout ) + return 1.0; + + BOOST_ASSERT_MSG(static_cast(instruction) < 9, + "Turn confidence only supports basic turn instructions"); + const double deviations[] = {10, 10, 35, 50, 45, 0, 45, 50, 35}; + const double difference = getAngularPenalty(angle, instruction); + const double max_deviation = deviations[static_cast(instruction)]; + return 1.0 - (difference / max_deviation) * (difference / max_deviation); +} + // Translates between angles and their human-friendly directional representation inline TurnInstruction getTurnDirection(const double angle) { - if (angle >= 23 && angle < 67) - { + // 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 TurnInstruction::TurnSharpRight; - } - if (angle >= 67 && angle < 113) - { + if (angle >= 60 && angle < 140) return TurnInstruction::TurnRight; - } - if (angle >= 113 && angle < 158) - { + if (angle >= 140 && angle < 170) return TurnInstruction::TurnSlightRight; - } - if (angle >= 158 && angle < 202) - { + if (angle >= 170 && angle <= 190) return TurnInstruction::GoStraight; - } - if (angle >= 202 && angle < 248) - { + if (angle > 190 && angle <= 220) return TurnInstruction::TurnSlightLeft; - } - if (angle >= 248 && angle < 292) - { + if (angle > 220 && angle <= 300) return TurnInstruction::TurnLeft; - } - if (angle >= 292 && angle < 336) - { + if (angle > 300 && angle < 360) return TurnInstruction::TurnSharpLeft; - } return TurnInstruction::UTurn; } @@ -75,6 +131,51 @@ inline bool isTurnNecessary(const TurnInstruction turn_instruction) } return true; } + +inline bool resolve(TurnInstruction &to_resolve, const TurnInstruction neighbor, bool resolve_right) +{ + const auto shifted_turn = + resolve_right ? shiftTurnToRight(to_resolve) : shiftTurnToLeft(to_resolve); + if (shifted_turn == neighbor || shifted_turn == to_resolve) + return false; + + to_resolve = shifted_turn; + return true; +} + +inline bool resolveTransitive(TurnInstruction &first, + TurnInstruction &second, + const TurnInstruction third, + bool resolve_right) +{ + if (resolve(second, third, resolve_right)) + { + first = resolve_right ? shiftTurnToRight(first) : shiftTurnToLeft(first); + return true; + } + return false; +} + +inline bool isSlightTurn(const TurnInstruction turn) +{ + return turn == TurnInstruction::GoStraight || turn == TurnInstruction::TurnSlightRight || + turn == TurnInstruction::TurnSlightLeft || turn == TurnInstruction::NoTurn; +} + +inline bool isSharpTurn(const TurnInstruction turn) +{ + return turn == TurnInstruction::TurnSharpLeft || turn == TurnInstruction::TurnSharpRight; +} + +inline bool isStraight(const TurnInstruction turn) +{ + return turn == TurnInstruction::GoStraight || turn == TurnInstruction::NoTurn; +} + +inline bool isConflict(const TurnInstruction first, const TurnInstruction second) +{ + return first == second || (isStraight(first) && isStraight(second)); +} } } diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index 6ffbb4bea..12a5490f8 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -1,5 +1,6 @@ #include "extractor/edge_based_edge.hpp" #include "extractor/edge_based_graph_factory.hpp" +#include "util/coordinate.hpp" #include "util/coordinate_calculation.hpp" #include "util/percent.hpp" #include "util/integer_range.hpp" @@ -12,15 +13,38 @@ #include +#include +#include #include #include #include +#include +#include namespace osrm { namespace extractor { +// configuration of turn classification +const bool constexpr INVERT = true; +const bool constexpr RESOLVE_TO_RIGHT = true; +const bool constexpr RESOLVE_TO_LEFT = false; + +// what angle is interpreted as going straight +const double constexpr STRAIGHT_ANGLE = 180.; +// if a turn deviates this much from going straight, it will be kept straight +const double constexpr MAXIMAL_ALLOWED_NO_TURN_DEVIATION = 2.; +// angle that lies between two nearly indistinguishable roads +const double constexpr NARROW_TURN_ANGLE = 25.; +// angle difference that can be classified as straight, if its the only narrow turn +const double constexpr FUZZY_STRAIGHT_ANGLE = 15.; +const double constexpr DISTINCTION_RATIO = 2; + +// Configuration to find representative candidate for turn angle calculations +const double constexpr MINIMAL_SEGMENT_LENGTH = 1.; +const double constexpr DESIRED_SEGMENT_LENGTH = 10.; + EdgeBasedGraphFactory::EdgeBasedGraphFactory( std::shared_ptr node_based_graph, const CompressedEdgeContainer &compressed_edge_container, @@ -361,8 +385,12 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( { util::SimpleLogger().Write() << "generating edge-expanded edges"; - unsigned node_based_edge_counter = 0; - unsigned original_edges_counter = 0; + std::size_t node_based_edge_counter = 0; + std::size_t original_edges_counter = 0; + restricted_turns_counter = 0; + skipped_uturns_counter = 0; + skipped_barrier_turns_counter = 0; + std::size_t compressed = 0; std::ofstream edge_data_file(original_edge_data_filename.c_str(), std::ios::binary); std::ofstream edge_segment_file; @@ -383,11 +411,6 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // Loop over all turns and generate new set of edges. // Three nested loop look super-linear, but we are dealing with a (kind of) // linear number of turns only. - unsigned restricted_turns_counter = 0; - unsigned skipped_uturns_counter = 0; - unsigned skipped_barrier_turns_counter = 0; - unsigned compressed = 0; - util::Percent progress(m_node_based_graph->GetNumberOfNodes()); #ifdef DEBUG_GEOMETRY @@ -397,79 +420,30 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( for (const auto node_u : util::irange(0u, m_node_based_graph->GetNumberOfNodes())) { // progress.printStatus(node_u); - for (const EdgeID e1 : m_node_based_graph->GetAdjacentEdgeRange(node_u)) + for (const EdgeID edge_form_u : m_node_based_graph->GetAdjacentEdgeRange(node_u)) { - if (m_node_based_graph->GetEdgeData(e1).reversed) + if (m_node_based_graph->GetEdgeData(edge_form_u).reversed) { continue; } ++node_based_edge_counter; - const NodeID node_v = m_node_based_graph->GetTarget(e1); - const NodeID only_restriction_to_node = - m_restriction_map->CheckForEmanatingIsOnlyTurn(node_u, node_v); - const bool is_barrier_node = m_barrier_nodes.find(node_v) != m_barrier_nodes.end(); + auto turn_candidates = getTurnCandidates(node_u, edge_form_u); + turn_candidates = optimizeCandidates(edge_form_u, turn_candidates); + turn_candidates = suppressTurns(edge_form_u, turn_candidates); - for (const EdgeID e2 : m_node_based_graph->GetAdjacentEdgeRange(node_v)) + const NodeID node_v = m_node_based_graph->GetTarget(edge_form_u); + + for (const auto turn : turn_candidates) { - if (m_node_based_graph->GetEdgeData(e2).reversed) - { + if (!turn.valid) continue; - } - const NodeID node_w = m_node_based_graph->GetTarget(e2); - if ((only_restriction_to_node != SPECIAL_NODEID) && - (node_w != only_restriction_to_node)) - { - // We are at an only_-restriction but not at the right turn. - ++restricted_turns_counter; - continue; - } - - if (is_barrier_node) - { - if (node_u != node_w) - { - ++skipped_barrier_turns_counter; - continue; - } - } - else - { - if (node_u == node_w && m_node_based_graph->GetOutDegree(node_v) > 1) - { - auto number_of_emmiting_bidirectional_edges = 0; - for (auto edge : m_node_based_graph->GetAdjacentEdgeRange(node_v)) - { - auto target = m_node_based_graph->GetTarget(edge); - auto reverse_edge = m_node_based_graph->FindEdge(target, node_v); - if (!m_node_based_graph->GetEdgeData(reverse_edge).reversed) - { - ++number_of_emmiting_bidirectional_edges; - } - } - if (number_of_emmiting_bidirectional_edges > 1) - { - ++skipped_uturns_counter; - continue; - } - } - } - - // only add an edge if turn is not a U-turn except when it is - // at the end of a dead-end street - if (m_restriction_map->CheckIfTurnIsRestricted(node_u, node_v, node_w) && - (only_restriction_to_node == SPECIAL_NODEID) && - (node_w != only_restriction_to_node)) - { - // We are at an only_-restriction but not at the right turn. - ++restricted_turns_counter; - continue; - } + const double turn_angle = turn.angle; // only add an edge if turn is not prohibited - const EdgeData &edge_data1 = m_node_based_graph->GetEdgeData(e1); - const EdgeData &edge_data2 = m_node_based_graph->GetEdgeData(e2); + const EdgeData &edge_data1 = m_node_based_graph->GetEdgeData(edge_form_u); + const EdgeData &edge_data2 = m_node_based_graph->GetEdgeData(turn.eid); BOOST_ASSERT(edge_data1.edge_id != edge_data2.edge_id); BOOST_ASSERT(!edge_data1.reversed); @@ -485,23 +459,9 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( speed_profile.traffic_signal_penalty); } - // unpack last node of first segment if packed - const auto first_coordinate = - m_node_info_list[(m_compressed_edge_container.HasEntryForID(e1) - ? m_compressed_edge_container.GetLastEdgeSourceID(e1) - : node_u)]; - - // unpack first node of second segment if packed - const auto third_coordinate = - m_node_info_list[(m_compressed_edge_container.HasEntryForID(e2) - ? m_compressed_edge_container.GetFirstEdgeTargetID(e2) - : node_w)]; - - const double turn_angle = util::coordinate_calculation::computeAngle( - first_coordinate, m_node_info_list[node_v], third_coordinate); - const int turn_penalty = GetTurnPenalty(turn_angle, lua_state); - TurnInstruction turn_instruction = AnalyzeTurn(node_u, node_v, node_w, turn_angle); + const TurnInstruction turn_instruction = turn.instruction; + if (turn_instruction == TurnInstruction::UTurn) { distance += speed_profile.u_turn_penalty; @@ -509,12 +469,10 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( util::DEBUG_UTURN(node_v, m_node_info_list, speed_profile.u_turn_penalty); } - util::DEBUG_TURN(node_v, m_node_info_list, first_coordinate, turn_angle, - turn_penalty); - distance += turn_penalty; - const bool edge_is_compressed = m_compressed_edge_container.HasEntryForID(e1); + const bool edge_is_compressed = + m_compressed_edge_container.HasEntryForID(edge_form_u); if (edge_is_compressed) { @@ -522,7 +480,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( } original_edge_data_vector.emplace_back( - (edge_is_compressed ? m_compressed_edge_container.GetPositionForID(e1) + (edge_is_compressed ? m_compressed_edge_container.GetPositionForID(edge_form_u) : node_v), edge_data1.name_id, turn_instruction, edge_is_compressed, edge_data2.travel_mode); @@ -563,7 +521,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( if (edge_is_compressed) { const auto node_based_edges = - m_compressed_edge_container.GetBucketReference(e1); + m_compressed_edge_container.GetBucketReference(edge_form_u); NodeID previous = node_u; const unsigned node_count = node_based_edges.size() + 1; @@ -638,6 +596,470 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( << " turns over barriers"; } +// requires sorted candidates +std::vector +EdgeBasedGraphFactory::optimizeCandidates(NodeID via_eid, + std::vector turn_candidates) const +{ + BOOST_ASSERT_MSG(std::is_sorted(turn_candidates.begin(), turn_candidates.end(), + [](const TurnCandidate &left, const TurnCandidate &right) + { + return left.angle < right.angle; + }), + "Turn Candidates not sorted by angle."); + if (turn_candidates.size() <= 1) + return turn_candidates; + + const auto getLeft = [&turn_candidates](std::size_t index) + { + return (index + 1) % turn_candidates.size(); + }; + const auto getRight = [&turn_candidates](std::size_t index) + { + return (index + turn_candidates.size() - 1) % turn_candidates.size(); + }; + + // handle availability of multiple u-turns (e.g. street with separated small parking roads) + if (turn_candidates[0].instruction == TurnInstruction::UTurn && turn_candidates[0].angle == 0) + { + if (turn_candidates[getLeft(0)].instruction == TurnInstruction::UTurn) + turn_candidates[getLeft(0)].instruction = TurnInstruction::TurnSharpLeft; + if (turn_candidates[getRight(0)].instruction == TurnInstruction::UTurn) + turn_candidates[getRight(0)].instruction = TurnInstruction::TurnSharpRight; + } + + const auto keepStraight = [](double angle) + { + return std::abs(angle - 180) < 5; + }; + + for (std::size_t turn_index = 0; turn_index < turn_candidates.size(); ++turn_index) + { + auto &turn = turn_candidates[turn_index]; + if (turn.instruction > TurnInstruction::TurnSlightLeft || + turn.instruction == TurnInstruction::UTurn) + continue; + auto &left = turn_candidates[getLeft(turn_index)]; + if (turn.angle == left.angle) + { + util::SimpleLogger().Write(logDEBUG) + << "[warning] conflicting turn angles, identical road duplicated? " + << m_node_info_list[m_node_based_graph->GetTarget(via_eid)].lat << " " + << m_node_info_list[m_node_based_graph->GetTarget(via_eid)].lon << std::endl; + } + if (isConflict(turn.instruction, left.instruction)) + { + // begin of a conflicting region + std::size_t conflict_begin = turn_index; + std::size_t conflict_end = getLeft(turn_index); + std::size_t conflict_size = 2; + while ( + isConflict(turn_candidates[getLeft(conflict_end)].instruction, turn.instruction) && + conflict_size < turn_candidates.size()) + { + conflict_end = getLeft(conflict_end); + ++conflict_size; + } + + turn_index = (conflict_end < conflict_begin) ? turn_candidates.size() : conflict_end; + + if (conflict_size > 3) + { + // check if some turns are invalid to find out about good handling + } + + auto &instruction_left_of_end = turn_candidates[getLeft(conflict_end)].instruction; + auto &instruction_right_of_begin = + turn_candidates[getRight(conflict_begin)].instruction; + auto &candidate_at_end = turn_candidates[conflict_end]; + auto &candidate_at_begin = turn_candidates[conflict_begin]; + if (conflict_size == 2) + { + if (turn.instruction == TurnInstruction::GoStraight) + { + if (instruction_left_of_end != TurnInstruction::TurnSlightLeft && + instruction_right_of_begin != TurnInstruction::TurnSlightRight) + { + std::int32_t resolved_count = 0; + //uses side-effects in resolve + if (!keepStraight(candidate_at_end.angle) && + !resolve(candidate_at_end.instruction, instruction_left_of_end, + RESOLVE_TO_LEFT)) + util::SimpleLogger().Write(logDEBUG) << "[warning] failed to resolve conflict"; + else + ++resolved_count; + //uses side-effects in resolve + if (!keepStraight(candidate_at_begin.angle) && + !resolve(candidate_at_begin.instruction, instruction_right_of_begin, + RESOLVE_TO_RIGHT)) + util::SimpleLogger().Write(logDEBUG) << "[warning] failed to resolve conflict"; + else + ++resolved_count; + if (resolved_count >= 1 && + (!keepStraight(candidate_at_begin.angle) || + !keepStraight(candidate_at_end.angle))) // should always be the + // case, theoretically + continue; + } + } + if (candidate_at_begin.confidence < candidate_at_end.confidence) + { // if right shift is cheaper, or only option + if (resolve(candidate_at_begin.instruction, instruction_right_of_begin, + RESOLVE_TO_RIGHT)) + continue; + else if (resolve(candidate_at_end.instruction, instruction_left_of_end, + RESOLVE_TO_LEFT)) + continue; + } + else + { + if (resolve(candidate_at_end.instruction, instruction_left_of_end, + RESOLVE_TO_LEFT)) + continue; + else if (resolve(candidate_at_begin.instruction, instruction_right_of_begin, + RESOLVE_TO_RIGHT)) + continue; + } + if (isSlightTurn(turn.instruction) || isSharpTurn(turn.instruction)) + { + auto resolve_direction = + (turn.instruction == TurnInstruction::TurnSlightRight || + turn.instruction == TurnInstruction::TurnSharpLeft) + ? RESOLVE_TO_RIGHT + : RESOLVE_TO_LEFT; + if (resolve_direction == RESOLVE_TO_RIGHT && + resolveTransitive( + candidate_at_begin.instruction, instruction_right_of_begin, + turn_candidates[getRight(getRight(conflict_begin))].instruction, + RESOLVE_TO_RIGHT)) + continue; + else if (resolve_direction == RESOLVE_TO_LEFT && + resolveTransitive( + candidate_at_end.instruction, instruction_left_of_end, + turn_candidates[getLeft(getLeft(conflict_end))].instruction, + RESOLVE_TO_LEFT)) + continue; + } + } + else if (conflict_size >= 3) + { + // a conflict of size larger than three cannot be handled with the current + // model. + // Handle it as best as possible and keep the rest of the conflicting turns + if (conflict_size > 3) + { + NodeID conflict_location = m_node_based_graph->GetTarget(via_eid); + util::SimpleLogger().Write(logDEBUG) + << "[warning] found conflict larget than size three at " + << m_node_info_list[conflict_location].lat << ", " + << m_node_info_list[conflict_location].lon; + } + + if (!resolve(candidate_at_begin.instruction, instruction_right_of_begin, + RESOLVE_TO_RIGHT)) + { + if (isSlightTurn(turn.instruction)) + resolveTransitive( + candidate_at_begin.instruction, instruction_right_of_begin, + turn_candidates[getRight(getRight(conflict_begin))].instruction, + RESOLVE_TO_RIGHT); + else if (isSharpTurn(turn.instruction)) + resolveTransitive( + candidate_at_end.instruction, instruction_left_of_end, + turn_candidates[getLeft(getLeft(conflict_end))].instruction, + RESOLVE_TO_LEFT); + } + if (!resolve(candidate_at_end.instruction, instruction_left_of_end, + RESOLVE_TO_LEFT)) + { + if (isSlightTurn(turn.instruction)) + resolveTransitive( + candidate_at_end.instruction, instruction_left_of_end, + turn_candidates[getLeft(getLeft(conflict_end))].instruction, + RESOLVE_TO_LEFT); + else if (isSharpTurn(turn.instruction)) + resolveTransitive( + candidate_at_begin.instruction, instruction_right_of_begin, + turn_candidates[getRight(getRight(conflict_begin))].instruction, + RESOLVE_TO_RIGHT); + } + } + } + } + return turn_candidates; +} + +bool EdgeBasedGraphFactory::isObviousChoice(EdgeID via_eid, + std::size_t turn_index, + const std::vector &turn_candidates) const +{ + const auto getLeft = [&turn_candidates](std::size_t index) + { + return (index + 1) % turn_candidates.size(); + }; + const auto getRight = [&turn_candidates](std::size_t index) + { + return (index + turn_candidates.size() - 1) % turn_candidates.size(); + }; + const auto &candidate = turn_candidates[turn_index]; + const EdgeData &in_data = m_node_based_graph->GetEdgeData(via_eid); + const EdgeData &out_data = m_node_based_graph->GetEdgeData(candidate.eid); + const auto &candidate_to_the_left = turn_candidates[getLeft(turn_index)]; + + const auto &candidate_to_the_right = turn_candidates[getRight(turn_index)]; + + const auto hasValidRatio = [](const TurnCandidate &left, const TurnCandidate ¢er, + const TurnCandidate &right) + { + auto angle_left = (left.angle > 180) ? angularDeviation(left.angle, STRAIGHT_ANGLE) : 180; + auto angle_right = + (right.angle < 180) ? angularDeviation(right.angle, STRAIGHT_ANGLE) : 180; + auto self_angle = angularDeviation(center.angle, STRAIGHT_ANGLE); + return angularDeviation(center.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE && + ((center.angle < STRAIGHT_ANGLE) + ? (angle_right > self_angle && angle_left / self_angle > DISTINCTION_RATIO) + : (angle_left > self_angle && angle_right / self_angle > DISTINCTION_RATIO)); + }; + // only valid turn + + return turn_candidates.size() == 1 || + // only non u-turn + (turn_candidates.size() == 2 && + candidate_to_the_left.instruction == TurnInstruction::UTurn) || // nearly straight turn + angularDeviation(candidate.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION || + hasValidRatio(candidate_to_the_left, candidate, candidate_to_the_right) || + (in_data.name_id != 0 && in_data.name_id == out_data.name_id && + angularDeviation(candidate.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE / 2); +} + +std::vector +EdgeBasedGraphFactory::suppressTurns(EdgeID via_eid, + std::vector turn_candidates) const +{ + // remove invalid candidates + BOOST_ASSERT_MSG(std::is_sorted(turn_candidates.begin(), turn_candidates.end(), + [](const TurnCandidate &left, const TurnCandidate &right) + { + return left.angle < right.angle; + }), + "Turn Candidates not sorted by angle."); + const auto end_valid = std::remove_if(turn_candidates.begin(), turn_candidates.end(), + [](const TurnCandidate &candidate) + { + return !candidate.valid; + }); + turn_candidates.erase(end_valid, turn_candidates.end()); + + const auto getLeft = [&turn_candidates](std::size_t index) + { + return (index + 1) % turn_candidates.size(); + }; + const auto getRight = [&turn_candidates](std::size_t index) + { + return (index + turn_candidates.size() - 1) % turn_candidates.size(); + }; + + const EdgeData &in_data = m_node_based_graph->GetEdgeData(via_eid); + + bool has_obvious_with_same_name = false; + double obvious_with_same_name_angle = 0; + for (std::size_t turn_index = 0; turn_index < turn_candidates.size(); ++turn_index) + { + if (m_node_based_graph->GetEdgeData(turn_candidates[turn_index].eid).name_id == + in_data.name_id && + isObviousChoice(via_eid, turn_index, turn_candidates)) + { + has_obvious_with_same_name = true; + obvious_with_same_name_angle = turn_candidates[turn_index].angle; + break; + } + } + + for (std::size_t turn_index = 0; turn_index < turn_candidates.size(); ++turn_index) + { + auto &candidate = turn_candidates[turn_index]; + const EdgeData &out_data = m_node_based_graph->GetEdgeData(candidate.eid); + if (candidate.valid && candidate.instruction != TurnInstruction::UTurn) + { + // TODO road category would be useful to indicate obviousness of turn + // check if turn can be omitted or at least changed + const auto &left = turn_candidates[getLeft(turn_index)]; + const auto &right = turn_candidates[getRight(turn_index)]; + + // make very slight instructions straight, if they are the only valid choice going with + // at most a slight turn + if (candidate.instruction < TurnInstruction::ReachViaLocation && + (!isSlightTurn(getTurnDirection(left.angle)) || !left.valid) && + (!isSlightTurn(getTurnDirection(right.angle)) || !right.valid) && + angularDeviation(candidate.angle, STRAIGHT_ANGLE) < FUZZY_STRAIGHT_ANGLE) + candidate.instruction = TurnInstruction::GoStraight; + + // TODO this smaller comparison for turns is DANGEROUS, has to be revised if turn + // instructions change + if (candidate.instruction < TurnInstruction::ReachViaLocation) + { + if (in_data.travel_mode == + out_data.travel_mode) // make sure to always announce mode changes + { + if (isObviousChoice(via_eid, turn_index, turn_candidates)) + { + + if (in_data.name_id == out_data.name_id) // same road + { + candidate.instruction = TurnInstruction::NoTurn; + } + + else if (!has_obvious_with_same_name) + { + // TODO discuss, we might want to keep the current name of the turn. But + // this would mean emitting a turn when you just keep on a road + candidate.instruction = TurnInstruction::NameChanges; + } + else if (candidate.angle < obvious_with_same_name_angle) + candidate.instruction = TurnInstruction::TurnSlightRight; + else + candidate.instruction = TurnInstruction::TurnSlightLeft; + } + else if (candidate.instruction == TurnInstruction::GoStraight && + has_obvious_with_same_name) + { + if (candidate.angle < obvious_with_same_name_angle) + candidate.instruction = TurnInstruction::TurnSlightRight; + else + candidate.instruction = TurnInstruction::TurnSlightLeft; + } + } + } + } + } + return turn_candidates; +} + +std::vector +EdgeBasedGraphFactory::getTurnCandidates(NodeID from_node, EdgeID via_eid) +{ + std::vector turn_candidates; + const NodeID turn_node = m_node_based_graph->GetTarget(via_eid); + const NodeID only_restriction_to_node = + m_restriction_map->CheckForEmanatingIsOnlyTurn(from_node, turn_node); + const bool is_barrier_node = m_barrier_nodes.find(turn_node) != m_barrier_nodes.end(); + + for (const EdgeID onto_edge : m_node_based_graph->GetAdjacentEdgeRange(turn_node)) + { + bool turn_is_valid = true; + if (m_node_based_graph->GetEdgeData(onto_edge).reversed) + { + turn_is_valid = false; + } + const NodeID to_node = m_node_based_graph->GetTarget(onto_edge); + + if (turn_is_valid && (only_restriction_to_node != SPECIAL_NODEID) && + (to_node != only_restriction_to_node)) + { + // We are at an only_-restriction but not at the right turn. + ++restricted_turns_counter; + turn_is_valid = false; + } + + if (turn_is_valid) + { + if (is_barrier_node) + { + if (from_node != to_node) + { + ++skipped_barrier_turns_counter; + turn_is_valid = false; + } + } + else + { + if (from_node == to_node && m_node_based_graph->GetOutDegree(turn_node) > 1) + { + auto number_of_emmiting_bidirectional_edges = 0; + for (auto edge : m_node_based_graph->GetAdjacentEdgeRange(turn_node)) + { + auto target = m_node_based_graph->GetTarget(edge); + auto reverse_edge = m_node_based_graph->FindEdge(target, turn_node); + if (!m_node_based_graph->GetEdgeData(reverse_edge).reversed) + { + ++number_of_emmiting_bidirectional_edges; + } + } + if (number_of_emmiting_bidirectional_edges > 1) + { + ++skipped_uturns_counter; + turn_is_valid = false; + } + } + } + } + + // only add an edge if turn is not a U-turn except when it is + // at the end of a dead-end street + if (m_restriction_map->CheckIfTurnIsRestricted(from_node, turn_node, to_node) && + (only_restriction_to_node == SPECIAL_NODEID) && (to_node != only_restriction_to_node)) + { + // We are at an only_-restriction but not at the right turn. + ++restricted_turns_counter; + turn_is_valid = false; + } + + // unpack first node of second segment if packed + + const auto first_coordinate = + getRepresentativeCoordinate(from_node, turn_node, via_eid, INVERT); + const auto third_coordinate = + getRepresentativeCoordinate(turn_node, to_node, onto_edge, !INVERT); + + const auto angle = util::coordinate_calculation::computeAngle( + first_coordinate, m_node_info_list[turn_node], third_coordinate); + + const auto turn = AnalyzeTurn(from_node, via_eid, turn_node, onto_edge, to_node, angle); + + auto confidence = getTurnConfidence(angle, turn); + if (!turn_is_valid) + confidence *= 0.8; // makes invalid turns more likely to be resolved in conflicts + + turn_candidates.push_back({onto_edge, turn_is_valid, angle, turn, confidence}); + } + + const auto ByAngle = [](const TurnCandidate &first, const TurnCandidate second) + { + return first.angle < second.angle; + }; + std::sort(std::begin(turn_candidates), std::end(turn_candidates), ByAngle); + + const auto getLeft = [&](std::size_t index) + { + return (index + 1) % turn_candidates.size(); + }; + + const auto getRight = [&](std::size_t index) + { + return (index + turn_candidates.size() - 1) % turn_candidates.size(); + }; + + const auto isInvalidEquivalent = [&](std::size_t this_turn, std::size_t valid_turn) + { + if (!turn_candidates[valid_turn].valid || turn_candidates[this_turn].valid) + return false; + + return angularDeviation(turn_candidates[this_turn].angle, + turn_candidates[valid_turn].angle) < NARROW_TURN_ANGLE; + }; + + for (std::size_t index = 0; index < turn_candidates.size(); ++index) + { + if (isInvalidEquivalent(index, getRight(index)) || + isInvalidEquivalent(index, getLeft(index))) + { + turn_candidates.erase(turn_candidates.begin() + index); + --index; + } + } + return turn_candidates; +}; + int EdgeBasedGraphFactory::GetTurnPenalty(double angle, lua_State *lua_state) const { @@ -658,22 +1080,22 @@ int EdgeBasedGraphFactory::GetTurnPenalty(double angle, lua_State *lua_state) co return 0; } +// node_u -- (edge_1) --> node_v -- (edge_2) --> node_w TurnInstruction EdgeBasedGraphFactory::AnalyzeTurn(const NodeID node_u, + const EdgeID edge1, const NodeID node_v, + const EdgeID edge2, const NodeID node_w, const double angle) const { + + const EdgeData &data1 = m_node_based_graph->GetEdgeData(edge1); + const EdgeData &data2 = m_node_based_graph->GetEdgeData(edge2); if (node_u == node_w) { return TurnInstruction::UTurn; } - const EdgeID edge1 = m_node_based_graph->FindEdge(node_u, node_v); - const EdgeID edge2 = m_node_based_graph->FindEdge(node_v, node_w); - - const EdgeData &data1 = m_node_based_graph->GetEdgeData(edge1); - const EdgeData &data2 = m_node_based_graph->GetEdgeData(edge2); - // roundabouts need to be handled explicitely if (data1.roundabout && data2.roundabout) { @@ -700,19 +1122,89 @@ TurnInstruction EdgeBasedGraphFactory::AnalyzeTurn(const NodeID node_u, } } - // If street names stay the same and if we are certain that it is not a - // a segment of a roundabout, we skip it. - if (data1.name_id == data2.name_id && data1.travel_mode == data2.travel_mode) - { - // TODO: Here we should also do a small graph exploration to check for - // more complex situations - if (0 != data1.name_id || m_node_based_graph->GetOutDegree(node_v) <= 2) - { - return TurnInstruction::NoTurn; - } - } - + // assign a designated turn angle instruction purely based on the angle return getTurnDirection(angle); } + +QueryNode EdgeBasedGraphFactory::getRepresentativeCoordinate(const NodeID src, + const NodeID tgt, + const EdgeID via_eid, + bool INVERTED) const +{ + if (m_compressed_edge_container.HasEntryForID(via_eid)) + { + util::FixedPointCoordinate prev = util::FixedPointCoordinate( + m_node_info_list[INVERTED ? tgt : src].lat, + m_node_info_list[INVERTED ? tgt : src].lon), + cur; + // walk along the edge for the first 5 meters + const auto &geometry = m_compressed_edge_container.GetBucketReference(via_eid); + double dist = 0; + double this_dist = 0; + NodeID prev_id = INVERTED ? tgt : src; + + const auto selectBestCandidate = [this](const NodeID current, const double current_distance, + const NodeID previous, + const double previous_distance) + { + if (current_distance < DESIRED_SEGMENT_LENGTH || + current_distance - DESIRED_SEGMENT_LENGTH < + DESIRED_SEGMENT_LENGTH - previous_distance || + previous_distance < MINIMAL_SEGMENT_LENGTH) + { + return m_node_info_list[current]; + } + else + { + return m_node_info_list[previous]; + } + }; + + if (INVERTED) + { + for (auto itr = geometry.rbegin(), end = geometry.rend(); itr != end; ++itr) + { + const auto compressed_node = *itr; + cur = util::FixedPointCoordinate(m_node_info_list[compressed_node.first].lat, + m_node_info_list[compressed_node.first].lon); + this_dist = util::coordinate_calculation::haversineDistance(prev, cur); + if (dist + this_dist > DESIRED_SEGMENT_LENGTH) + { + return selectBestCandidate(compressed_node.first, dist + this_dist, prev_id, + dist); + } + dist += this_dist; + prev = cur; + prev_id = compressed_node.first; + } + cur = util::FixedPointCoordinate(m_node_info_list[src].lat, m_node_info_list[src].lon); + this_dist = util::coordinate_calculation::haversineDistance(prev, cur); + return selectBestCandidate(src, dist + this_dist, prev_id, dist); + } + else + { + for (auto itr = geometry.begin(), end = geometry.end(); itr != end; ++itr) + { + const auto compressed_node = *itr; + cur = util::FixedPointCoordinate(m_node_info_list[compressed_node.first].lat, + m_node_info_list[compressed_node.first].lon); + this_dist = util::coordinate_calculation::haversineDistance(prev, cur); + if (dist + this_dist > DESIRED_SEGMENT_LENGTH) + { + return selectBestCandidate(compressed_node.first, dist + this_dist, prev_id, + dist); + } + dist += this_dist; + prev = cur; + prev_id = compressed_node.first; + } + cur = util::FixedPointCoordinate(m_node_info_list[tgt].lat, m_node_info_list[tgt].lon); + this_dist = util::coordinate_calculation::haversineDistance(prev, cur); + return selectBestCandidate(tgt, dist + this_dist, prev_id, dist); + } + } + // default: If the edge is very short, or we do not have a compressed geometry + return m_node_info_list[INVERTED ? src : tgt]; } -} +} // namespace extractor +} // namespace osrm