From 6c3390f14dc53cd85c9864db8db91d327e5c2c7f Mon Sep 17 00:00:00 2001 From: Moritz Kobitzsch Date: Wed, 15 Feb 2017 15:12:24 +0100 Subject: [PATCH] refactor of post-processing - moves collapse into a dedicated set of functions / files - make collapse scenarios distinct (slight performance cost) - reduce verbosity for short name segments (now actually working, was supposed to do so before) --- features/bicycle/area.feature | 44 +- features/bicycle/bridge.feature | 22 +- features/bicycle/names.feature | 4 +- features/bicycle/restrictions.feature | 1 + features/bicycle/turn_penalty.feature | 15 +- features/car/bridge.feature | 11 +- features/car/names.feature | 10 +- features/car/traffic_turn_penalties.feature | 10 +- features/foot/area.feature | 1 + features/foot/names.feature | 1 + features/foot/restrictions.feature | 1 + features/guidance/anticipate-lanes.feature | 22 +- features/guidance/bugs.feature | 4 +- features/guidance/collapse-detail.feature | 67 +- features/guidance/collapse.feature | 138 ++- .../guidance/dedicated-turn-roads.feature | 33 +- features/guidance/fork.feature | 14 +- features/guidance/intersections.feature | 6 +- features/guidance/low-priority.feature | 10 +- .../guidance/merge-segregated-roads.feature | 46 +- features/guidance/new-name.feature | 2 +- features/guidance/perception.feature | 4 +- features/guidance/ramp.feature | 6 +- features/guidance/roundabout.feature | 6 +- .../guidance/staggered-intersections.feature | 10 +- features/guidance/turn-angles.feature | 46 +- features/guidance/turn-lanes.feature | 39 +- features/guidance/turn.feature | 88 +- features/options/profiles/version0.feature | 2 +- features/options/profiles/version1.feature | 2 +- features/testbot/alternative_loop.feature | 1 + features/testbot/basic.feature | 12 +- features/testbot/bearing.feature | 1 + features/testbot/bearing_param.feature | 22 +- features/testbot/fastest.feature | 1 + features/testbot/ferry.feature | 6 +- features/testbot/loop.feature | 6 +- features/testbot/snap.feature | 11 +- features/testbot/time.feature | 2 +- features/testbot/via.feature | 36 +- features/testbot/weight.feature | 8 +- include/engine/api/route_api.hpp | 7 +- .../guidance/collapse_scenario_detection.hpp | 94 ++ include/engine/guidance/collapse_turns.hpp | 147 +++ .../engine/guidance/collapsing_utility.hpp | 190 ++++ include/engine/guidance/post_processing.hpp | 28 +- .../engine/guidance/verbosity_reduction.hpp | 37 + .../guidance/intersection_normalizer.hpp | 3 +- .../extractor/guidance/sliproad_handler.hpp | 5 + include/util/debug.hpp | 2 +- .../guidance/collapse_scenario_detection.cpp | 405 ++++++++ src/engine/guidance/collapse_turns.cpp | 460 +++++++++ src/engine/guidance/lane_processing.cpp | 7 +- src/engine/guidance/post_processing.cpp | 946 +----------------- src/engine/guidance/verbosity_reduction.cpp | 133 +++ .../guidance/intersection_handler.cpp | 7 +- .../guidance/intersection_normalizer.cpp | 62 +- .../guidance/mergable_road_detector.cpp | 1 + src/extractor/guidance/sliproad_handler.cpp | 24 + 59 files changed, 1992 insertions(+), 1337 deletions(-) create mode 100644 include/engine/guidance/collapse_scenario_detection.hpp create mode 100644 include/engine/guidance/collapse_turns.hpp create mode 100644 include/engine/guidance/collapsing_utility.hpp create mode 100644 include/engine/guidance/verbosity_reduction.hpp create mode 100644 src/engine/guidance/collapse_scenario_detection.cpp create mode 100644 src/engine/guidance/collapse_turns.cpp create mode 100644 src/engine/guidance/verbosity_reduction.cpp diff --git a/features/bicycle/area.feature b/features/bicycle/area.feature index 716f5b75a..ea56e686a 100644 --- a/features/bicycle/area.feature +++ b/features/bicycle/area.feature @@ -71,17 +71,17 @@ Feature: Bike - Squares and other areas | abcda | (nil) | parking | When I route I should get - | from | to | route | - | x | y | xa,abcda,by,by | - | y | x | by,abcda,xa,xa | - | a | b | abcda,abcda | - | a | d | abcda,abcda | - | b | c | abcda,abcda | - | c | b | abcda,abcda | - | c | d | abcda,abcda | - | d | c | abcda,abcda | - | d | a | abcda,abcda | - | a | d | abcda,abcda | + | from | to | route | + | x | y | xa,abcda,by | + | y | x | by,abcda,xa | + | a | b | abcda,abcda | + | a | d | abcda,abcda | + | b | c | abcda,abcda | + | c | b | abcda,abcda | + | c | d | abcda,abcda | + | d | c | abcda,abcda | + | d | a | abcda,abcda | + | a | d | abcda,abcda | @train @platform @@ -99,14 +99,14 @@ Feature: Bike - Squares and other areas | abcda | (nil) | platform | When I route I should get - | from | to | route | - | x | y | xa,abcda,by,by | - | y | x | by,abcda,xa,xa | - | a | b | abcda,abcda | - | a | d | abcda,abcda | - | b | c | abcda,abcda | - | c | b | abcda,abcda | - | c | d | abcda,abcda | - | d | c | abcda,abcda | - | d | a | abcda,abcda | - | a | d | abcda,abcda | + | from | to | route | + | x | y | xa,abcda,by | + | y | x | by,abcda,xa | + | a | b | abcda,abcda | + | a | d | abcda,abcda | + | b | c | abcda,abcda | + | c | b | abcda,abcda | + | c | d | abcda,abcda | + | d | c | abcda,abcda | + | d | a | abcda,abcda | + | a | d | abcda,abcda | diff --git a/features/bicycle/bridge.feature b/features/bicycle/bridge.feature index 424dde94f..a02d586be 100644 --- a/features/bicycle/bridge.feature +++ b/features/bicycle/bridge.feature @@ -7,9 +7,9 @@ Feature: Bicycle - Handle cycling Scenario: Bicycle - Use a ferry route Given the node map """ - a b c - d - e f g + a b c + d + e f g """ And the ways @@ -19,22 +19,22 @@ Feature: Bicycle - Handle cycling | efg | primary | | | When I route I should get - | from | to | route | modes | + | from | to | route | modes | | a | g | abc,cde,efg,efg | cycling,cycling,cycling,cycling | | b | f | abc,cde,efg,efg | cycling,cycling,cycling,cycling | - | e | c | cde,cde | cycling,cycling | + | e | c | cde,cde | cycling,cycling | | e | b | cde,abc,abc | cycling,cycling,cycling | | e | a | cde,abc,abc | cycling,cycling,cycling | - | c | e | cde,cde | cycling,cycling | + | c | e | cde,cde | cycling,cycling | | c | f | cde,efg,efg | cycling,cycling,cycling | | c | g | cde,efg,efg | cycling,cycling,cycling | Scenario: Bicycle - Properly handle durations Given the node map """ - a b c - d - e f g + a b c + d + e f g """ And the ways @@ -45,7 +45,7 @@ Feature: Bicycle - Handle cycling When I route I should get | from | to | route | modes | speed | - | a | g | abc,cde,efg,efg | cycling,cycling,cycling,cycling | 5 km/h | - | b | f | abc,cde,efg,efg | cycling,cycling,cycling,cycling | 4 km/h | + | a | g | abc,cde,efg,efg | cycling,cycling,cycling,cycling | 6 km/h | + | b | f | abc,cde,efg,efg | cycling,cycling,cycling,cycling | 5 km/h | | c | e | cde,cde | cycling,cycling | 2 km/h | | e | c | cde,cde | cycling,cycling | 2 km/h | diff --git a/features/bicycle/names.feature b/features/bicycle/names.feature index 4f59fcb87..b11b54abe 100644 --- a/features/bicycle/names.feature +++ b/features/bicycle/names.feature @@ -17,8 +17,8 @@ Feature: Bike - Street names in instructions | bc | Your Way | A7 | When I route I should get - | from | to | route | ref | - | a | c | My Way,Your Way,Your Way | A6,A7,A7 | + | from | to | route | ref | + | a | c | My Way,Your Way | A6,A7 | @unnamed Scenario: Bike - No longer use way type to describe unnamed ways, see #3231 diff --git a/features/bicycle/restrictions.feature b/features/bicycle/restrictions.feature index 08565c116..828507348 100644 --- a/features/bicycle/restrictions.feature +++ b/features/bicycle/restrictions.feature @@ -5,6 +5,7 @@ Feature: Bike - Turn restrictions Background: Given the profile "bicycle" + Given a grid size of 200 meters @no_turning Scenario: Bike - No left turn diff --git a/features/bicycle/turn_penalty.feature b/features/bicycle/turn_penalty.feature index 3ac9f5f53..fd872a5b8 100644 --- a/features/bicycle/turn_penalty.feature +++ b/features/bicycle/turn_penalty.feature @@ -3,6 +3,7 @@ Feature: Turn Penalties Background: Given the profile "turnbot" + Given a grid size of 200 meters Scenario: Bike - turns should incur a delay that depend on the angle @@ -26,10 +27,10 @@ Feature: Turn Penalties When I route I should get | from | to | route | time | distance | - | s | a | sj,ja,ja | 39s +-1 | 242m +-1 | - | s | b | sj,jb,jb | 30s +-1 | 200m +-1 | - | s | c | sj,jc,jc | 29s +-1 | 242m +-1 | - | s | d | sj,jd,jd | 20s +-1 | 200m +-1 | - | s | e | sj,je,je | 29s +-1 | 242m +-1 | - | s | f | sj,jf,jf | 30s +-1 | 200m +-1 | - | s | g | sj,jg,jg | 39s +-1 | 242m +-1 | + | s | a | sj,ja,ja | 63s +-1 | 483m +-1 | + | s | b | sj,jb,jb | 50s +-1 | 400m +-1 | + | s | c | sj,jc,jc | 54s +-1 | 483m +-1 | + | s | d | sj,jd,jd | 40s +-1 | 400m +-1 | + | s | e | sj,je,je | 53s +-1 | 483m +-1 | + | s | f | sj,jf,jf | 50s +-1 | 400m +-1 | + | s | g | sj,jg,jg | 63s +-1 | 483m +-1 | diff --git a/features/car/bridge.feature b/features/car/bridge.feature index c63ca8e9e..120ec5509 100644 --- a/features/car/bridge.feature +++ b/features/car/bridge.feature @@ -3,6 +3,7 @@ Feature: Car - Handle driving Background: Given the profile "car" + Given a grid size of 200 meters Scenario: Car - Use a ferry route Given the node map @@ -45,10 +46,10 @@ Feature: Car - Handle driving When I route I should get | from | to | route | modes | speed | time | - | a | g | abc,cde,efg,efg | driving,driving,driving,driving | 12 km/h | 173s +-1 | - | b | f | abc,cde,efg,efg | driving,driving,driving,driving | 9 km/h | 162s +-1 | - | c | e | cde,cde | driving,driving | 5 km/h | 146s +-1 | - | e | c | cde,cde | driving,driving | 5 km/h | 149s +-1 | + | a | g | abc,cde,efg,efg | driving,driving,driving,driving | 13 km/h | 340s +-1 | + | b | f | abc,cde,efg,efg | driving,driving,driving,driving | 9 km/h | 318s +-1 | + | c | e | cde,cde | driving,driving | 5 km/h | 295s +-1 | + | e | c | cde,cde | driving,driving | 5 km/h | 295s +-1 | Scenario: Car - Properly handle durations Given the node map @@ -61,7 +62,7 @@ Feature: Car - Handle driving And the ways | nodes | highway | bridge | duration | | abc | primary | | | - | cde | primary | movable | 00:05:00 | + | cde | primary | movable | 00:10:00 | | efg | primary | | | When I route I should get diff --git a/features/car/names.feature b/features/car/names.feature index f0f367e63..2660efe23 100644 --- a/features/car/names.feature +++ b/features/car/names.feature @@ -18,8 +18,8 @@ Feature: Car - Street names in instructions | bc | Your Way | A1 | When I route I should get - | from | to | route | ref | - | a | c | My Way,Your Way,Your Way | ,A1,A1| + | from | to | route | ref | + | a | c | My Way,Your Way | ,A1| Scenario: Car - A named street with pronunciation Given the node map @@ -36,9 +36,9 @@ Feature: Car - Street names in instructions | cd | Your Way | yourewaye | | When I route I should get - | from | to | route | pronunciations | ref | - | a | d | My Way,My Way,My Way | ,meyeway,meyeway | ,A1,A1 | - | 1 | c | Your Way,Your Way | yourewaye,yourewaye | , | + | from | to | route | pronunciations | ref | + | a | d | My Way,My Way | ,meyeway | ,A1 | + | 1 | c | Your Way,Your Way | yourewaye,yourewaye | , | # See #2860 Scenario: Car - same street name but different pronunciation diff --git a/features/car/traffic_turn_penalties.feature b/features/car/traffic_turn_penalties.feature index 7769b1eca..7fcbe197e 100644 --- a/features/car/traffic_turn_penalties.feature +++ b/features/car/traffic_turn_penalties.feature @@ -53,13 +53,13 @@ Feature: Traffic - turn penalties Scenario: Weighting not based on turn penalty file When I route I should get | from | to | route | speed | weight | time | - | a | h | ad,dhk,dhk | 65 km/h | 11s +-1 | 11s +-1 | + | a | h | ad,dhk | 65 km/h | 11s +-1 | 11s +-1 | # straight | i | g | fim,fg,fg | 55 km/h | 13s +-1 | 13s +-1 | # right | a | e | ad,def,def | 44 km/h | 16.3s +-1 | 16.3s +-1 | # left - | c | g | cd,def,fg,fg | 65 km/h | 22s +-1 | 22s +-1 | + | c | g | cd,def,fg | 65 km/h | 22s +-1 | 22s +-1 | # double straight | p | g | mp,fim,fg,fg | 60 km/h | 24s +-1 | 24s +-1 | # straight-right @@ -89,13 +89,13 @@ Feature: Traffic - turn penalties And the contract extra arguments "--turn-penalty-file {penalties_file}" When I route I should get | from | to | route | speed | weight | time | - | a | h | ad,dhk,dhk | 65 km/h | 11 | 11s +-1 | + | a | h | ad,dhk | 65 km/h | 11 | 11s +-1 | # straight | i | g | fim,fg,fg | 56 km/h | 12.8 | 12s +-1 | # right - ifg penalty | a | e | ad,def,def | 67 km/h | 10.8 | 10s +-1 | # left - faster because of negative ade penalty - | c | g | cd,def,fg,fg | 65 km/h | 22 | 22s +-1 | + | c | g | cd,def,fg | 65 km/h | 22 | 22s +-1 | # double straight | p | g | mp,fim,fg,fg | 61 km/h | 23.8 | 23s +-1 | # straight-right - ifg penalty @@ -105,7 +105,7 @@ Feature: Traffic - turn penalties # double right - forced left by lkh penalty | g | n | fg,fim,mn,mn | 28 km/h | 51.8 | 51s +-1 | # double left - imn penalty - | j | c | jk,klm,fim,def,cd,cd | 53 km/h | 54.6 | 54s +-1 | + | j | c | jk,klm,fim,def,cd | 53 km/h | 54.6 | 54s +-1 | # double left - hdc penalty ever so slightly higher than imn; forces all the way around Scenario: Too-negative penalty clamps, but does not fail diff --git a/features/foot/area.feature b/features/foot/area.feature index 53226ee6a..8db2bd048 100644 --- a/features/foot/area.feature +++ b/features/foot/area.feature @@ -3,6 +3,7 @@ Feature: Foot - Squares and other areas Background: Given the profile "foot" + Given a grid size of 200 meters @square Scenario: Foot - Route along edge of a squares diff --git a/features/foot/names.feature b/features/foot/names.feature index 2f1508d1f..61d42dc3c 100644 --- a/features/foot/names.feature +++ b/features/foot/names.feature @@ -3,6 +3,7 @@ Feature: Foot - Street names in instructions Background: Given the profile "foot" + Given a grid size of 200 meters Scenario: Foot - A named street Given the node map diff --git a/features/foot/restrictions.feature b/features/foot/restrictions.feature index 6a691f661..4d63cd537 100644 --- a/features/foot/restrictions.feature +++ b/features/foot/restrictions.feature @@ -4,6 +4,7 @@ Feature: Foot - Turn restrictions Background: Given the profile "foot" + Given a grid size of 200 meters @no_turning Scenario: Foot - No left turn diff --git a/features/guidance/anticipate-lanes.feature b/features/guidance/anticipate-lanes.feature index 07c9b7350..3a51cac26 100644 --- a/features/guidance/anticipate-lanes.feature +++ b/features/guidance/anticipate-lanes.feature @@ -297,19 +297,19 @@ Feature: Turn Lane Guidance """ And the ways - | nodes | turn:lanes:forward | name | - | ab | | main | - | bc | left\|through\|through\|through\|right | main | - | cd | left\|through\|right | main | - | de | | main | - | cf | | off | - | ch | | off | - | dg | | off | - | di | | off | + | nodes | turn:lanes:forward | name | destination | oneway | + | ab | | main | One | yes | + | bc | left\|through\|through\|through\|right | main | One | yes | + | cd | left\|through\|right | main | Two | yes | + | de | | main | Three | yes | + | cf | | off | | yes | + | ch | | off | | yes | + | dg | | off | | yes | + | di | | off | | yes | When I route I should get - | waypoints | route | turns | lanes | - | a,e | main,main,main | depart,use lane straight,arrive | ,left:false straight:false straight:true straight:false right:false, | + | waypoints | route | turns | destinations | lanes | locations | + | a,e | main,main,main | depart,use lane straight,arrive | One,Two,Three | ,left:false straight:false straight:true straight:false right:false, | a,c,e | @anticipate Scenario: Anticipate Lanes for through and collapse multiple use lanes diff --git a/features/guidance/bugs.feature b/features/guidance/bugs.feature index 7f8d5a1d0..aa26cb19e 100644 --- a/features/guidance/bugs.feature +++ b/features/guidance/bugs.feature @@ -33,8 +33,8 @@ Feature: Features related to bugs | e | traffic_signals | When I route I should get - | waypoints | route | turns | - | 1,2 | top,right,right | depart,new name right,arrive | + | waypoints | route | turns | + | 1,2 | top,right | depart,arrive | @3156 Scenario: Incorrect lanes tag diff --git a/features/guidance/collapse-detail.feature b/features/guidance/collapse-detail.feature index d3a8f1565..8b98df9db 100644 --- a/features/guidance/collapse-detail.feature +++ b/features/guidance/collapse-detail.feature @@ -42,8 +42,8 @@ Feature: Collapse | nodes | highway | name | oneway | | abc | primary | road | yes | | dejfg | primary | road | yes | - | fhb | primary_link | | | - | bie | primary_link | | | + | fhb | primary_link | | yes | + | bie | primary_link | | yes | And the nodes | node | highway | @@ -54,7 +54,7 @@ Feature: Collapse When I route I should get | waypoints | route | turns | locations | | a,g | road,road,road | depart,continue uturn,arrive | a,b,g | - | d,c | road,road,road | depart,continue uturn,arrive | d,f,b | + | d,c | road,road,road | depart,continue uturn,arrive | d,f,c | Scenario: Forking before a turn (forky) Given the node map @@ -66,7 +66,7 @@ Feature: Collapse `d. f e """ - # note: check clooapse.feature for a similar test case where we do not + # note: check collapse.feature for a similar test case where we do not # classify the situation as Sliproad and therefore keep the fork inst. And the ways @@ -87,6 +87,63 @@ Feature: Collapse When I route I should get | waypoints | route | turns | locations | | a,g | road,cross,cross | depart,turn left,arrive | a,b,g | - | a,e | road,road,road | depart,continue right,arrive | a,b,e | + | a,e | road,road,road | depart,continue slight right,arrive | a,b,e | # We should discuss whether the next item should be collapsed to depart,turn right,arrive. | a,f | road,road,cross,cross | depart,continue slight right,turn right,arrive | a,b,d,f | + + Scenario: Forking before a turn (forky), larger + Given the node map + """ + g + . + c + .'| + .' | + a . . b .' | + ' . | + ` d + f ' e + """ + + And the ways + | nodes | name | oneway | highway | + | ab | road | yes | primary | + | bd | road | yes | primary | + | bc | road | yes | primary | + | de | road | yes | primary | + | fd | cross | no | secondary | + | dc | cross | no | secondary | + | cg | cross | no | secondary | + + And the relations + | type | way:from | way:to | node:via | restriction | + | restriction | bd | dc | d | no_left_turn | + | restriction | bc | dc | c | no_right_turn | + + When I route I should get + | waypoints | route | turns | locations | + | a,g | road,cross,cross | depart,turn left,arrive | a,b,g | + | a,e | road,road,road | depart,continue slight right,arrive | a,b,e | + # We should discuss whether the next item should be collapsed to depart,turn right,arrive. + | a,f | road,road,cross,cross | depart,continue slight right,turn right,arrive | a,b,d,f | + + Scenario: Pulled Back Turn + Given the node map + """ + d + | + a-b-c + | + e + """ + + And the ways + | nodes | highway | name | + | abc | tertiary | road | + | cd | tertiary | left | + | be | tertiary | right | + + When I route I should get + | waypoints | route | turns | + | a,d | road,left,left | depart,turn left,arrive | + | a,e | road,right,right | depart,turn right,arrive | diff --git a/features/guidance/collapse.feature b/features/guidance/collapse.feature index aabdd1c27..d5106b135 100644 --- a/features/guidance/collapse.feature +++ b/features/guidance/collapse.feature @@ -399,7 +399,9 @@ Feature: Collapse | waypoints | route | turns | | a,d | , | depart,arrive | - Scenario: Crossing Bridge into Segregated Turn + # This scenario could be considered to require a `turn left`. The danger to create random/unwanted instructions + # from a setting like this are just to big, though. Therefore I opted to use `depart,arrive` only + Scenario: Crossing Bridge into Segregated Turn Given the node map """ f @@ -419,8 +421,8 @@ Feature: Collapse | hi | primary | yes | to_bridge | When I route I should get - | waypoints | route | turns | locations | - | a,f | to_bridge,target_road,target_road | depart,turn left,arrive | a,d,f | + | waypoints | route | turns | locations | + | a,f | to_bridge,target_road | depart,arrive | a,f | Scenario: Pankenbruecke Given the node map @@ -438,6 +440,9 @@ Feature: Collapse | | | + | + | + | g """ @@ -529,25 +534,6 @@ Feature: Collapse | waypoints | route | turns | | a,d | road,road | depart,arrive | - Scenario: Pulled Back Turn - Given the node map - """ - d - a b c - e - """ - - And the ways - | nodes | highway | name | - | abc | tertiary | road | - | cd | tertiary | left | - | be | tertiary | right | - - When I route I should get - | waypoints | route | turns | - | a,d | road,left,left | depart,turn left,arrive | - | a,e | road,right,right | depart,turn right,arrive | - Scenario: No Name During Turns, keep important turns Given the node map """ @@ -708,41 +694,6 @@ Feature: Collapse | a,g | road,cross,cross | depart,turn left,arrive | a,b,g | | a,e | road,road | depart,arrive | a,e | - Scenario: Forking before a turn (forky) - Given the node map - """ - g - . - c - a . . b .' - ` d. - f e - """ - # as it is right now we don't classify this as a sliproad, - # check collapse-detail.feature for a similar test case - # which removes the fork here due to it being a Sliproad. - - And the ways - | nodes | name | oneway | highway | - | ab | road | yes | primary | - | bd | road | yes | primary | - | bc | road | yes | primary | - | de | road | yes | primary | - | fd | cross | no | secondary | - | dc | cross | no | secondary | - | cg | cross | no | secondary | - - And the relations - | type | way:from | way:to | node:via | restriction | - | restriction | bd | dc | d | no_left_turn | - | restriction | bc | dc | c | no_right_turn | - - When I route I should get - | waypoints | route | turns | locations | - | a,g | road,cross,cross | depart,fork left,arrive | a,b,g | - | a,e | road,road,road | depart,fork slight right,arrive | a,b,e | - | a,f | road,road,cross,cross | depart,fork slight right,turn right,arrive | a,b,d,f | - Scenario: On-Off on Highway Given the node map """ @@ -857,7 +808,7 @@ Feature: Collapse When I route I should get | waypoints | route | turns | locations | - | a,e | main,main,main | depart,use lane straight,arrive | a,b,e | + | a,e | main,main,main | depart,use lane straight,arrive | a,c,e | Scenario: But _do_ collapse UseLane step when lanes stay the same Given the node map @@ -931,8 +882,8 @@ Feature: Collapse | ej | primary | | off | yes | When I route I should get - | waypoints | route | turns | locations | - | k,j | on,ferry,road,road,ferry,off,off | depart,notification straight,notification straight,continue uturn,turn straight,notification straight,arrive | k,g,a,b,c,d,e,j | + | waypoints | route | turns | locations | + | k,j | on,ferry,road,road,ferry,off,off | depart,notification straight,notification straight,continue uturn,turn straight,notification straight,arrive | k,g,a,b,d,e,j | # http://www.openstreetmap.org/#map=19/37.78090/-122.41251 Scenario: U-Turn onto unnamed-road @@ -956,7 +907,7 @@ Feature: Collapse When I route I should get | waypoints | route | turns | locations | - | a,1 | up,turn,, | depart,turn right,turn sharp right,arrive | a,b,e,f,_ | + | a,1 | up,turn,, | depart,turn right,turn sharp right,arrive | a,b,e,_ | #http://www.openstreetmap.org/#map=19/52.48778/13.30024 Scenario: Hohenzollerdammbrücke @@ -1008,23 +959,21 @@ Feature: Collapse | os | motorway_link | a100 | yes | And the relations - | type | way:from | way:to | node:via | restriction | - | restriction | ck | kh | k | no_right_turn | - | restriction | bk | ki | k | no_left_turn | - | restriction | hl | lc | l | no_right_turn | - | restriction | gl | ld | l | no_left_turn | - | restriction | bc | cm | c | no_right_turn | - | restriction | bc | ck | c | no_left_turn | - | restriction | nc | cm | c | no_left_turn | - | restriction | nc | cd | c | no_right_turn | - | restriction | lc | ck | c | no_left_turn | - | restriction | lc | cd | c | no_right_turn | - | restriction | gh | ho | h | no_right_turn | - | restriction | gh | hl | h | no_left_turn | - | restriction | kh | hi | h | no_left_turn | - | restriction | kh | hl | h | no_right_turn | - | restriction | ph | ho | h | no_left_turn | - | restriction | ph | hi | h | no_right_turn | + | type | way:from | way:to | node:via | restriction | + | restriction | ck | ki | k | only_straight_on | + | restriction | bk | kh | k | only_straight_on | + | restriction | hl | ld | l | only_straight_on | + | restriction | gl | lc | l | only_straight_on | + | restriction | bc | cm | c | no_right_turn | + | restriction | bc | ck | c | no_left_turn | + | restriction | nc | cm | c | no_left_turn | + | restriction | nc | cd | c | no_right_turn | + | restriction | lc | cm | c | only_straight_on | + | restriction | gh | ho | h | no_right_turn | + | restriction | gh | hl | h | no_left_turn | + | restriction | kh | ho | h | only_straight_on | + | restriction | ph | ho | h | no_left_turn | + | restriction | ph | hi | h | no_right_turn | When I route I should get | waypoints | route | turns | locations | @@ -1037,3 +986,36 @@ Feature: Collapse | f,e | | | | | q,j | a100,hohe,hohe | depart,turn right,arrive | q,p,j | | q,e | a100,hohebruecke,hohe | depart,turn left,arrive | q,p,e | + + Scenario: Forking before a turn (forky) + Given the node map + """ + g + . + c + a . . b .' + ` d. + f e + """ + #Check collapse.detail for a similar case (shorter) that does not classify these turns as a sliproad anymore + + And the ways + | nodes | name | oneway | highway | + | ab | road | yes | primary | + | bd | road | yes | primary | + | bc | road | yes | primary | + | de | road | yes | primary | + | fd | cross | no | secondary | + | dc | cross | no | secondary | + | cg | cross | no | secondary | + + And the relations + | type | way:from | way:to | node:via | restriction | + | restriction | bd | dc | d | no_left_turn | + | restriction | bc | dc | c | no_right_turn | + + When I route I should get + | waypoints | route | turns | locations | + | a,g | road,cross,cross | depart,fork left,arrive | a,b,g | + | a,e | road,road,road | depart,fork slight right,arrive | a,b,e | + | a,f | road,road,cross,cross | depart,fork slight right,turn right,arrive | a,b,d,f | diff --git a/features/guidance/dedicated-turn-roads.feature b/features/guidance/dedicated-turn-roads.feature index b509fbbeb..ac1645e26 100644 --- a/features/guidance/dedicated-turn-roads.feature +++ b/features/guidance/dedicated-turn-roads.feature @@ -64,9 +64,10 @@ Feature: Slipways and Dedicated Turn Lanes | type | way:from | way:to | node:via | restriction | | restriction | abc | cfg | c | no_right_turn | - When I route I should get - | waypoints | route | turns | - | a,g | first,,second,second | depart,turn right,turn straight,arrive | + #this is very ugly :(, but we don't have a way to overrule ramps right now, also: this tagging sucks + When I route I should get + | waypoints | route | turns | + | a,g | first,,second,second | depart,off ramp right,turn straight,arrive | Scenario: Turning Sliproad onto a ferry Given the node map @@ -99,9 +100,9 @@ Feature: Slipways and Dedicated Turn Lanes | restriction | abc | cf | c | no_right_turn | When I route I should get - | waypoints | route | turns | - | a,i | first,,second,second,second | depart,turn right,turn straight,notification straight,arrive | - | a,1 | first,, | depart,turn right,arrive | + | waypoints | route | turns | + | a,i | first,,second,second,second | depart,off ramp right,turn straight,notification straight,arrive | + | a,1 | first,, | depart,off ramp right,arrive | Scenario: Turn Instead of Ramp - Max-Speed Given the node map @@ -315,8 +316,8 @@ Feature: Slipways and Dedicated Turn Lanes | qe | secondary_link | Ettlinger Allee | | yes | When I route I should get - | waypoints | route | turns | ref | locations | - | a,o | Schwarzwaldstrasse,Ettlinger Allee,Ettlinger Allee | depart,turn right,arrive | L561,L561, | a,b,o | + | waypoints | route | turns | ref | locations | + | a,o | Schwarzwaldstrasse,Ettlinger Allee,Ettlinger Allee | depart,turn right,arrive | L561,, | a,b,o | Scenario: Traffic Lights everywhere #http://map.project-osrm.org/?z=18¢er=48.995336%2C8.383813&loc=48.995467%2C8.384548&loc=48.995115%2C8.382761&hl=en&alt=0 @@ -341,9 +342,17 @@ Feature: Slipways and Dedicated Turn Lanes And the ways | nodes | highway | name | oneway | - | aklbci | secondary | Ebertstrasse | yes | + | ak | secondary | Ebertstrasse | yes | + | klbc | secondary | Ebertstrasse | yes | + | ci | secondary | Ebertstrasse | yes | | kdeh | secondary_link | | yes | - | jcghf | primary | Brauerstrasse | yes | + | jc | primary | Brauerstrasse | yes | + | cgh | primary | Brauerstrasse | yes | + | hf | primary | Brauerstrasse | yes | + + And the relations + | type | way:from | way:to | node:via | restriction | + | restriction | klbc | cgh | c | no_right_turn | When I route I should get | waypoints | route | turns | locations | @@ -933,5 +942,5 @@ Feature: Slipways and Dedicated Turn Lanes | restriction | bc | cd | c | only_straight | When I route I should get - | waypoints | route | turns | locations | - | a,k | road,,, | depart,continue right,roundabout turn right exit-1,arrive | a,b,h,k | + | waypoints | route | turns | locations | + | a,k | road,,, | depart,turn right,roundabout turn right exit-1,arrive | a,b,h,k | diff --git a/features/guidance/fork.feature b/features/guidance/fork.feature index 4136ba48c..8fd468319 100644 --- a/features/guidance/fork.feature +++ b/features/guidance/fork.feature @@ -39,8 +39,8 @@ Feature: Fork Instructions | bd | primary | yes | When I route I should get - | waypoints | route | turns | - | a,d | ab,bd,bd | depart,new name slight right,arrive | + | waypoints | route | turns | + | a,d | ab,bd | depart,arrive | Scenario: Don't Fork On Single Road Given the node map @@ -56,8 +56,8 @@ Feature: Fork Instructions | bd | primary | yes | turn | When I route I should get - | waypoints | route | turns | - | a,d | road,turn,turn | depart,new name straight,arrive | + | waypoints | route | turns | + | a,d | road,turn | depart,arrive | Scenario: Do not fork on link type Given the node map @@ -382,6 +382,6 @@ Feature: Fork Instructions | ab | on | motorway_link | When I route I should get - | waypoints | route | turns | - | a,j | on,xbcj | depart,arrive | - | a,i | on,off,off | depart,turn slight right,arrive | + | waypoints | route | turns | + | a,j | on,xbcj | depart,arrive | + | a,i | on,off,off | depart,turn right,arrive | diff --git a/features/guidance/intersections.feature b/features/guidance/intersections.feature index fe8b79919..56bc2cab2 100644 --- a/features/guidance/intersections.feature +++ b/features/guidance/intersections.feature @@ -110,9 +110,9 @@ Feature: Intersections Data | cf | corner | When I route I should get - | waypoints | route | intersections | - | a,d | through,through | true:90,true:0 true:90 false:270,true:90 true:180 false:270;true:270 | - | f,a | corner,through,through | true:0;true:90 false:180 true:270,true:0 false:90 true:270;true:90 | + | waypoints | route | intersections | + | a,d | through,through | true:90,true:0 true:90 false:270,true:90 true:180 false:270;true:270 | + | f,a | corner,throughbridge,through | true:0;true:90 false:180 true:270,true:0 false:90 true:270;true:90 | Scenario: Roundabouts Given the node map diff --git a/features/guidance/low-priority.feature b/features/guidance/low-priority.feature index 3bac9abd6..38dd315cc 100644 --- a/features/guidance/low-priority.feature +++ b/features/guidance/low-priority.feature @@ -80,8 +80,8 @@ Feature: Exceptions for routing onto low-priority roads | bc | service | service | When I route I should get - | waypoints | route | turns | - | a,c | road,service,service | depart,new name straight,arrive | + | waypoints | route | turns | + | a,c | road,service | depart,arrive | Scenario: Straight onto low-priority, with driveway Given the node map @@ -114,6 +114,6 @@ Feature: Exceptions for routing onto low-priority roads | bf | driveway | | When I route I should get - | waypoints | route | turns | - | a,c | road, | depart,arrive | - | c,a | ,road,road | depart,new name straight,arrive | + | waypoints | route | turns | + | a,c | road, | depart,arrive | + | c,a | ,road | depart,arrive | diff --git a/features/guidance/merge-segregated-roads.feature b/features/guidance/merge-segregated-roads.feature index 644c669ed..2e0600221 100644 --- a/features/guidance/merge-segregated-roads.feature +++ b/features/guidance/merge-segregated-roads.feature @@ -354,13 +354,13 @@ Feature: Merge Segregated Roads | hb | road | yes | When I route I should get - | waypoints | turns | route | intersections | - | a,f | depart,arrive | road,road | true:180,false:0 true:180,false:0 true:180;true:0 | - | c,f | depart,new name straight,arrive | bridge,road,road | true:180;false:0 true:180;true:0 | - | 1,f | depart,new name straight,arrive | bridge,road,road | true:180;false:0 true:180;true:0 | - | f,a | depart,arrive | road,road | true:0,true:0 false:180,true:0 false:180;true:180 | - | g,a | depart,new name straight,arrive | bridge,road,road | true:0;true:0 false:180;true:180 | - | 2,a | depart,new name straight,arrive | bridge,road,road | true:0;true:0 false:180;true:180 | + | waypoints | turns | route | intersections | + | a,f | depart,arrive | road,road | true:180,false:0 true:180,false:0 true:180;true:0 | + | c,f | depart,arrive | bridge,road | true:180,false:0 true:180;true:0 | + | 1,f | depart,arrive | bridge,road | true:180,false:0 true:180;true:0 | + | f,a | depart,arrive | road,road | true:0,true:0 false:180,true:0 false:180;true:180 | + | g,a | depart,arrive | bridge,road | true:0,true:0 false:180;true:180 | + | 2,a | depart,arrive | bridge,road | true:0,true:0 false:180;true:180 | @negative Scenario: Traffic Circle @@ -501,25 +501,25 @@ Feature: Merge Segregated Roads | jc | vert | yes | | cf | vert | yes | | fl | vert | yes | - | gx | horiz | no | + | xg | horiz | no | | xc | horiz | no | - | fx | horiz | no | + | xf | horiz | no | | xb | horiz | no | And the relations - | type | way:from | way:to | node:via | restriction | - | restriction | bc | cf | c | no_left_turn | - | restriction | fg | gb | g | no_left_turn | - | restriction | cf | fg | f | no_left_turn | - | restriction | gb | bc | b | no_left_turn | - | restriction | xb | bc | b | no_left_turn | - | restriction | xc | cf | c | no_left_turn | - | restriction | xf | fg | f | no_left_turn | - | restriction | xg | gb | g | no_left_turn | + | type | way:from | way:to | node:via | restriction | + | restriction | bc | cf | c | no_left_turn | + | restriction | fg | gb | g | no_left_turn | + | restriction | gb | bc | b | no_left_turn | + | restriction | cf | fg | f | no_left_turn | + | restriction | xb | xf | x | only_straight_on | + | restriction | xf | xb | x | only_straight_on | + | restriction | xg | xc | x | only_straight_on | + | restriction | xc | xg | x | only_straight_on | # the goal here should be not to mention the intersection in the middle at all and also suppress the segregated parts When I route I should get - | waypoints | route | intersections | - | a,l | horiz,vert,vert | true:90;false:0 true:60 true:90 true:180 false:270,true:60 true:120 false:240 true:300,true:0 false:90 false:180 false:240 false:270;true:180 | - | a,d | horiz,horiz | true:90,false:0 true:60 true:90 true:180 false:270,false:0 true:90 false:180 false:270 true:300;true:270 | - | j,h | vert,horiz,horiz | true:0;true:0 true:90 false:180 false:270 true:300,true:60 false:120 true:240 true:300,false:0 false:90 false:120 false:180 true:270;true:90 | - | j,l | vert,vert | true:0,true:0 true:90 false:180 false:270 true:300,true:0 false:90 false:180 true:240 false:270;true:180 | + | waypoints | route | intersections | + | a,l | horiz,vert,vert | true:90;false:0 true:60 true:90 true:180 false:270,true:60 false:120 false:240 false:300,true:0 false:90 false:180 false:240 true:270;true:180 | + | a,d | horiz,horiz | true:90,false:0 true:60 true:90 true:180 false:270,false:0 true:90 false:180 false:270 true:300;true:270 | + | j,h | vert,horiz,horiz | true:0;true:0 true:90 false:180 false:270 true:300,false:60 false:120 false:240 true:300,false:0 false:90 false:120 true:180 true:270;true:90 | + | j,l | vert,vert | true:0,true:0 true:90 false:180 false:270 true:300,true:0 false:90 false:180 true:240 false:270;true:180 | diff --git a/features/guidance/new-name.feature b/features/guidance/new-name.feature index 4f62530ae..65aa38690 100644 --- a/features/guidance/new-name.feature +++ b/features/guidance/new-name.feature @@ -3,7 +3,7 @@ Feature: New-Name Instructions Background: Given the profile "car" - Given a grid size of 100 meters + Given a grid size of 150 meters Scenario: Undisturbed name Change Given the node map diff --git a/features/guidance/perception.feature b/features/guidance/perception.feature index ad027eefa..1758dff6d 100644 --- a/features/guidance/perception.feature +++ b/features/guidance/perception.feature @@ -97,5 +97,5 @@ Feature: Simple Turns | ei | left | yes | When I route I should get - | waypoints | route | - | g,a | in,road,road | + | waypoints | route | turns | + | g,a | in,road,road | depart,fork right,arrive | diff --git a/features/guidance/ramp.feature b/features/guidance/ramp.feature index 426668093..a4e823ded 100644 --- a/features/guidance/ramp.feature +++ b/features/guidance/ramp.feature @@ -233,9 +233,9 @@ Feature: Ramp Guidance | cd | motorway | When I route I should get - | waypoints | route | turns | - | a,d | ac,cd,cd | depart,new name slight left,arrive | - | b,d | bc,cd,cd | depart,new name slight right,arrive | + | waypoints | route | turns | + | a,d | ac,cd | depart,arrive | + | b,d | bc,cd | depart,arrive | Scenario: Two Ramps Joining into common Motorway Unnamed Given the node map diff --git a/features/guidance/roundabout.feature b/features/guidance/roundabout.feature index bfb33cc57..3fd906425 100644 --- a/features/guidance/roundabout.feature +++ b/features/guidance/roundabout.feature @@ -410,10 +410,10 @@ Feature: Basic Roundabout | h | give_way | When I route I should get - | waypoints | route | turns | - # since we cannot handle these invalid roundabout tags yet, we cannout output roundabout taggings. This will hopefully change some day + | waypoints | route | turns | locations | + # since we cannot handle these invalid roundabout tags yet, we cannot output roundabout taggings. This will hopefully change some day #| w,x | ll,egg,egg,tr,tr | depart,roundabout-exit-1,roundabout-exit-2,arrive | - | w,x | ll,egg,egg,tr,tr | depart,turn right,continue left,turn straight,arrive | + | w,x | ll,egg,egg,tr,tr | depart,turn right,continue left,turn straight,arrive | w,b,d,f,x | Scenario: Use Lane in Roundabout Given the node map diff --git a/features/guidance/staggered-intersections.feature b/features/guidance/staggered-intersections.feature index 9d000a926..8857b9345 100644 --- a/features/guidance/staggered-intersections.feature +++ b/features/guidance/staggered-intersections.feature @@ -79,7 +79,7 @@ Feature: Staggered Intersections | a,g | Oak St,Oak St,Oak St | depart,continue uturn,arrive | a,c,g | | g,a | Oak St,Oak St,Oak St | depart,continue uturn,arrive | g,e,a | - Scenario: Staggered Intersection: do not collapse if the names are not the same + Scenario: Staggered Intersection: use new-name if the names are not the same Given the node map """ j @@ -97,9 +97,9 @@ Feature: Staggered Intersections | jcdehi | residential | Cedar Dr | When I route I should get - | waypoints | route | turns | locations | - | a,g | Oak St,Cedar Dr,Elm St,Elm St | depart,turn right,turn left,arrive | a,c,e,g | - | g,a | Elm St,Cedar Dr,Oak St,Oak St | depart,turn right,turn left,arrive | g,e,c,a | + | waypoints | route | turns | locations | + | a,g | Oak St,Elm St | depart,arrive | a,g | + | g,a | Elm St,Oak St | depart,arrive | g,a | Scenario: Staggered Intersection: do not collapse if a mode change is involved Given the node map @@ -107,7 +107,7 @@ Feature: Staggered Intersections j a b c d - e f g + e~~f - - - - g h """ diff --git a/features/guidance/turn-angles.feature b/features/guidance/turn-angles.feature index fb9dae653..23ddaff2d 100644 --- a/features/guidance/turn-angles.feature +++ b/features/guidance/turn-angles.feature @@ -150,7 +150,7 @@ Feature: Simple Turns | nodes | highway | name | lanes | oneway | | akb | primary | road | 4 | yes | | hgi | primary | road | 4 | yes | - | akcdefg | primary_link | | 1 | yes | + | kcdefg | primary_link | | 1 | yes | | gj | tertiary | turn | 1 | | When I route I should get @@ -444,7 +444,7 @@ Feature: Simple Turns When I route I should get | waypoints | route | turns | locations | # | - | g,f | turn,road | depart,arrive | g,f | #could offer an additional turn at `e` if you don't detect the turn in between as curve | + | g,f | turn,road,road | depart,turn left,arrive | g,e,f | #could offer an additional turn at `e` if you don't detect the turn in between as curve | | c,f | road,road,road | depart,continue right,arrive | c,b,f | | #http://www.openstreetmap.org/search?query=52.479264%2013.295617#map=19/52.47926/13.29562 @@ -808,15 +808,15 @@ Feature: Simple Turns | cjk | Friede | no | | tertiary | When I route I should get - | waypoints | route | turns | intersections | - | a,g | Perle,Heide,Heide | depart,turn right,arrive | true:90;true:90 true:180 false:270 true:345;true:18 | - | a,k | Perle,Friede,Friede | depart,turn left,arrive | true:90;true:90 true:180 false:270 true:345;true:153 | - | a,e | Perle,Perle | depart,arrive | true:90,true:90 true:180 false:270 true:345;true:270 | - | e,k | Perle,Friede,Friede | depart,turn right,arrive | true:270;false:90 true:180 true:270 true:345;true:153 | - | e,g | Perle,Heide,Heide | depart,turn left,arrive | true:270;false:90 true:180 true:270 true:345;true:18 | - | h,k | Heide,Friede,Friede | depart,new name straight,arrive | true:16;true:90 true:180 true:270 true:345;true:153 | - | h,e | Heide,Perle,Perle | depart,turn right,arrive | true:16;true:90 true:180 true:270 true:345;true:270 | - | h,a | Heide,Perle,Perle | depart,turn left,arrive | true:16;true:90 true:180 true:270 true:345;true:90 | + | waypoints | route | turns | intersections | + | a,g | Perle,Heide,Heide | depart,turn right,arrive | true:90;true:90 true:180 false:270 true:345;true:18 | + | a,k | Perle,Friede,Friede | depart,turn left,arrive | true:90;true:90 true:180 false:270 true:345;true:153 | + | a,e | Perle,Perle | depart,arrive | true:90,true:90 true:180 false:270 true:345;true:270 | + | e,k | Perle,Friede,Friede | depart,turn right,arrive | true:270;false:90 true:180 true:270 true:345;true:153 | + | e,g | Perle,Heide,Heide | depart,turn left,arrive | true:270;false:90 true:180 true:270 true:345;true:18 | + | h,k | Heide,Friede | depart,arrive | true:16,true:90 true:180 true:270 true:345;true:153 | + | h,e | Heide,Perle,Perle | depart,turn right,arrive | true:16;true:90 true:180 true:270 true:345;true:270 | + | h,a | Heide,Perle,Perle | depart,turn left,arrive | true:16;true:90 true:180 true:270 true:345;true:90 | #http://www.openstreetmap.org/#map=19/52.53293/13.32956 Scenario: Curved Exit from Curved Road @@ -852,9 +852,9 @@ Feature: Simple Turns When I route I should get | waypoints | route | turns | | a,j | Siemens,Siemens,Siemens | depart,continue slight right,arrive | - | a,g | Siemens,Erna,Erna | depart,new name slight left,arrive | + | a,g | Siemens,Erna | depart,arrive | | g,j | Erna,Siemens,Siemens | depart,turn left,arrive | - | g,a | Erna,Siemens,Siemens | depart,new name slight right,arrive | + | g,a | Erna,Siemens | depart,arrive | #http://www.openstreetmap.org/#map=19/52.51303/13.32170 Scenario: Ernst Reuter Platz @@ -1188,12 +1188,12 @@ Feature: Simple Turns | j | traffic_signals | When I route I should get - | waypoints | route | turns | - | a,c | rose,trift,trift | depart,new name slight left,arrive | - | a,k | rose,muhle,muhle | depart,turn slight right,arrive | - | d,f | trift,rose,rose | depart,new name straight,arrive | - | d,k | trift,muhle,muhle | depart,turn sharp left,arrive | - | d,c | trift,trift,trift | depart,continue uturn,arrive | + | waypoints | route | turns | + | a,c | rose,trift | depart,arrive | + | a,k | rose,muhle,muhle | depart,turn slight right,arrive | + | d,f | trift,rose | depart,arrive | + | d,k | trift,muhle,muhle | depart,turn sharp left,arrive | + | d,c | trift,trift,trift | depart,continue uturn,arrive | #http://www.openstreetmap.org/#map=19/52.50740/13.44824 Scenario: Turning Loop at the end of the road @@ -1275,8 +1275,8 @@ Feature: Simple Turns | bcde | 6 | When I route I should get - | waypoints | route | - | a,e | ab,bcde,bcde | + | waypoints | route | + | a,e | ab,bcde | @3401 @@ -1312,8 +1312,8 @@ Feature: Simple Turns # we don't care for turn instructions, this is a coordinate extraction bug check When I route I should get - | waypoints | route | intersections | - | a,g | ab,bcdefgh,bcdefgh | true:90;true:45 false:180 false:270;true:180 | + | waypoints | route | intersections | + | a,g | ab,bcdefgh | true:90,true:45 false:180 false:270;true:180 | #https://github.com/Project-OSRM/osrm-backend/pull/3469#issuecomment-270806580 Scenario: Oszillating Lower Priority Road diff --git a/features/guidance/turn-lanes.feature b/features/guidance/turn-lanes.feature index a1d2fc1a4..db4733420 100644 --- a/features/guidance/turn-lanes.feature +++ b/features/guidance/turn-lanes.feature @@ -3,7 +3,7 @@ Feature: Turn Lane Guidance Background: Given the profile "car" - Given a grid size of 20 meters + Given a grid size of 5 meters @simple Scenario: Basic Turn Lane 3-way Turn with empty lanes @@ -602,6 +602,17 @@ Feature: Turn Lane Guidance a e | | | | + | | + | | + | | + | | + | | + | | + | | + | | + | | + | | + | | b d h c ' -- g - - f @@ -609,8 +620,8 @@ Feature: Turn Lane Guidance And the ways | nodes | name | turn:lanes:forward | oneway | highway | lanes | - | abc | road | left\|left\|left\|through\|through | yes | primary | 5 | - | cde | road | | yes | primary | 3 | + | abc | road | left\|left\|left\|through\|through | yes | primary | 2 | + | cde | road | | yes | primary | 2 | | hc | cross | | yes | secondary | | | cg | straight | | no | tertiary | | | cf | left | | yes | primary | | @@ -808,6 +819,10 @@ Feature: Turn Lane Guidance """ a b e + + + + d c f """ @@ -858,9 +873,9 @@ Feature: Turn Lane Guidance | ab | on | motorway_link | | When I route I should get - | waypoints | route | turns | lanes | - | a,j | on,xbcj | depart,arrive | , | - | a,i | on,off,off | depart,turn slight right,arrive | ,none:false slight right:true, | + | waypoints | route | turns | lanes | + | a,j | on,xbcj | depart,arrive | , | + | a,i | on,off,off | depart,turn right,arrive | ,none:false slight right:true, | #http://www.openstreetmap.org/#map=17/52.47414/13.35712 @todo @ramp @2645 @@ -937,9 +952,9 @@ Feature: Turn Lane Guidance | dce | cross | primary | yes | | 1 | When I route I should get - | waypoints | route | turns | lanes | - | a,g | road,cross,cross | depart,turn right,arrive | ,left:false right:true, | - | a,e | road,cross,cross | depart,turn left,arrive | ,left:true right:false, | + | waypoints | route | turns | lanes | locations | + | a,g | road,cross,cross | depart,turn right,arrive | ,left:false right:true, | a,b,g | + | a,e | road,cross,cross | depart,end of road left,arrive | ,left:true right:false, | a,c,e | Scenario: Partitioned turn, Slight Curve Given the node map @@ -960,9 +975,9 @@ Feature: Turn Lane Guidance | dce | cross | primary | yes | | When I route I should get - | waypoints | route | turns | lanes | - | a,g | road,cross,cross | depart,turn right,arrive | ,left:false right:true, | - | a,e | road,cross,cross | depart,turn left,arrive | ,left:true right:false, | + | waypoints | route | turns | lanes | locations | + | a,g | road,cross,cross | depart,turn right,arrive | ,left:false right:true, | a,b,g | + | a,e | road,cross,cross | depart,end of road left,arrive | ,left:true right:false, | a,c,e | Scenario: Lane Parsing Issue #2694 Given the node map diff --git a/features/guidance/turn.feature b/features/guidance/turn.feature index 12126d8c9..dbc8809ad 100644 --- a/features/guidance/turn.feature +++ b/features/guidance/turn.feature @@ -23,15 +23,15 @@ Feature: Simple Turns When I route I should get | waypoints | route | turns | | a,c | ab,cb,cb | depart,turn left,arrive | - | a,e | ab,eb,eb | depart,new name straight,arrive | + | a,e | ab,eb | depart,arrive | | a,d | ab,db,db | depart,turn right,arrive | | c,a | cb,ab,ab | depart,turn right,arrive | - | c,d | cb,db,db | depart,new name straight,arrive | + | c,d | cb,db | depart,arrive | | c,e | cb,eb,eb | depart,turn left,arrive | | d,a | db,ab,ab | depart,turn left,arrive | - | d,c | db,cb,cb | depart,new name straight,arrive | + | d,c | db,cb | depart,arrive | | d,e | db,eb,eb | depart,turn right,arrive | - | e,a | eb,ab,ab | depart,new name straight,arrive | + | e,a | eb,ab | depart,arrive | | e,c | eb,cb,cb | depart,turn right,arrive | | e,d | eb,db,db | depart,turn left,arrive | @@ -53,15 +53,15 @@ Feature: Simple Turns When I route I should get | waypoints | route | turns | | a,c | ab,cb,cb | depart,turn left,arrive | - | a,e | ab,eb,eb | depart,new name straight,arrive | + | a,e | ab,eb | depart,arrive | | a,d | ab,db,db | depart,turn right,arrive | | c,a | cb,ab,ab | depart,turn right,arrive | - | c,d | cb,db,db | depart,new name straight,arrive | + | c,d | cb,db | depart,arrive | | c,e | cb,eb,eb | depart,turn left,arrive | | d,a | db,ab,ab | depart,turn left,arrive | - | d,c | db,cb,cb | depart,new name straight,arrive | + | d,c | db,cb | depart,arrive | | d,e | db,eb,eb | depart,turn right,arrive | - | e,a | eb,ab,ab | depart,new name straight,arrive | + | e,a | eb,ab | depart,arrive | | e,c | eb,cb,cb | depart,turn right,arrive | | e,d | eb,db,db | depart,turn left,arrive | @@ -86,10 +86,10 @@ Feature: Simple Turns | a,e | abe,abe | depart,arrive | | a,d | abe,db,db | depart,turn right,arrive | | c,a | cb,abe,abe | depart,turn right,arrive | - | c,d | cb,db,db | depart,new name straight,arrive | + | c,d | cb,db | depart,arrive | | c,e | cb,abe,abe | depart,turn left,arrive | | d,a | db,abe,abe | depart,turn left,arrive | - | d,c | db,cb,cb | depart,new name straight,arrive | + | d,c | db,cb | depart,arrive | | d,e | db,abe,abe | depart,turn right,arrive | | e,a | abe,abe | depart,arrive | | e,c | abe,cb,cb | depart,turn right,arrive | @@ -139,9 +139,9 @@ Feature: Simple Turns When I route I should get | waypoints | route | turns | | a,c | ab,cb,cb | depart,turn left,arrive | - | a,d | ab,db,db | depart,new name straight,arrive | + | a,d | ab,db | depart,arrive | | d,c | db,cb,cb | depart,turn right,arrive | - | d,a | db,ab,ab | depart,new name straight,arrive | + | d,a | db,ab | depart,arrive | Scenario: Three Way Intersection - Meeting Oneways Given the node map @@ -208,7 +208,7 @@ Feature: Simple Turns | b,c | ab,ac,ac | depart,turn sharp left,arrive | | b,d | ab,ad,ad | depart,turn left,arrive | | b,e | ab,ae,ae | depart,turn slight left,arrive | - | b,f | ab,af,af | depart,new name straight,arrive | + | b,f | ab,af | depart,arrive | | b,g | ab,ag,ag | depart,turn slight right,arrive | | b,h | ab,ah,ah | depart,turn right,arrive | | b,i | ab,ai,ai | depart,turn sharp right,arrive | @@ -241,7 +241,7 @@ Feature: Simple Turns | b,c | ab,ac,ac | depart,turn sharp left,arrive | | b,d | ab,ad,ad | depart,turn left,arrive | | b,e | ab,ae,ae | depart,turn slight left,arrive | - | b,f | ab,af,af | depart,new name straight,arrive | + | b,f | ab,af | depart,arrive | | b,g | ab,ag,ag | depart,turn slight right,arrive | | b,h | ab,ah,ah | depart,turn right,arrive | | b,i | ab,ai,ai | depart,turn sharp right,arrive | @@ -743,10 +743,10 @@ Feature: Simple Turns | be | primary | no | When I route I should get - | waypoints | route | turns | - | a,c | abc,abc | depart,arrive | - | d,e | db,be,be | depart,new name slight right,arrive | - | e,d | be,db,db | depart,new name slight left,arrive | + | waypoints | route | turns | + | a,c | abc,abc | depart,arrive | + | d,e | db,be | depart,arrive | + | e,d | be,db | depart,arrive | Scenario: Right Turn Assignment Three Conflicting Turns with invalid - 1 Given the node map @@ -904,19 +904,19 @@ Feature: Simple Turns | bd | residential | in | When I route I should get - | waypoints | turns | route | - | a,c | depart,arrive | road,road | - | d,a | depart,turn left,arrive | in,road,road | - | d,c | depart,new name straight,arrive | in,road,road | + | waypoints | turns | route | + | a,c | depart,arrive | road,road | + | d,a | depart,turn left,arrive | in,road,road | + | d,c | depart,arrive | in,road | Scenario: Channing Street Given the node map """ g f - - d c b a - - + | | + d---c-b-a + | | + | | h e """ @@ -1030,9 +1030,9 @@ Feature: Simple Turns | ec | Molkenmarkt | secondary | yes | When I route I should get - | waypoints | turns | route | - | a,d | depart,new name straight,arrive | Molkenmarkt,Stralauer Str,Stralauer Str | - | e,d | depart,new name slight left,arrive | Molkenmarkt,Stralauer Str,Stralauer Str | + | waypoints | turns | route | + | a,d | depart,arrive | Molkenmarkt,Stralauer Str | + | e,d | depart,arrive | Molkenmarkt,Stralauer Str | # http://www.openstreetmap.org/#map=18/39.28158/-76.62291 @3002 @@ -1149,19 +1149,19 @@ Feature: Simple Turns | a,c | in,through,through | depart,turn left,arrive | # http://www.openstreetmap.org/#map=19/52.51556/13.41832 - Scenario: No Slight Right over Jannowitzbruecke + Scenario: No Slight Right at Stralauer Strasse Given the node map """ l m | | f._ | | ' g---h. - | | '. - | | i + | | '-i + | | a_ | | ''.b---c - | |'d._ - | | 'e + | |' d._ + | | 'e j k """ @@ -1175,20 +1175,20 @@ Feature: Simple Turns | kchm | Alexanderstr | primary | yes | When I route I should get - | waypoints | turns | route | - | a,e | depart,new name straight,arrive | Stralauer Str,Holzmarktstr,Holzmarktstr | + | waypoints | turns | route | + | a,e | depart,arrive | Stralauer Str,Holzmarktstr | - Scenario: No Slight Right over Jannowitzbruecke -- less extreme + Scenario: No Slight Right at Stralauer Strasse -- less extreme Given the node map """ l m | | f_ | | - ' 'g h_ + ' 'g---h_ | | '\_ | | i a_ | | - '_ b c_ + '_ b___c_ | | \_ | | e j k @@ -1204,20 +1204,20 @@ Feature: Simple Turns | kchm | Alexanderstr | primary | yes | When I route I should get - | waypoints | turns | route | - | a,e | depart,new name straight,arrive | Stralauer Str,Holzmarktstr,Holzmarktstr | + | waypoints | turns | route | + | a,e | depart,arrive | Stralauer Str,Holzmarktstr | - Scenario: No Slight Right over Jannowitzbruecke + Scenario: No Slight Right at Stralauer Strasse Given the node map """ l m | | | | - _ _ g h_ + _ _ g---h_ f' | | '_ | | i | | - _ _b c__ + _ _b---c__ a' | | 'd | | j k diff --git a/features/options/profiles/version0.feature b/features/options/profiles/version0.feature index 5884b48b1..dce789349 100644 --- a/features/options/profiles/version0.feature +++ b/features/options/profiles/version0.feature @@ -104,4 +104,4 @@ end | from | to | route | time | | a | b | ac,cb,cb | 24.2s | | a | d | ac,cd,cd | 24.2s | - | a | e | ac,ce,ce | 20s | + | a | e | ac,ce | 20s | diff --git a/features/options/profiles/version1.feature b/features/options/profiles/version1.feature index 592e8d4fe..856a5cec4 100644 --- a/features/options/profiles/version1.feature +++ b/features/options/profiles/version1.feature @@ -63,4 +63,4 @@ end | from | to | route | time | | a | b | ac,cb,cb | 19.2s | | a | d | ac,cd,cd | 19.2s | - | a | e | ac,ce,ce | 20s | + | a | e | ac,ce | 20s | diff --git a/features/testbot/alternative_loop.feature b/features/testbot/alternative_loop.feature index d35431224..f8530c661 100644 --- a/features/testbot/alternative_loop.feature +++ b/features/testbot/alternative_loop.feature @@ -3,6 +3,7 @@ Feature: Alternative route Background: Given the profile "testbot" + Given a grid size of 200 meters Scenario: Alternative Loop Paths Given the node map diff --git a/features/testbot/basic.feature b/features/testbot/basic.feature index 7f9674c13..c7da7b243 100644 --- a/features/testbot/basic.feature +++ b/features/testbot/basic.feature @@ -3,6 +3,7 @@ Feature: Basic Routing Background: Given the profile "testbot" + Given a grid size of 100 meters @smallest Scenario: A single way with two nodes @@ -145,7 +146,6 @@ Feature: Basic Routing | c | b | bc,bc | Scenario: 3 connected triangles - Given a grid size of 100 meters Given the node map """ x a b s @@ -178,12 +178,14 @@ Feature: Basic Routing | c | a | ca,ca | | c | b | bc,bc | - Scenario: To ways connected at a 45 degree angle + Scenario: To ways connected at a 90 degree angle Given the node map """ a + | b - c d e + | + c----d----e """ And the ways @@ -270,7 +272,7 @@ Feature: Basic Routing | de | primary | | When I route I should get - | from | to | route | + | from | to | route | | d | c | de,ce,ce | | e | d | de,de | @@ -294,7 +296,7 @@ Feature: Basic Routing Scenario: Ambiguous edge names - Use lexicographically smallest name Given the node map """ - a b c + a-------b-------c """ And the ways diff --git a/features/testbot/bearing.feature b/features/testbot/bearing.feature index 4d99ec589..d74f4bafd 100644 --- a/features/testbot/bearing.feature +++ b/features/testbot/bearing.feature @@ -3,6 +3,7 @@ Feature: Compass bearing Background: Given the profile "testbot" + Given a grid size of 200 meters Scenario: Bearing when going northwest Given the node map diff --git a/features/testbot/bearing_param.feature b/features/testbot/bearing_param.feature index dca67a451..2c6a96701 100644 --- a/features/testbot/bearing_param.feature +++ b/features/testbot/bearing_param.feature @@ -64,17 +64,17 @@ Feature: Bearing parameter | ha | yes | When I route I should get - | from | to | bearings | route | bearing | - | 0 | b | 10 10 | bc,bc | 0->0,0->0 | - | 0 | b | 90 90 | ab,ab | 0->90,90->0 | - | 0 | b | 170 170 | da,da | 0->0,0->0 | - | 0 | b | 189 189 | da,da | 0->0,0->0 | - | 0 | 1 | 90 270 | ab,bc,cd,cd | 0->90,90->0,0->270,270->0 | - | 1 | 2 | 10 10 | bc,bc | 0->0,0->0 | - | 1 | 2 | 90 90 | ab,bc,cd,da,ab,ab | 0->90,90->0,0->270,270->180,180->90,90->0 | - | 1 | 0 | 189 189 | da,da | 0->180,180->0 | - | 1 | 2 | 270 270 | cd,cd | 0->270,270->0 | - | 1 | 2 | 349 349 | | | + | from | to | bearings | route | bearing | + | 0 | b | 10 10 | bc,bc | 0->0,0->0 | + | 0 | b | 90 90 | ab,ab | 0->90,90->0 | + | 0 | b | 170 170 | da,da | 0->0,0->0 | + | 0 | b | 189 189 | da,da | 0->0,0->0 | + | 0 | 1 | 90 270 | ab,cd,cd | 0->90,90->0,270->0 | + | 1 | 2 | 10 10 | bc,bc | 0->0,0->0 | + | 1 | 2 | 90 90 | ab,cd,ab,ab | 0->90,90->0,270->180,90->0 | + | 1 | 0 | 189 189 | da,da | 0->180,180->0 | + | 1 | 2 | 270 270 | cd,cd | 0->270,270->0 | + | 1 | 2 | 349 349 | | | Scenario: Testbot - Initial bearing in all direction Given the node map diff --git a/features/testbot/fastest.feature b/features/testbot/fastest.feature index 72f52c803..0b595bc99 100644 --- a/features/testbot/fastest.feature +++ b/features/testbot/fastest.feature @@ -3,6 +3,7 @@ Feature: Choosing fastest route Background: Given the profile "testbot" + Given a grid size of 200 meters Scenario: Pick the geometrically shortest route, way types being equal Given the node map diff --git a/features/testbot/ferry.feature b/features/testbot/ferry.feature index 1fe98dfde..42401c82d 100644 --- a/features/testbot/ferry.feature +++ b/features/testbot/ferry.feature @@ -168,9 +168,9 @@ Feature: Testbot - Handle ferry routes | defg | | ferry | 0:02 | When I route I should get - | from | to | route | time | - | a | g | xa,xy,yg,yg | 60s +-25% | - | g | a | yg,xy,xa,xa | 60s +-25% | + | from | to | route | time | + | a | g | xa,xy,yg | 60s +-25% | + | g | a | yg,xy,xa | 60s +-25% | Scenario: Testbot - Long winding ferry route Given the node map diff --git a/features/testbot/loop.feature b/features/testbot/loop.feature index 238692271..41a6bb4c0 100644 --- a/features/testbot/loop.feature +++ b/features/testbot/loop.feature @@ -98,6 +98,6 @@ Feature: Avoid weird loops caused by rounding errors | bh | primary | When I route I should get - | waypoints | route | - | a,2,d | ab,be,ef,ef,ef,cf,cd,cd | - | a,1,d | ab,be,ef,ef,ef,cf,cd,cd | + | waypoints | route | + | a,2,d | ab,be,ef,ef,ef,cd,cd | + | a,1,d | ab,be,ef,ef,ef,cd,cd | diff --git a/features/testbot/snap.feature b/features/testbot/snap.feature index eb0c46053..a98d8724e 100644 --- a/features/testbot/snap.feature +++ b/features/testbot/snap.feature @@ -56,10 +56,13 @@ Feature: Snap start/end point to the nearest way Scenario: Snap to edge right under start/end point Given the node map """ - d e f g - c h - b i - a l k j + d e f g + + c h + + b i + + a l k j """ And the ways diff --git a/features/testbot/time.feature b/features/testbot/time.feature index 1bb8e66b1..a7d55a2a5 100644 --- a/features/testbot/time.feature +++ b/features/testbot/time.feature @@ -209,7 +209,7 @@ Feature: Estimation of travel time | from | to | route | time | | b | c | abc,abc | 10s +-1 | | c | e | cde,cde | 60s +-1 | - | b | d | abc,cde,cde | 40s +-1 | + | b | d | abc,cde | 40s +-1 | | a | e | abc,cde,cde | 80s +-1 | Scenario: Time of travel on part of a way diff --git a/features/testbot/via.feature b/features/testbot/via.feature index 90da6191d..b06d1ecfe 100644 --- a/features/testbot/via.feature +++ b/features/testbot/via.feature @@ -79,9 +79,9 @@ Feature: Via points | dh | When I route I should get - | waypoints | route | - | a,c,f | ab,bcd,bcd,bcd,de,efg,efg | - | a,c,f,h | ab,bcd,bcd,bcd,de,efg,efg,efg,gh,gh | + | waypoints | route | + | a,c,f | ab,bcd,bcd,de,efg | + | a,c,f,h | ab,bcd,bcd,de,efg,efg,gh,gh | Scenario: Duplicate via point @@ -124,12 +124,12 @@ Feature: Via points | fa | yes | When I route I should get - | waypoints | route | distance | - | 1,3 | ab,bc,cd,cd | 400m +-1 | - | 3,1 | cd,de,ef,fa,ab,ab | 1000m +-1 | - | 1,2,3 | ab,bc,bc,bc,cd,cd | 400m +-1 | - | 1,3,2 | ab,bc,cd,cd,cd,de,ef,fa,ab,bc,bc | 1600m +-1 | - | 3,2,1 | cd,de,ef,fa,ab,bc,bc,bc,cd,de,ef,fa,ab,ab | 2400m +-1 | + | waypoints | route | distance | + | 1,3 | ab,bc,cd | 400m +-1 | + | 3,1 | cd,de,ef,fa,ab,ab | 1000m +-1 | + | 1,2,3 | ab,bc,bc,cd | 400m +-1 | + | 1,3,2 | ab,bc,cd,cd,de,ef,fa,ab,bc | 1600m +-1 | + | 3,2,1 | cd,de,ef,fa,ab,bc,bc,cd,de,ef,fa,ab,ab | 2400m +-1 | Scenario: Via points on ring on the same oneway # xa it to avoid only having a single ring, which cna trigger edge cases @@ -269,11 +269,11 @@ Feature: Via points | da | yes | When I route I should get - | waypoints | route | distance | - | 2,1 | ab,bc,cd,da,ab,ab | 1100m +-1 | - | 4,3 | bc,cd,da,ab,bc,bc | 1100m +-1 | - | 6,5 | cd,da,ab,bc,cd,cd | 1100m +-1 | - | 8,7 | da,ab,bc,cd,da,da | 1100m +-1 | + | waypoints | route | distance | + | 2,1 | ab,bc,cd,da,ab | 1100m +-1 | + | 4,3 | bc,cd,da,ab,bc | 1100m +-1 | + | 6,5 | cd,da,ab,bc,cd | 1100m +-1 | + | 8,7 | da,ab,bc,cd,da | 1100m +-1 | Scenario: Multiple Via points on ring on the same oneway, forces one of the vertices to be top node Given the node map @@ -293,10 +293,10 @@ Feature: Via points | da | yes | When I route I should get - | waypoints | route | distance | - | 3,2,1 | ab,bc,cd,da,ab,ab,ab,bc,cd,da,ab,ab | 3000m +-1 | - | 6,5,4 | bc,cd,da,ab,bc,bc,bc,cd,da,ab,bc,bc | 3000m +-1 | - | 9,8,7 | cd,da,ab,bc,cd,cd,cd,da,ab,bc,cd,cd | 3000m +-1 | + | waypoints | route | distance | + | 3,2,1 | ab,bc,cd,da,ab,ab,ab,bc,cd,da,ab | 3000m +-1 | + | 6,5,4 | bc,cd,da,ab,bc,bc,bc,cd,da,ab,bc | 3000m +-1 | + | 9,8,7 | cd,da,ab,bc,cd,cd,cd,da,ab,bc,cd | 3000m +-1 | # See issue #2706 # this case is currently broken. It simply works as put here due to staggered intersections triggering a name collapse. diff --git a/features/testbot/weight.feature b/features/testbot/weight.feature index 8306d45f9..7e9c113ea 100644 --- a/features/testbot/weight.feature +++ b/features/testbot/weight.feature @@ -29,12 +29,12 @@ Feature: Weight tests | cde | When I route I should get - | waypoints | route | a:weight | - | s,t | abc,cde,cde | 1.1:2:2:1 | + | waypoints | route | a:weight | + | s,t | abc,cde | 1.1:2:2:1 | When I route I should get - | waypoints | route | times | weight_name | weights | - | s,t | abc,cde,cde | 3.1s,3s,0s | duration | 3.1,3,0 | + | waypoints | route | times | weight_name | weights | + | s,t | abc,cde | 6.1s,0s | duration | 6.1,0 | # FIXME include/engine/guidance/assemble_geometry.hpp:95 Scenario: Start and target on the same and adjacent edge diff --git a/include/engine/api/route_api.hpp b/include/engine/api/route_api.hpp index 11ef41d70..394af1e75 100644 --- a/include/engine/api/route_api.hpp +++ b/include/engine/api/route_api.hpp @@ -12,8 +12,10 @@ #include "engine/guidance/assemble_overview.hpp" #include "engine/guidance/assemble_route.hpp" #include "engine/guidance/assemble_steps.hpp" +#include "engine/guidance/collapse_turns.hpp" #include "engine/guidance/lane_processing.hpp" #include "engine/guidance/post_processing.hpp" +#include "engine/guidance/verbosity_reduction.hpp" #include "engine/internal_route_result.hpp" @@ -165,9 +167,10 @@ class RouteAPI : public BaseAPI */ guidance::trimShortSegments(steps, leg_geometry); - leg.steps = guidance::postProcess(std::move(steps)); - leg.steps = guidance::collapseTurns(std::move(leg.steps)); + leg.steps = guidance::collapseTurnInstructions(std::move(steps)); + leg.steps = guidance::postProcess(std::move(leg.steps)); leg.steps = guidance::buildIntersections(std::move(leg.steps)); + leg.steps = guidance::suppressShortNameSegments(std::move(leg.steps)); leg.steps = guidance::assignRelativeLocations(std::move(leg.steps), leg_geometry, phantoms.source_phantom, diff --git a/include/engine/guidance/collapse_scenario_detection.hpp b/include/engine/guidance/collapse_scenario_detection.hpp new file mode 100644 index 000000000..b2f302339 --- /dev/null +++ b/include/engine/guidance/collapse_scenario_detection.hpp @@ -0,0 +1,94 @@ +#ifndef OSRM_ENGINE_GUIDANCE_COLLAPSE_SCENARIO_DETECTION_HPP_ +#define OSRM_ENGINE_GUIDANCE_COLLAPSE_SCENARIO_DETECTION_HPP_ + +#include "engine/guidance/collapsing_utility.hpp" +#include "engine/guidance/route_step.hpp" + +namespace osrm +{ +namespace engine +{ +namespace guidance +{ + +// check basic collapse preconditions (mode ok, no roundabout types); +bool basicCollapsePreconditions(const RouteStepIterator first, const RouteStepIterator second); + +bool basicCollapsePreconditions(const RouteStepIterator first, + const RouteStepIterator second, + const RouteStepIterator third); + +// Staggered intersection are very short zig-zags of a few meters. +// We do not want to announce these short left-rights or right-lefts: +//  +// * -> b a -> * +// | or | becomes a -> b +// a -> * * -> b +bool isStaggeredIntersection(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); + +// Two two turns following close after another, we can announce them as a U-Turn if both end up +// involving the same (segregated) road. +//  +// b < - y +// | will be represented by at x, turn around instead of turn left at x, turn left at y +// a - > x +bool isUTurn(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); + +// detect oscillating names where a name switch A->B->A occurs. This is often the case due to +// bridges or tunnels. Any such oszillation is not supposed to show up +bool isNameOszillation(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); + +// Sometimes, segments names don't match the perceived turns. We try to detect these additional +// name changes and issue a combined turn. +//  +// | e | +// a - b - c +// d +//  +// can have `a-b` as one name, `b-c-d` as a second. At `b` we would issue a new name, even though +// the road turns right after. The offset would only be there due to the broad road at `e` +bool maneuverPreceededByNameChange(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); +bool maneuverPreceededBySuppressedDirection(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); +bool suppressedStraightBetweenTurns(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_at_center_of_intersection, + const RouteStepIterator step_leaving_intersection); + +bool maneuverSucceededByNameChange(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); +bool maneuverSucceededBySuppressedDirection(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); +bool nameChangeImmediatelyAfterSuppressed(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); +bool closeChoicelessTurnAfterTurn(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); +// if modelled turn roads meet in the center of a segregated intersection, we can end up with double +// choiceless turns +bool doubleChoiceless(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); + +// Due to obvious detection, sometimes we can have straight turns followed by a different turn right +// next to each other. We combine both turns into one, if the second turn is without choice +//  +//  e +// a - b - c +// ' d +//  +// with a main road `abd`, the turn `continue straight` at `b` and `turn left at `c` will become a +// `turn left` at `b` +bool straightTurnFollowedByChoiceless(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection); + +} /* namespace guidance */ +} /* namespace engine */ +} /* namespace osrm */ + +#endif /* OSRM_ENGINE_GUIDANCE_COLLAPSE_SCENARIO_DETECTION_HPP_ */ diff --git a/include/engine/guidance/collapse_turns.hpp b/include/engine/guidance/collapse_turns.hpp new file mode 100644 index 000000000..bcdca07d7 --- /dev/null +++ b/include/engine/guidance/collapse_turns.hpp @@ -0,0 +1,147 @@ +#ifndef OSRM_ENGINE_GUIDANCE_COLLAPSE_HPP + +#include "engine/guidance/route_step.hpp" +#include "util/attributes.hpp" + +#include +#include + +namespace osrm +{ +namespace engine +{ +namespace guidance +{ + +// Multiple possible reasons can result in unnecessary/confusing instructions +// A prime example would be a segregated intersection. Turning around at this +// intersection would result in two instructions to turn left. +// Collapsing such turns into a single turn instruction, we give a clearer +// set of instructionst that is not cluttered by unnecessary turns/name changes. +OSRM_ATTR_WARN_UNUSED +std::vector collapseTurnInstructions(std::vector steps); + +// A combined turn is a set of two instructions that actually form a single turn, as far as we +// perceive it. A u-turn consisting of two left turns is one such example. But there are also lots +// of other items that influence how we combine turns. This function is an entry point, defining the +// possibility to select one of multiple strategies when combining a turn with another one. +template +RouteStep combineRouteSteps(const RouteStep &step_at_turn_location, + const RouteStep &step_after_turn_location, + const CombinedTurnStrategy combined_turn_stragey, + const SignageStrategy signage_strategy); + +// TAGS +// These tags are used to ensure correct strategy usage. Make sure your new strategy is derived from +// (at least) one of these tags. It can only be used for the intended tags, to ensure we don't +// accidently use a lane strategy to cover signage +struct CombineStrategy +{ +}; + +struct SignageStrategy +{ +}; + +struct LaneStrategy +{ +}; + +// Return the step at the turn location, without modification +struct NoModificationStrategy : CombineStrategy, SignageStrategy, LaneStrategy +{ + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; +}; + +// transfer the turn type from the second step +struct TransferTurnTypeStrategy : CombineStrategy +{ + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; +}; + +// Combine both turn and turn angle to a common item +struct AdjustToCombinedTurnAngleStrategy : CombineStrategy +{ + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; +}; + +// Combine only the turn types +struct AdjustToCombinedTurnStrategy : CombineStrategy +{ + AdjustToCombinedTurnStrategy(const RouteStep &step_prior_to_intersection); + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; + + const RouteStep &step_prior_to_intersection; +}; + +// Set a fixed instruction type +struct SetFixedInstructionStrategy : CombineStrategy +{ + SetFixedInstructionStrategy(const extractor::guidance::TurnInstruction instruction); + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; + + const extractor::guidance::TurnInstruction instruction; +}; + +// Handling of staggered intersections +struct StaggeredTurnStrategy : CombineStrategy +{ + StaggeredTurnStrategy(const RouteStep &step_prior_to_intersection); + + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; + + const RouteStep &step_prior_to_intersection; +}; + +// Signage Strategies + +// Transfer the signage from the next step onto this step +struct TransferSignageStrategy : SignageStrategy +{ + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; +}; + +// Lane Strategies + +// Transfer the turn lanes from the intermediate step +struct TransferLanesStrategy : LaneStrategy +{ + void operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const; +}; + +// Pattern to combine a route step using the predefined strategies +template +void combineRouteSteps(RouteStep &step_at_turn_location, + RouteStep &step_after_turn_location, + CombineStrategyClass combined_turn_stragey, + SignageStrategyClass signage_strategy, + LaneStrategyClass lane_strategy) +{ + // assign the combined turn type + static_assert(std::is_base_of::value, + "Supplied Strategy isn't a combine strategy."); + combined_turn_stragey(step_at_turn_location, step_after_turn_location); + + // assign the combind signage + static_assert(std::is_base_of::value, + "Supplied Strategy isn't a signage strategy."); + signage_strategy(step_at_turn_location, step_after_turn_location); + + // assign the desired turn lanes + static_assert(std::is_base_of::value, + "Supplied Strategy isn't a lane strategy."); + lane_strategy(step_at_turn_location, step_after_turn_location); + + // further stuff should happen here as well + step_at_turn_location.ElongateBy(step_after_turn_location); + step_after_turn_location.Invalidate(); +} + +// alias for suppressing a step, using CombineRouteStep with NoModificationStrategy only +void suppressStep(RouteStep &step_at_turn_location, RouteStep &step_after_turn_location); + +} /* namespace guidance */ +} /* namespace osrm */ +} /* namespace osrm */ + +#endif /* OSRM_ENGINE_GUIDANCE_COLLAPSE_HPP_ */ diff --git a/include/engine/guidance/collapsing_utility.hpp b/include/engine/guidance/collapsing_utility.hpp new file mode 100644 index 000000000..895dbd34b --- /dev/null +++ b/include/engine/guidance/collapsing_utility.hpp @@ -0,0 +1,190 @@ +#ifndef OSRM_ENGINE_GUIDANCE_COLLAPSING_UTILITY_HPP_ +#define OSRM_ENGINE_GUIDANCE_COLLAPSING_UTILITY_HPP_ + +#include "extractor/guidance/turn_instruction.hpp" +#include "engine/guidance/route_step.hpp" +#include "util/attributes.hpp" +#include "util/guidance/name_announcements.hpp" + +#include +#include + +using osrm::extractor::guidance::TurnInstruction; +using namespace osrm::extractor::guidance; + +namespace osrm +{ +namespace engine +{ +namespace guidance +{ + +using RouteSteps = std::vector; +using RouteStepIterator = typename RouteSteps::iterator; +const constexpr std::size_t MIN_END_OF_ROAD_INTERSECTIONS = std::size_t{2}; +const constexpr double MAX_COLLAPSE_DISTANCE = 30.0; +// a bit larger than 100 to avoid oscillation in tests +const constexpr double NAME_SEGMENT_CUTOFF_LENGTH = 105.0; + +// check if a step is completely without turn type +inline bool hasTurnType(const RouteStep &step) +{ + return step.maneuver.instruction.type != TurnType::NoTurn; +} +inline bool hasWaypointType(const RouteStep &step) +{ + return step.maneuver.waypoint_type != WaypointType::None; +} + +// skip backwards through possibly disabled turns until we find a turn type (or the first step) +inline RouteStepIterator findPreviousTurn(RouteStepIterator current_step) +{ + BOOST_ASSERT(!hasWaypointType(*current_step)); + // find the first element preceeding the current step that has an actual turn type (not + // necessarily announced) + do + { + // safety to do this loop is asserted in collapseTurnInstructions + --current_step; + } while (!hasTurnType(*current_step) && !hasWaypointType(*current_step)); + return current_step; +} + +// skip forwards over possible NoTurn entries (e.g. ferries) until we find the next instruction with +// a turn type +inline RouteStepIterator findNextTurn(RouteStepIterator current_step) +{ + BOOST_ASSERT(!hasWaypointType(*current_step)); + // find the first element preceeding the current step that has an actual turn type (not + // necessarily announced) + do + { + // safety to do this loop is asserted in collapseTurnInstructions + ++current_step; + } while (!hasTurnType(*current_step) && !hasWaypointType(*current_step)); + return current_step; +} + +// alias for comparisons +inline bool hasTurnType(const RouteStep &step, const TurnType::Enum type) +{ + return type == step.maneuver.instruction.type; +} +// alias for comparisons +inline bool hasModifier(const RouteStep &step, const DirectionModifier::Enum modifier) +{ + return modifier == step.maneuver.instruction.direction_modifier; +} +inline bool hasLanes(const RouteStep &step) +{ + return step.intersections.front().lanes.lanes_in_turn != 0; +} + +// alias for detectors, gives the number of connected roads +inline std::size_t numberOfAvailableTurns(const RouteStep &step) +{ + return step.intersections.front().entry.size(); +} +// alias for detectors, counts only the allowed turns +inline std::size_t numberOfAllowedTurns(const RouteStep &step) +{ + return std::count( + step.intersections.front().entry.begin(), step.intersections.front().entry.end(), true); +} +// traffic lights are very specifically modelled. Sometimes we need to skip them. All checks need to +// fulfill: +inline bool isTrafficLightStep(const RouteStep &step) +{ + return hasTurnType(step, TurnType::Suppressed) && numberOfAvailableTurns(step) == 2 && + numberOfAllowedTurns(step) == 1; +} + +// alias for readability +inline void setInstructionType(RouteStep &step, const TurnType::Enum type) +{ + step.maneuver.instruction.type = type; +} + +// alias for readability +inline bool haveSameMode(const RouteStep &lhs, const RouteStep &rhs) +{ + return lhs.mode == rhs.mode; +} + +// alias for readability +inline bool haveSameMode(const RouteStep &first, const RouteStep &second, const RouteStep &third) +{ + return haveSameMode(first, second) && haveSameMode(second, third); +} + +// alias for readability +inline bool haveSameName(const RouteStep &lhs, const RouteStep &rhs) +{ + // make sure empty is not involved + if (lhs.name_id == EMPTY_NAMEID || rhs.name_id == EMPTY_NAMEID) + return false; + + // easy check to not go over the strings if not necessary + else if (lhs.name_id == rhs.name_id) + return true; + + // ok, bite the sour grape and check the strings already + else + return !util::guidance::requiresNameAnnounced( + lhs.name, lhs.ref, lhs.pronunciation, rhs.name, rhs.ref, rhs.pronunciation); +} + +// alias for readability, both turn right | left +inline bool areSameSide(const RouteStep &lhs, const RouteStep &rhs) +{ + const auto is_left = [](const RouteStep &step) { + return hasModifier(step, DirectionModifier::Straight) || + hasLeftModifier(step.maneuver.instruction); + }; + + const auto is_right = [](const RouteStep &step) { + return hasModifier(step, DirectionModifier::Straight) || + hasRightModifier(step.maneuver.instruction); + }; + + return (is_left(lhs) && is_left(rhs)) || (is_right(lhs) && is_right(rhs)); +} + +// do this after invalidating any steps to compress the step array again +OSRM_ATTR_WARN_UNUSED +inline std::vector removeNoTurnInstructions(std::vector steps) +{ + // finally clean up the post-processed instructions. + // Remove all invalid instructions from the set of instructions. + // An instruction is invalid, if its NO_TURN and has WaypointType::None. + // Two valid NO_TURNs exist in each leg in the form of Depart/Arrive + + // keep valid instructions + const auto not_is_valid = [](const RouteStep &step) { + return step.maneuver.instruction == TurnInstruction::NO_TURN() && + step.maneuver.waypoint_type == WaypointType::None; + }; + + boost::remove_erase_if(steps, not_is_valid); + + // the steps should still include depart and arrive at least + BOOST_ASSERT(steps.size() >= 2); + + BOOST_ASSERT(steps.front().intersections.size() >= 1); + BOOST_ASSERT(steps.front().intersections.front().bearings.size() == 1); + BOOST_ASSERT(steps.front().intersections.front().entry.size() == 1); + BOOST_ASSERT(steps.front().maneuver.waypoint_type == WaypointType::Depart); + + BOOST_ASSERT(steps.back().intersections.size() == 1); + BOOST_ASSERT(steps.back().intersections.front().bearings.size() == 1); + BOOST_ASSERT(steps.back().intersections.front().entry.size() == 1); + BOOST_ASSERT(steps.back().maneuver.waypoint_type == WaypointType::Arrive); + + return steps; +} + +} /* namespace guidance */ +} /* namespace engine */ +} /* namespace osrm */ + +#endif /* OSRM_ENGINE_GUIDANCE_COLLAPSING_UTILITY_HPP_ */ diff --git a/include/engine/guidance/post_processing.hpp b/include/engine/guidance/post_processing.hpp index 1c1d680a6..ace5256b5 100644 --- a/include/engine/guidance/post_processing.hpp +++ b/include/engine/guidance/post_processing.hpp @@ -14,23 +14,11 @@ namespace engine { namespace guidance { + // passed as none-reference to modify in-place and move out again OSRM_ATTR_WARN_UNUSED std::vector postProcess(std::vector steps); -// Multiple possible reasons can result in unnecessary/confusing instructions -// A prime example would be a segregated intersection. Turning around at this -// intersection would result in two instructions to turn left. -// Collapsing such turns into a single turn instruction, we give a clearer -// set of instructionst that is not cluttered by unnecessary turns/name changes. -OSRM_ATTR_WARN_UNUSED -std::vector collapseTurns(std::vector steps); - -// A check whether two instructions can be treated as one. This is only the case for very short -// maneuvers that can, in some form, be seen as one. Lookahead of one step. -// This is only a pre-check and does not necessarily allow collapsing turns!!! -bool collapsable(const RouteStep &step, const RouteStep &next); - // trim initial/final segment of very short length. // This function uses in/out parameter passing to modify both steps and geometry in place. // We use this method since both steps and geometry are closely coupled logically but @@ -49,20 +37,6 @@ std::vector assignRelativeLocations(std::vector steps, OSRM_ATTR_WARN_UNUSED std::vector buildIntersections(std::vector steps); -// remove steps invalidated by post-processing -OSRM_ATTR_WARN_UNUSED -std::vector removeNoTurnInstructions(std::vector steps); - -// remove use lane information that is not actually a turn. For post-processing, we need to -// associate lanes with every turn. Some of these use-lane instructions are not required after lane -// anticipation anymore. This function removes all use lane instructions that are not actually used -// anymore since all lanes going straight are used anyhow. -// FIXME this is currently only a heuristic. We need knowledge on which lanes actually might become -// turn lanes. If a straight lane becomes a turn lane, this might be something to consider. Right -// now we bet on lane-anticipation to catch this. -OSRM_ATTR_WARN_UNUSED -std::vector collapseUseLane(std::vector steps); - // postProcess will break the connection between the leg geometry // for which a segment is supposed to represent exactly the coordinates // between routing maneuvers and the route steps itself. diff --git a/include/engine/guidance/verbosity_reduction.hpp b/include/engine/guidance/verbosity_reduction.hpp new file mode 100644 index 000000000..f41bd2432 --- /dev/null +++ b/include/engine/guidance/verbosity_reduction.hpp @@ -0,0 +1,37 @@ +#ifndef OSRM_ENGINE_GUIDANCE_VERBOSITY_REDUCTION_HPP_ +#define OSRM_ENGINE_GUIDANCE_VERBOSITY_REDUCTION_HPP_ + +#include "engine/guidance/route_step.hpp" +#include "util/attributes.hpp" + +#include + +namespace osrm +{ +namespace engine +{ +namespace guidance +{ + +// Name changes on roads are posing relevant information. However if they are short, we don't want +// to announce them. All these that are not collapsed into a single turn (think segregated +// intersection) have to be checked for the length they are active in. If they are active for a +// short distance only, we don't announce them +OSRM_ATTR_WARN_UNUSED +std::vector suppressShortNameSegments(std::vector steps); + +// remove use lane information that is not actually a turn. For post-processing, we need to +// associate lanes with every turn. Some of these use-lane instructions are not required after lane +// anticipation anymore. This function removes all use lane instructions that are not actually used +// anymore since all lanes going straight are used anyhow. +// FIXME this is currently only a heuristic. We need knowledge on which lanes actually might become +// turn lanes. If a straight lane becomes a turn lane, this might be something to consider. Right +// now we bet on lane-anticipation to catch this. +OSRM_ATTR_WARN_UNUSED +std::vector collapseUseLane(std::vector steps); + +} // namespace guidance +} // namespace engine +} // namespace osrm + +#endif /* OSRM_ENGINE_GUIDANCE_VERBOSITY_REDUCTION_HPP_ */ diff --git a/include/extractor/guidance/intersection_normalizer.hpp b/include/extractor/guidance/intersection_normalizer.hpp index 80d705ebd..3a0fd287c 100644 --- a/include/extractor/guidance/intersection_normalizer.hpp +++ b/include/extractor/guidance/intersection_normalizer.hpp @@ -83,7 +83,8 @@ class IntersectionNormalizer const IntersectionShapeData &source) const; IntersectionShapeData MergeRoads(const IntersectionNormalizationOperation direction, const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs) const; + const IntersectionShapeData &rhs, + const double opposite_bearing) const; // Merge segregated roads to omit invalid turns in favor of treating segregated roads as // one. diff --git a/include/extractor/guidance/sliproad_handler.hpp b/include/extractor/guidance/sliproad_handler.hpp index 82e2e7d0b..e04e2c53d 100644 --- a/include/extractor/guidance/sliproad_handler.hpp +++ b/include/extractor/guidance/sliproad_handler.hpp @@ -65,6 +65,11 @@ class SliproadHandler final : public IntersectionHandler const IntersectionViewData &first, const IntersectionViewData &second) const; + // check if no mode changes are involved + bool allSameMode(const EdgeID in_road, + const EdgeID sliproad_candidate, + const EdgeID target_road) const; + // Could a Sliproad reach this intersection? static bool canBeTargetOfSliproad(const IntersectionView &intersection); diff --git a/include/util/debug.hpp b/include/util/debug.hpp index 45bc6a00f..3c5629291 100644 --- a/include/util/debug.hpp +++ b/include/util/debug.hpp @@ -46,7 +46,7 @@ inline void print(const engine::guidance::RouteStep &step) std::cout << ")"; } std::cout << "] name[" << step.name_id << "]: " << step.name << " Ref: " << step.ref - << " Pronunciation: " << step.pronunciation; + << " Pronunciation: " << step.pronunciation << "Destination: " << step.destinations; } inline void print(const std::vector &steps) diff --git a/src/engine/guidance/collapse_scenario_detection.cpp b/src/engine/guidance/collapse_scenario_detection.cpp new file mode 100644 index 000000000..2f26ed190 --- /dev/null +++ b/src/engine/guidance/collapse_scenario_detection.cpp @@ -0,0 +1,405 @@ +#include "engine/guidance/collapse_scenario_detection.hpp" +#include "extractor/guidance/constants.hpp" +#include "util/bearing.hpp" + +#include + +#include + +namespace osrm +{ +namespace engine +{ +namespace guidance +{ + +namespace +{ + +// check bearings for u-turns. +// since bearings are wrapped around at 0 (we only support 0,360), we need to do some minor math to +// check if bearings `a` and `b` go in opposite directions. In general we accept some minor +// deviations for u-turns. +bool bearingsAreReversed(const double bearing_in, const double bearing_out) +{ + // Nearly perfectly reversed angles have a difference close to 180 degrees (straight) + const double left_turn_angle = [&]() { + if (0 <= bearing_out && bearing_out <= bearing_in) + return bearing_in - bearing_out; + return bearing_in + 360 - bearing_out; + }(); + return util::angularDeviation(left_turn_angle, 180) <= 35; +} + +// to collapse steps, we focus on short segments that don't interact with other roads. To collapse +// two instructions into one, we need to look at to instrutions immediately after each other. +bool noIntermediaryIntersections(const RouteStep &step) +{ + return std::all_of(step.intersections.begin() + 1, + step.intersections.end(), + [](const auto &intersection) { return intersection.entry.size() == 2; }); +} + +// Link roads, as far as we are concerned, are short unnamed segments between to named segments. +bool isLinkroad(const RouteStep &pre_link_step, + const RouteStep &link_step, + const RouteStep &post_link_step) +{ + const constexpr double MAX_LINK_ROAD_LENGTH = 2 * MAX_COLLAPSE_DISTANCE; + const auto is_short = link_step.distance <= MAX_LINK_ROAD_LENGTH; + const auto unnamed = link_step.name_id == EMPTY_NAMEID; + const auto between_named = + (pre_link_step.name_id != EMPTY_NAMEID) && (post_link_step.name_id != EMPTY_NAMEID); + + return is_short && unnamed && between_named && noIntermediaryIntersections(link_step); +} + +// Just like a link step, but shorter and no name restrictions. +bool isShortAndUndisturbed(const RouteStep &step) +{ + const auto is_short = step.distance <= MAX_COLLAPSE_DISTANCE; + return is_short && noIntermediaryIntersections(step); +} + +} // namespace + +bool basicCollapsePreconditions(const RouteStepIterator first, const RouteStepIterator second) +{ + const auto has_roundabout_type = hasRoundaboutType(first->maneuver.instruction) || + hasRoundaboutType(second->maneuver.instruction); + + const auto waypoint_type = hasWaypointType(*first) || hasWaypointType(*second); + + return !has_roundabout_type && !waypoint_type && haveSameMode(*first, *second); +} + +bool basicCollapsePreconditions(const RouteStepIterator first, + const RouteStepIterator second, + const RouteStepIterator third) +{ + const auto has_roundabout_type = hasRoundaboutType(first->maneuver.instruction) || + hasRoundaboutType(second->maneuver.instruction) || + hasRoundaboutType(third->maneuver.instruction); + + // require modes to match up + return !has_roundabout_type && haveSameMode(*first, *second, *third); +} + +bool isStaggeredIntersection(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + BOOST_ASSERT(!hasWaypointType(*step_entering_intersection) && + !(hasWaypointType(*step_leaving_intersection))); + // don't touch roundabouts + if (entersRoundabout(step_entering_intersection->maneuver.instruction) || + entersRoundabout(step_leaving_intersection->maneuver.instruction)) + return false; + // Base decision on distance since the zig-zag is a visual clue. + // If adjusted, make sure to check validity of the is_right/is_left classification below + const constexpr auto MAX_STAGGERED_DISTANCE = 3; // debatable, but keep short to be on safe side + + const auto angle = [](const RouteStep &step) { + const auto &intersection = step.intersections.front(); + const auto entry_bearing = intersection.bearings[intersection.in]; + const auto exit_bearing = intersection.bearings[intersection.out]; + return util::bearing::angleBetween(entry_bearing, exit_bearing); + }; + + // Instead of using turn modifiers (e.g. as in isRightTurn) we want to be more strict here. + // We do not want to trigger e.g. on sharp uturn'ish turns or going straight "turns". + // Therefore we use the turn angle to derive 90 degree'ish right / left turns. + // This more closely resembles what we understand as Staggered Intersection. + // We have to be careful in cases with larger MAX_STAGGERED_DISTANCE values. If the distance + // gets large, sharper angles might be not obvious enough to consider them a staggered + // intersection. We might need to consider making the decision here dependent on the actual turn + // angle taken. To do so, we could scale the angle-limits by a factor depending on the distance + // between the turns. + const auto is_right = [](const double angle) { return angle > 45 && angle < 135; }; + const auto is_left = [](const double angle) { return angle > 225 && angle < 315; }; + + const auto left_right = + is_left(angle(*step_entering_intersection)) && is_right(angle(*step_leaving_intersection)); + const auto right_left = + is_right(angle(*step_entering_intersection)) && is_left(angle(*step_leaving_intersection)); + + // A RouteStep holds distance/duration from the maneuver to the subsequent step. + // We are only interested in the distance between the first and the second. + const auto is_short = step_entering_intersection->distance < MAX_STAGGERED_DISTANCE; + const auto intermediary_mode_change = + step_prior_to_intersection->mode == step_leaving_intersection->mode && + step_entering_intersection->mode != step_leaving_intersection->mode; + + const auto mode_change_when_entering = + step_prior_to_intersection->mode != step_entering_intersection->mode; + + // previous step maneuver intersections should be length 1 to indicate that + // there are no intersections between the two potentially collapsible turns + return is_short && (left_right || right_left) && !intermediary_mode_change && + !mode_change_when_entering && noIntermediaryIntersections(*step_entering_intersection); +} + +bool isUTurn(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions( + step_prior_to_intersection, step_entering_intersection, step_leaving_intersection)) + return false; + + // the most basic condition for a uturn is that we actually turn around + const bool takes_u_turn = bearingsAreReversed( + util::bearing::reverse(step_entering_intersection->intersections.front() + .bearings[step_entering_intersection->intersections.front().in]), + step_leaving_intersection->intersections.front() + .bearings[step_leaving_intersection->intersections.front().out]); + + if (!takes_u_turn) + return false; + + // TODO check for name match after additional step + const auto names_match = haveSameName(*step_prior_to_intersection, *step_leaving_intersection); + + // names within a u-turn road have to match from entry step to exit step + if (!names_match) + return false; + + const auto collapsable = isShortAndUndisturbed(*step_entering_intersection); + const auto only_allowed_turn = (numberOfAllowedTurns(*step_leaving_intersection) == 1) && + noIntermediaryIntersections(*step_entering_intersection); + + return collapsable || isLinkroad(*step_prior_to_intersection, + *step_entering_intersection, + *step_leaving_intersection) || + only_allowed_turn; +} + +bool isNameOszillation(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions( + step_prior_to_intersection, step_entering_intersection, step_leaving_intersection)) + return false; + + const auto are_name_changes = + (hasTurnType(*step_entering_intersection, TurnType::NewName) || + (hasTurnType(*step_entering_intersection, TurnType::Turn) && + hasModifier(*step_entering_intersection, DirectionModifier::Straight))) && + (hasTurnType(*step_leaving_intersection, TurnType::NewName) || + (hasTurnType(*step_leaving_intersection, TurnType::Suppressed) && + step_leaving_intersection->name_id == EMPTY_NAMEID) || + (hasTurnType(*step_leaving_intersection, TurnType::Turn) && + hasModifier(*step_leaving_intersection, DirectionModifier::Straight))); + + if (!are_name_changes) + return false; + + const auto names_match = + // accept empty names as well as same names + step_prior_to_intersection->name_id == step_leaving_intersection->name_id || + haveSameName(*step_prior_to_intersection, *step_leaving_intersection); + return names_match; +} + +bool maneuverPreceededByNameChange(const RouteStepIterator step_prior_to_intersection, + const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions( + step_prior_to_intersection, step_entering_intersection, step_leaving_intersection)) + return false; + + const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection); + const auto is_name_change = + hasTurnType(*step_entering_intersection, TurnType::NewName) || + ((hasTurnType(*step_entering_intersection, TurnType::Turn) || + hasTurnType(*step_entering_intersection, TurnType::Continue)) && + hasModifier(*step_entering_intersection, DirectionModifier::Straight)); + + const auto followed_by_maneuver = + hasTurnType(*step_leaving_intersection) && + !hasTurnType(*step_leaving_intersection, TurnType::Suppressed); + + return short_and_undisturbed && is_name_change && followed_by_maneuver; +} + +bool maneuverPreceededBySuppressedDirection(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection)) + return false; + + const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection); + + const auto is_suppressed_direction = + hasTurnType(*step_entering_intersection, TurnType::Suppressed) && + !hasModifier(*step_entering_intersection, DirectionModifier::Straight); + + const auto followed_by_maneuver = + hasTurnType(*step_leaving_intersection) && + !hasTurnType(*step_leaving_intersection, TurnType::Suppressed); + + const auto keeps_direction = + areSameSide(*step_entering_intersection, *step_leaving_intersection); + + const auto has_choice = numberOfAllowedTurns(*step_entering_intersection) > 1; + + return short_and_undisturbed && has_choice && is_suppressed_direction && followed_by_maneuver && + keeps_direction; +} + +bool suppressedStraightBetweenTurns(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_at_center_of_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions( + step_entering_intersection, step_at_center_of_intersection, step_leaving_intersection)) + return false; + + const auto both_short_enough = + step_entering_intersection->distance < 0.8 * MAX_COLLAPSE_DISTANCE && + step_at_center_of_intersection->distance < 0.8 * MAX_COLLAPSE_DISTANCE; + + const auto similar_length = + (step_entering_intersection->distance < 5 && + step_at_center_of_intersection->distance < 5) || + std::min(step_entering_intersection->distance, step_at_center_of_intersection->distance) / + std::max(step_entering_intersection->distance, + step_at_center_of_intersection->distance) > + 0.75; + + const auto correct_types = + hasTurnType(*step_at_center_of_intersection, TurnType::Suppressed) && + hasModifier(*step_at_center_of_intersection, DirectionModifier::Straight) && + (hasTurnType(*step_entering_intersection, TurnType::Turn) || + hasTurnType(*step_entering_intersection, TurnType::Continue)) && + (hasTurnType(*step_leaving_intersection, TurnType::Turn) || + hasTurnType(*step_leaving_intersection, TurnType::Continue) || + hasTurnType(*step_leaving_intersection, TurnType::OnRamp)); + + return both_short_enough && similar_length && correct_types; +} + +bool maneuverSucceededByNameChange(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection)) + return false; + + const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection); + const auto followed_by_name_change = + hasTurnType(*step_leaving_intersection, TurnType::NewName) || + ((hasTurnType(*step_leaving_intersection, TurnType::Turn) || + hasTurnType(*step_leaving_intersection, TurnType::Continue)) && + hasModifier(*step_leaving_intersection, DirectionModifier::Straight)); + + const auto is_maneuver = hasTurnType(*step_entering_intersection) && + !hasTurnType(*step_entering_intersection, TurnType::Suppressed); + + // a straight name change can overrule max collapse distance + const auto is_strong_name_change = + hasTurnType(*step_leaving_intersection, TurnType::NewName) && + hasModifier(*step_leaving_intersection, DirectionModifier::Straight) && + step_entering_intersection->distance <= 1.5 * MAX_COLLAPSE_DISTANCE; + + // also allow a bit more, if the new name is without choice + const auto is_choiceless_name_change = + hasTurnType(*step_leaving_intersection, TurnType::NewName) && + numberOfAllowedTurns(*step_leaving_intersection) == 1 && + step_entering_intersection->distance <= 1.5 * MAX_COLLAPSE_DISTANCE; + + return (short_and_undisturbed || is_strong_name_change || is_choiceless_name_change) && + followed_by_name_change && is_maneuver; +} + +bool maneuverSucceededBySuppressedDirection(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection)) + return false; + + const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection); + + const auto followed_by_suppressed_direction = + hasTurnType(*step_leaving_intersection, TurnType::Suppressed) && + !hasModifier(*step_leaving_intersection, DirectionModifier::Straight); + + const auto is_maneuver = hasTurnType(*step_entering_intersection) && + !hasTurnType(*step_entering_intersection, TurnType::Suppressed); + + const auto keeps_direction = + areSameSide(*step_entering_intersection, *step_leaving_intersection); + + return short_and_undisturbed && followed_by_suppressed_direction && is_maneuver && + keeps_direction; +} + +bool nameChangeImmediatelyAfterSuppressed(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection)) + return false; + + const auto very_short = step_entering_intersection->distance < 0.25 * MAX_COLLAPSE_DISTANCE; + const auto correct_types = hasTurnType(*step_entering_intersection, TurnType::Suppressed) && + hasTurnType(*step_leaving_intersection, TurnType::NewName); + + return very_short && correct_types; +} + +bool closeChoicelessTurnAfterTurn(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection)) + return false; + + const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection); + const auto is_turn = !hasModifier(*step_entering_intersection, DirectionModifier::Straight); + + const auto followed_by_choiceless = numberOfAllowedTurns(*step_leaving_intersection) == 1; + const auto followed_by_suppressed = + hasTurnType(*step_leaving_intersection, TurnType::Suppressed); + + return short_and_undisturbed && is_turn && followed_by_choiceless && !followed_by_suppressed; +} + +bool doubleChoiceless(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection)) + return false; + + const auto double_choiceless = + (numberOfAllowedTurns(*step_leaving_intersection) == 1) && + (step_entering_intersection->intersections.size() == 2) && + (std::count(step_entering_intersection->intersections.back().entry.begin(), + step_entering_intersection->intersections.back().entry.end(), + true) == 1); + + const auto short_enough = step_entering_intersection->distance < 1.5 * MAX_COLLAPSE_DISTANCE; + + return double_choiceless && short_enough; +} + +bool straightTurnFollowedByChoiceless(const RouteStepIterator step_entering_intersection, + const RouteStepIterator step_leaving_intersection) +{ + if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection)) + return false; + + const auto is_short = step_entering_intersection->distance <= 2 * MAX_COLLAPSE_DISTANCE; + const auto has_correct_type = hasTurnType(*step_entering_intersection, TurnType::Continue) || + hasTurnType(*step_entering_intersection, TurnType::Turn); + + const auto is_straight = hasModifier(*step_entering_intersection, DirectionModifier::Straight); + + const auto only_choice = numberOfAllowedTurns(*step_leaving_intersection) == 1; + + return is_short && has_correct_type && is_straight && only_choice && + noIntermediaryIntersections(*step_entering_intersection); +} + +} /* namespace guidance */ +} /* namespace engine */ +} /* namespace osrm */ diff --git a/src/engine/guidance/collapse_turns.cpp b/src/engine/guidance/collapse_turns.cpp new file mode 100644 index 000000000..971c6c39e --- /dev/null +++ b/src/engine/guidance/collapse_turns.cpp @@ -0,0 +1,460 @@ +#include "engine/guidance/collapse_turns.hpp" +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/turn_instruction.hpp" +#include "engine/guidance/collapse_scenario_detection.hpp" +#include "engine/guidance/collapsing_utility.hpp" +#include "util/bearing.hpp" +#include "util/guidance/name_announcements.hpp" + +#include + +#include + +using osrm::extractor::guidance::TurnInstruction; +using osrm::util::angularDeviation; +using namespace osrm::extractor::guidance; + +namespace osrm +{ +namespace engine +{ +namespace guidance +{ + +namespace +{ +const constexpr double MAX_COLLAPSE_DISTANCE = 30; + +// find the combined turn angle for two turns. Not in all scenarios we can easily add both angles +// (e.g 90 degree left followed by 90 degree right would be no turn at all). +double findTotalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_step) +{ + if (entry_step.geometry_begin > exit_step.geometry_begin) + return findTotalTurnAngle(exit_step, entry_step); + + const auto exit_intersection = exit_step.intersections.front(); + const auto exit_step_exit_bearing = exit_intersection.bearings[exit_intersection.out]; + const auto exit_step_entry_bearing = + util::bearing::reverse(exit_intersection.bearings[exit_intersection.in]); + + const auto entry_intersection = entry_step.intersections.front(); + const auto entry_step_entry_bearing = + util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]); + const auto entry_step_exit_bearing = entry_intersection.bearings[entry_intersection.out]; + + const auto exit_angle = + util::bearing::angleBetween(exit_step_entry_bearing, exit_step_exit_bearing); + const auto entry_angle = + util::bearing::angleBetween(entry_step_entry_bearing, entry_step_exit_bearing); + + const double total_angle = + util::bearing::angleBetween(entry_step_entry_bearing, exit_step_exit_bearing); + + // both angles are in the same direction, the total turn gets increased + //  + // a ---- b + // \ + // c + // | + // d + // + // Will be considered just like + //  + // a -----b + // | + // c + // | + // d + const auto use_total_angle = [&]() { + // only consider actual turns in combination: + if (angularDeviation(total_angle, 180) < 0.5 * NARROW_TURN_ANGLE) + return false; + + // entry step is short and the exit and the exit step does not have intersections?? + if (entry_step.distance < MAX_COLLAPSE_DISTANCE) + return true; + + if (entry_step.distance > 2 * MAX_COLLAPSE_DISTANCE) + return false; + + // both go roughly in the same direction + if ((entry_angle <= 185 && exit_angle <= 185) || (entry_angle >= 175 && exit_angle >= 175)) + return true; + + return false; + }(); + + // We allow for minor deviations from a straight line + if (use_total_angle) + { + return total_angle; + } + else + { + // to prevent ignoring angles like + //  + // a -- b + // | + // c -- d + //  + // We don't combine both turn angles here but keep the very first turn angle. + // We choose the first one, since we consider the first maneuver in a merge range the + // important one + return entry_angle; + } +} + +inline void handleSliproad(RouteStepIterator sliproad_step) +{ + // find the next step after the sliproad step itself (this is not necessarily the next step, + // since we might have to skip over traffic lights/node penalties) + auto next_step = [&sliproad_step]() { + auto next_step = findNextTurn(sliproad_step); + while (isTrafficLightStep(*next_step)) + { + // in sliproad checks, we should have made sure not to include invalid modes + BOOST_ASSERT(haveSameMode(*sliproad_step, *next_step)); + sliproad_step->ElongateBy(*next_step); + next_step->Invalidate(); + next_step = findNextTurn(next_step); + } + BOOST_ASSERT(haveSameMode(*sliproad_step, *next_step)); + return next_step; + }(); + + // have we reached the end? + if (hasWaypointType(*next_step)) + { + setInstructionType(*sliproad_step, TurnType::Turn); + } + else + { + const auto previous_step = findPreviousTurn(sliproad_step); + const auto connecting_same_name_roads = haveSameName(*previous_step, *next_step); + auto sliproad_turn_type = connecting_same_name_roads ? TurnType::Continue : TurnType::Turn; + setInstructionType(*sliproad_step, sliproad_turn_type); + combineRouteSteps(*sliproad_step, + *next_step, + AdjustToCombinedTurnAngleStrategy(), + TransferSignageStrategy(), + TransferLanesStrategy()); + } +} + +} // namespace + +// STRATEGIES + +// keep signage/other entries in route step intact +void NoModificationStrategy::operator()(RouteStep &, const RouteStep &) const +{ + // actually do nothing. +} + +// transfer turn type from a different turn +void TransferTurnTypeStrategy::operator()(RouteStep &step_at_turn_location, + const RouteStep &transfer_from_step) const +{ + step_at_turn_location.maneuver = transfer_from_step.maneuver; +} + +void AdjustToCombinedTurnAngleStrategy::operator()(RouteStep &step_at_turn_location, + const RouteStep &transfer_from_step) const +{ + // TODO assert transfer_from_step == step_at_turn_location + 1 + const auto angle = findTotalTurnAngle(step_at_turn_location, transfer_from_step); + step_at_turn_location.maneuver.instruction.direction_modifier = getTurnDirection(angle); +} + +AdjustToCombinedTurnStrategy::AdjustToCombinedTurnStrategy( + const RouteStep &step_prior_to_intersection) + : step_prior_to_intersection(step_prior_to_intersection) +{ +} + +void AdjustToCombinedTurnStrategy::operator()(RouteStep &step_at_turn_location, + const RouteStep &transfer_from_step) const +{ + const auto angle = findTotalTurnAngle(step_at_turn_location, transfer_from_step); + const auto new_modifier = getTurnDirection(angle); + + // a turn that is a new name or straight (turn/continue) + const auto is_non_turn = [](const RouteStep &step) { + return hasTurnType(step, TurnType::NewName) || + (hasTurnType(step, TurnType::Turn) && + hasModifier(step, DirectionModifier::Straight)) || + (hasTurnType(step, TurnType::Continue) && + hasModifier(step, DirectionModifier::Straight)); + }; + + // check if the first part is the actual turn + const auto transferring_from_non_turn = is_non_turn(transfer_from_step); + + // or if the maneuver location does not perform an actual turn + const auto maneuver_at_non_turn = is_non_turn(step_at_turn_location) || + hasTurnType(step_at_turn_location, TurnType::Suppressed); + + // creating turns if the original instrution wouldn't be a maneuver (also for turn straights)` + if (transferring_from_non_turn || maneuver_at_non_turn) + { + if (hasTurnType(step_at_turn_location, TurnType::Suppressed)) + { + if (new_modifier == DirectionModifier::Straight) + setInstructionType(step_at_turn_location, TurnType::NewName); + else + step_at_turn_location.maneuver.instruction.type = + haveSameName(step_prior_to_intersection, transfer_from_step) + ? TurnType::Continue + : TurnType::Turn; + } + else if (hasTurnType(step_at_turn_location, TurnType::NewName) && + hasTurnType(transfer_from_step, TurnType::Suppressed) && + new_modifier != DirectionModifier::Straight) + { + setInstructionType(step_at_turn_location, TurnType::Turn); + } + else if (hasTurnType(step_at_turn_location, TurnType::Continue) && + !haveSameName(step_prior_to_intersection, transfer_from_step)) + { + setInstructionType(step_at_turn_location, TurnType::Turn); + } + else if (hasTurnType(step_at_turn_location, TurnType::Turn) && + haveSameName(step_prior_to_intersection, transfer_from_step)) + { + setInstructionType(step_at_turn_location, TurnType::Continue); + } + } + // if we are turning onto a ramp, we carry the ramp (e.g. a turn onto a ramp that is modelled + // later only) + else if (hasTurnType(transfer_from_step, TurnType::OnRamp)) + { + setInstructionType(step_at_turn_location, TurnType::OnRamp); + } + // switch two turns to a single continue, if necessary + else if (hasTurnType(step_at_turn_location, TurnType::Turn) && + hasTurnType(transfer_from_step, TurnType::Turn) && + haveSameName(step_prior_to_intersection, transfer_from_step)) + { + setInstructionType(step_at_turn_location, TurnType::Continue); + } + // switch continue to turn, if possible + else if (hasTurnType(step_at_turn_location, TurnType::Continue) && + hasTurnType(transfer_from_step, TurnType::Turn) && + !haveSameName(step_prior_to_intersection, transfer_from_step)) + { + setInstructionType(step_at_turn_location, TurnType::Turn); + } + + // finally set our new modifier + step_at_turn_location.maneuver.instruction.direction_modifier = new_modifier; +} + +StaggeredTurnStrategy::StaggeredTurnStrategy(const RouteStep &step_prior_to_intersection) + : step_prior_to_intersection(step_prior_to_intersection) +{ +} + +void StaggeredTurnStrategy::operator()(RouteStep &step_at_turn_location, + const RouteStep &transfer_from_step) const +{ + step_at_turn_location.maneuver.instruction.direction_modifier = DirectionModifier::Straight; + step_at_turn_location.maneuver.instruction.type = + haveSameName(step_prior_to_intersection, transfer_from_step) ? TurnType::Suppressed + : TurnType::NewName; +} + +SetFixedInstructionStrategy::SetFixedInstructionStrategy( + const extractor::guidance::TurnInstruction instruction) + : instruction(instruction) +{ +} + +void SetFixedInstructionStrategy::operator()(RouteStep &step_at_turn_location, + const RouteStep &) const +{ + step_at_turn_location.maneuver.instruction = instruction; +} + +void TransferSignageStrategy::operator()(RouteStep &step_at_turn_location, + const RouteStep &transfer_from_step) const +{ + step_at_turn_location.AdaptStepSignage(transfer_from_step); + step_at_turn_location.rotary_name = transfer_from_step.rotary_name; + step_at_turn_location.rotary_pronunciation = transfer_from_step.rotary_pronunciation; +} + +void TransferLanesStrategy::operator()(RouteStep &step_at_turn_location, + const RouteStep &transfer_from_step) const +{ + step_at_turn_location.intersections.front().lanes = + transfer_from_step.intersections.front().lanes; + step_at_turn_location.intersections.front().lane_description = + transfer_from_step.intersections.front().lane_description; +} + +void suppressStep(RouteStep &step_at_turn_location, RouteStep &step_after_turn_location) +{ + return combineRouteSteps(step_at_turn_location, + step_after_turn_location, + NoModificationStrategy(), + NoModificationStrategy(), + NoModificationStrategy()); +} + +// OTHER IMPLEMENTATIONS +OSRM_ATTR_WARN_UNUSED +RouteSteps collapseTurnInstructions(RouteSteps steps) +{ + // make sure we can safely iterate over all steps (has depart/arrive with TurnType::NoTurn) + BOOST_ASSERT(!hasTurnType(steps.front()) && !hasTurnType(steps.back())); + BOOST_ASSERT(hasWaypointType(steps.front()) && hasWaypointType(steps.back())); + + if (steps.size() <= 2) + return steps; + + // start of with no-op + for (auto current_step = steps.begin() + 1; current_step + 1 != steps.end(); ++current_step) + { + if (entersRoundabout(current_step->maneuver.instruction) || + staysOnRoundabout(current_step->maneuver.instruction)) + { + // Skip over all instructions within the roundabout + for (; current_step + 1 != steps.end(); ++current_step) + if (leavesRoundabout(current_step->maneuver.instruction)) + break; + + // are we done for good? + if (current_step + 1 == steps.end()) + break; + else + continue; + } + + // only operate on actual turns + if (!hasTurnType(*current_step)) + continue; + + // handle all situations involving the sliproad turn type + if (hasTurnType(*current_step, TurnType::Sliproad)) + { + handleSliproad(current_step); + continue; + } + + // don't collapse next step if it is a waypoint alread + const auto next_step = findNextTurn(current_step); + if (hasWaypointType(*next_step)) + break; + + const auto previous_step = findPreviousTurn(current_step); + + // don't collapse anything that does change modes + if (current_step->mode != next_step->mode) + continue; + + // handle staggered intersections: + // a staggered intersection describes to turns in rapid succession that go in opposite + // directions (e.g. right + left) with a very short segment in between + if (isStaggeredIntersection(previous_step, current_step, next_step)) + { + combineRouteSteps(*current_step, + *next_step, + StaggeredTurnStrategy(*previous_step), + TransferSignageStrategy(), + NoModificationStrategy()); + } + else if (isUTurn(previous_step, current_step, next_step)) + { + combineRouteSteps( + *current_step, + *next_step, + SetFixedInstructionStrategy({TurnType::Continue, DirectionModifier::UTurn}), + TransferSignageStrategy(), + NoModificationStrategy()); + } + else if (isNameOszillation(previous_step, current_step, next_step)) + { + // first deactivate the second name switch + suppressStep(*current_step, *next_step); + // and then the first (to ensure both iterators to be valid) + suppressStep(*previous_step, *current_step); + } + else if (maneuverPreceededByNameChange(previous_step, current_step, next_step) || + maneuverPreceededBySuppressedDirection(current_step, next_step)) + { + const auto strategy = AdjustToCombinedTurnStrategy(*previous_step); + strategy(*next_step, *current_step); + // suppress previous step + suppressStep(*previous_step, *current_step); + } + else if (maneuverSucceededByNameChange(current_step, next_step) || + nameChangeImmediatelyAfterSuppressed(current_step, next_step) || + maneuverSucceededBySuppressedDirection(current_step, next_step) || + closeChoicelessTurnAfterTurn(current_step, next_step)) + { + combineRouteSteps(*current_step, + *next_step, + AdjustToCombinedTurnStrategy(*previous_step), + TransferSignageStrategy(), + NoModificationStrategy()); + } + else if (straightTurnFollowedByChoiceless(current_step, next_step)) + { + combineRouteSteps(*current_step, + *next_step, + AdjustToCombinedTurnStrategy(*previous_step), + TransferSignageStrategy(), + NoModificationStrategy()); + } + else if (suppressedStraightBetweenTurns(previous_step, current_step, next_step)) + { + const auto far_back_step = findPreviousTurn(previous_step); + previous_step->ElongateBy(*current_step); + current_step->Invalidate(); + combineRouteSteps(*previous_step, + *next_step, + AdjustToCombinedTurnStrategy(*far_back_step), + TransferSignageStrategy(), + NoModificationStrategy()); + } + // if the current collapsing triggers, we can check for advanced scenarios that only are + // possible after an inital collapse step (e.g. name change right after a u-turn) + //  + // f - e - d + // | | + // a - b - c + //  + // In this scenario, bc and de might belong to a different road than a-b and f-e (since + // there are no fix conventions how to label them in segregated intersections). These steps + // might only become apparent after some initial collapsing + const auto new_next_step = findNextTurn(current_step); + if (doubleChoiceless(current_step, new_next_step)) + { + combineRouteSteps(*current_step, + *new_next_step, + AdjustToCombinedTurnStrategy(*previous_step), + TransferSignageStrategy(), + NoModificationStrategy()); + } + if (!hasWaypointType(*previous_step)) + { + const auto far_back_step = findPreviousTurn(previous_step); + // due to name changes, we can find u-turns a bit late. Thats why we check far back as + // well + if (isUTurn(far_back_step, previous_step, current_step)) + { + combineRouteSteps( + *previous_step, + *current_step, + SetFixedInstructionStrategy({TurnType::Continue, DirectionModifier::UTurn}), + TransferSignageStrategy(), + NoModificationStrategy()); + } + } + } + return steps; +} + +} // namespace guidance +} // namespace engine +} // namespace osrm diff --git a/src/engine/guidance/lane_processing.cpp b/src/engine/guidance/lane_processing.cpp index e2cc0d478..eb8ef91fc 100644 --- a/src/engine/guidance/lane_processing.cpp +++ b/src/engine/guidance/lane_processing.cpp @@ -2,17 +2,14 @@ #include "util/group_by.hpp" #include "extractor/guidance/turn_instruction.hpp" -#include "engine/guidance/post_processing.hpp" +#include "engine/guidance/collapsing_utility.hpp" #include #include #include #include -using TurnInstruction = osrm::extractor::guidance::TurnInstruction; -namespace TurnType = osrm::extractor::guidance::TurnType; -namespace DirectionModifier = osrm::extractor::guidance::DirectionModifier; - +using osrm::extractor::guidance::TurnInstruction; using osrm::extractor::guidance::isLeftTurn; using osrm::extractor::guidance::isRightTurn; diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index ab245cf85..c8e92b51f 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -5,13 +5,13 @@ #include "engine/guidance/assemble_steps.hpp" #include "engine/guidance/lane_processing.hpp" +#include "engine/guidance/collapsing_utility.hpp" #include "util/bearing.hpp" #include "util/guidance/name_announcements.hpp" #include "util/guidance/turn_lanes.hpp" #include #include -#include #include #include @@ -20,9 +20,6 @@ #include #include -using TurnInstruction = osrm::extractor::guidance::TurnInstruction; -namespace TurnType = osrm::extractor::guidance::TurnType; -namespace DirectionModifier = osrm::extractor::guidance::DirectionModifier; using osrm::util::angularDeviation; using osrm::extractor::guidance::getTurnDirection; using osrm::extractor::guidance::hasRampType; @@ -38,67 +35,6 @@ namespace guidance namespace { -const constexpr std::size_t MIN_END_OF_ROAD_INTERSECTIONS = std::size_t{2}; -const constexpr double MAX_COLLAPSE_DISTANCE = 30; - -// check if at least one of the turns is actually a maneuver -inline bool hasManeuver(const RouteStep &first, const RouteStep &second) -{ - return (first.maneuver.instruction.type != TurnType::Suppressed || - second.maneuver.instruction.type != TurnType::Suppressed) && - (first.maneuver.instruction.type != TurnType::NoTurn && - second.maneuver.instruction.type != TurnType::NoTurn); -} - -inline bool choiceless(const RouteStep &step, const RouteStep &previous) -{ - // if the next turn is choiceless, we consider longer turn roads collapsable than usually - // accepted. We might need to improve this to find out whether we merge onto a through-street. - BOOST_ASSERT(!step.intersections.empty()); - const auto is_without_choice = previous.distance < 4 * MAX_COLLAPSE_DISTANCE && - 1 >= std::count(step.intersections.front().entry.begin(), - step.intersections.front().entry.end(), - true); - - return is_without_choice && step.maneuver.instruction.type != TurnType::EndOfRoad; -} - -// List of types that can be collapsed, if all other restrictions pass -bool isCollapsableInstruction(const TurnInstruction instruction) -{ - return instruction.type == TurnType::NewName || - (instruction.type == TurnType::Suppressed && - instruction.direction_modifier == DirectionModifier::Straight) || - (instruction.type == TurnType::Turn && - instruction.direction_modifier == DirectionModifier::Straight) || - (instruction.type == TurnType::Continue && - instruction.direction_modifier == DirectionModifier::Straight) || - (instruction.type == TurnType::Merge); -} - -bool compatible(const RouteStep &lhs, const RouteStep &rhs) { return lhs.mode == rhs.mode; } - -// Checks if name change happens the user wants to know about. -// Treats e.g. "Name (Ref)" -> "Name" changes still as same name. -bool isNoticeableNameChange(const RouteStep &lhs, const RouteStep &rhs) -{ - // TODO: rotary_name is not handled at the moment. - return util::guidance::requiresNameAnnounced( - lhs.name, lhs.ref, lhs.pronunciation, rhs.name, rhs.ref, rhs.pronunciation); -} - -double nameSegmentLength(std::size_t at, const std::vector &steps) -{ - BOOST_ASSERT(at < steps.size()); - - double result = steps[at].distance; - while (at + 1 < steps.size() && !isNoticeableNameChange(steps[at], steps[at + 1])) - { - at += 1; - result += steps[at].distance; - } - return result; -} void fixFinalRoundabout(std::vector &steps) { @@ -282,588 +218,8 @@ void closeOffRoundabout(const bool on_roundabout, } } -bool bearingsAreReversed(const double bearing_in, const double bearing_out) -{ - // Nearly perfectly reversed angles have a difference close to 180 degrees (straight) - const double left_turn_angle = [&]() { - if (0 <= bearing_out && bearing_out <= bearing_in) - return bearing_in - bearing_out; - return bearing_in + 360 - bearing_out; - }(); - return angularDeviation(left_turn_angle, 180) <= 35; -} - -bool isLinkroad(const RouteStep &pre_link_step, - const RouteStep &link_step, - const RouteStep &post_link_step) -{ - const constexpr double MAX_LINK_ROAD_LENGTH = 60.0; - return link_step.distance <= MAX_LINK_ROAD_LENGTH && link_step.name_id == EMPTY_NAMEID && - pre_link_step.name_id != EMPTY_NAMEID && post_link_step.name_id != EMPTY_NAMEID; -} - -bool isUTurn(const RouteStep &in_step, const RouteStep &out_step, const RouteStep &pre_in_step) -{ - const bool is_possible_candidate = - in_step.distance <= MAX_COLLAPSE_DISTANCE || choiceless(out_step, in_step) || - (isLinkroad(pre_in_step, in_step, out_step) && out_step.name_id != EMPTY_NAMEID && - pre_in_step.name_id != EMPTY_NAMEID && !isNoticeableNameChange(pre_in_step, out_step)); - const bool takes_u_turn = bearingsAreReversed( - util::bearing::reverse( - in_step.intersections.front().bearings[in_step.intersections.front().in]), - out_step.intersections.front().bearings[out_step.intersections.front().out]); - - return is_possible_candidate && takes_u_turn && compatible(in_step, out_step); -} - -double findTotalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_step) -{ - const auto exit_intersection = exit_step.intersections.front(); - const auto exit_step_exit_bearing = exit_intersection.bearings[exit_intersection.out]; - const auto exit_step_entry_bearing = - util::bearing::reverse(exit_intersection.bearings[exit_intersection.in]); - - const auto entry_intersection = entry_step.intersections.front(); - const auto entry_step_entry_bearing = - util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]); - const auto entry_step_exit_bearing = entry_intersection.bearings[entry_intersection.out]; - - const auto exit_angle = - util::bearing::angleBetween(exit_step_entry_bearing, exit_step_exit_bearing); - const auto entry_angle = - util::bearing::angleBetween(entry_step_entry_bearing, entry_step_exit_bearing); - - const double total_angle = - util::bearing::angleBetween(entry_step_entry_bearing, exit_step_exit_bearing); - // We allow for minor deviations from a straight line - if (((entry_step.distance < MAX_COLLAPSE_DISTANCE && exit_step.intersections.size() == 1) || - (entry_angle <= 185 && exit_angle <= 185) || (entry_angle >= 175 && exit_angle >= 175)) && - angularDeviation(total_angle, 180) > 20) - { - // both angles are in the same direction, the total turn gets increased - // - // a ---- b - // \  - // c - // | - // d - // - // Will be considered just like - // a -----b - // | - // c - // | - // d - return total_angle; - } - else - { - // to prevent ignoring angles like - // a -- b - // | - // c -- d - // We don't combine both turn angles here but keep the very first turn angle. - // We choose the first one, since we consider the first maneuver in a merge range the - // important one - return entry_angle; - } -} - -std::size_t getPreviousIndex(std::size_t index, const std::vector &steps) -{ - BOOST_ASSERT(index > 0); - BOOST_ASSERT(index < steps.size()); - --index; - while (index > 0 && steps[index].maneuver.instruction.type == TurnType::NoTurn) - --index; - - return index; -} - -void collapseUTurn(std::vector &steps, - const std::size_t two_back_index, - const std::size_t one_back_index, - const std::size_t step_index) -{ - BOOST_ASSERT(two_back_index < steps.size()); - BOOST_ASSERT(step_index < steps.size()); - BOOST_ASSERT(one_back_index < steps.size()); - const auto ¤t_step = steps[step_index]; - - // the simple case is a u-turn that changes directly into the in-name again - const bool direct_u_turn = !isNoticeableNameChange(steps[two_back_index], current_step); - - // however, we might also deal with a dual-collapse scenario in which we have to - // additionall collapse a name-change as well - const auto next_step_index = step_index + 1; - const bool continues_with_name_change = - (next_step_index < steps.size()) && compatible(steps[step_index], steps[next_step_index]) && - ((steps[next_step_index].maneuver.instruction.type == TurnType::UseLane && - steps[next_step_index].maneuver.instruction.direction_modifier == - DirectionModifier::Straight) || - isCollapsableInstruction(steps[next_step_index].maneuver.instruction)); - const bool u_turn_with_name_change = - continues_with_name_change && steps[next_step_index].name_id != EMPTY_NAMEID && - !isNoticeableNameChange(steps[two_back_index], steps[next_step_index]); - - if (direct_u_turn || u_turn_with_name_change) - { - steps[one_back_index].ElongateBy(steps[step_index]); - steps[step_index].Invalidate(); - if (u_turn_with_name_change) - { - BOOST_ASSERT_MSG(compatible(steps[one_back_index], steps[next_step_index]), - "Compatibility should be transitive"); - steps[one_back_index].ElongateBy(steps[next_step_index]); - steps[next_step_index].Invalidate(); // will be skipped due to the - // continue statement at the - // beginning of this function - } - steps[one_back_index].AdaptStepSignage(steps[two_back_index]); - steps[one_back_index].maneuver.instruction.type = TurnType::Continue; - steps[one_back_index].maneuver.instruction.direction_modifier = DirectionModifier::UTurn; - } -} - -void collapseTurnAt(std::vector &steps, - const std::size_t two_back_index, - const std::size_t one_back_index, - const std::size_t step_index) -{ - BOOST_ASSERT(step_index < steps.size()); - BOOST_ASSERT(one_back_index < steps.size()); - const auto ¤t_step = steps[step_index]; - const auto &one_back_step = steps[one_back_index]; - // Don't collapse roundabouts - if (entersRoundabout(current_step.maneuver.instruction) || - entersRoundabout(one_back_step.maneuver.instruction)) - return; - - // This function assumes driving on the right hand side of the streat - BOOST_ASSERT(!one_back_step.intersections.empty() && !current_step.intersections.empty()); - - if (!hasManeuver(one_back_step, current_step)) - return; - - // A maneuver is preceded by a name change if the instruction just before can be collapsed - // normally or the instruction itself is collapsable and does not actually present a choice - const auto maneuverPrecededByNameChange = [](const RouteStep &turning_point, - const RouteStep &possible_name_change_location, - const RouteStep &preceeding_step) { - // the check against merge is a workaround for motorways - if (possible_name_change_location.maneuver.instruction.type == TurnType::Merge || - !compatible(possible_name_change_location, preceeding_step)) - return false; - - return collapsable(possible_name_change_location, turning_point) || - (isCollapsableInstruction(possible_name_change_location.maneuver.instruction) && - choiceless(possible_name_change_location, preceeding_step)); - }; - - // check if the actual turn we wan't to announce is delayed. This situation describes a turn - // that is expressed by two turns, - const auto isDelayedTurn = []( - const RouteStep &opening_turn, const RouteStep &finishing_turn, const RouteStep &pre_turn) { - // only possible if both are compatible - if (!compatible(opening_turn, finishing_turn)) - return false; - else - { - const auto is_short_and_collapsable = - opening_turn.distance <= MAX_COLLAPSE_DISTANCE && - isCollapsableInstruction(finishing_turn.maneuver.instruction); - - const auto without_choice = choiceless(finishing_turn, opening_turn); - - const auto is_not_too_long_and_choiceless = - opening_turn.distance <= 2 * MAX_COLLAPSE_DISTANCE && without_choice; - - // for ramps we allow longer stretches, since they are often on some major brides/large - // roads. A combined distance of of 4 intersections would be to long for a normal - // collapse. In case of a ramp though, we also account for situations that have the ramp - // tagged late - const auto is_delayed_turn_onto_a_ramp = - opening_turn.distance <= 4 * MAX_COLLAPSE_DISTANCE && without_choice && - hasRampType(finishing_turn.maneuver.instruction); - - const auto linkroad = isLinkroad(pre_turn, opening_turn, finishing_turn); - - return !hasRampType(opening_turn.maneuver.instruction) && - (is_short_and_collapsable || is_not_too_long_and_choiceless || linkroad || - is_delayed_turn_onto_a_ramp); - } - }; - - // Handle possible u-turns - if (isUTurn(one_back_step, current_step, steps[two_back_index])) - collapseUTurn(steps, two_back_index, one_back_index, step_index); - // Very Short New Name that will be suppressed. Turn location remains at current_step - else if (maneuverPrecededByNameChange(current_step, one_back_step, steps[two_back_index])) - { - BOOST_ASSERT(two_back_index < steps.size()); - BOOST_ASSERT(!one_back_step.intersections.empty()); - if (TurnType::Merge == current_step.maneuver.instruction.type) - { - steps[step_index].maneuver.instruction.direction_modifier = - mirrorDirectionModifier(steps[step_index].maneuver.instruction.direction_modifier); - steps[step_index].maneuver.instruction.type = TurnType::Turn; - } - else - { - const bool continue_or_suppressed = - (TurnType::Continue == current_step.maneuver.instruction.type || - (TurnType::Suppressed == current_step.maneuver.instruction.type && - current_step.maneuver.instruction.direction_modifier != - DirectionModifier::Straight)); - - const bool turning_name = - (TurnType::NewName == current_step.maneuver.instruction.type && - current_step.maneuver.instruction.direction_modifier != - DirectionModifier::Straight && - one_back_step.intersections.front().bearings.size() > 2); - - if (continue_or_suppressed) - steps[step_index].maneuver.instruction.type = TurnType::Turn; - else if (turning_name) - steps[step_index].maneuver.instruction.type = TurnType::Turn; - else if (TurnType::UseLane == current_step.maneuver.instruction.type && - current_step.maneuver.instruction.direction_modifier != - DirectionModifier::Straight && - one_back_step.intersections.front().bearings.size() > 2) - steps[step_index].maneuver.instruction.type = TurnType::Turn; - - // A new name with a continue/turning suppressed/name requires the adaption of the - // direction modifier. The combination of the in-bearing and the out bearing gives the - // new modifier for the turn - if (continue_or_suppressed || turning_name) - { - const auto in_bearing = [](const RouteStep &step) { - return util::bearing::reverse( - step.intersections.front().bearings[step.intersections.front().in]); - }; - const auto out_bearing = [](const RouteStep &step) { - return step.intersections.front().bearings[step.intersections.front().out]; - }; - - const auto first_angle = util::bearing::angleBetween(in_bearing(one_back_step), - out_bearing(one_back_step)); - const auto second_angle = util::bearing::angleBetween(in_bearing(current_step), - out_bearing(current_step)); - const auto bearing_turn_angle = util::bearing::angleBetween( - in_bearing(one_back_step), out_bearing(current_step)); - - // When looking at an intersection, some angles, even though present, feel more like - // a straight turn. This happens most often at segregated intersections. - // We consider two cases - // I) a shift in the road: - // - // a g h - // . | | - // b ---- c - // | | . - // f e d - // - // Where a-d technicall continues straight, even though the shift models it as a - // slight left and a slight right turn. - // - // II) A curved road - // - // g h - // | | - // b ---- c - // . | | . - // a f e d - // - // where a-d is a curve passing by an intersection. - // - // We distinguish this case from other bearings though where the interpretation as - // straight would end up disguising turns. - - // check if there is another similar turn next to the turn itself - const auto hasSimilarAngle = [&](const std::size_t index, - const std::vector &bearings) { - return (angularDeviation(bearings[index], - bearings[(index + 1) % bearings.size()]) < - extractor::guidance::NARROW_TURN_ANGLE) || - (angularDeviation( - bearings[index], - bearings[(index + bearings.size() - 1) % bearings.size()]) < - extractor::guidance::NARROW_TURN_ANGLE); - }; - - const auto is_shift_or_curve = [&]() -> bool { - // since we move an intersection modifier from a slight turn to a straight, we - // need to make sure that there is not a similar angle which could prevent this - // perception of angles to be true. - if (hasSimilarAngle(one_back_step.intersections.front().in, - one_back_step.intersections.front().bearings) || - hasSimilarAngle(current_step.intersections.front().out, - current_step.intersections.front().bearings)) - return false; - - // Check if we are on a potential curve, both angles go in the same direction - if (angularDeviation(first_angle, second_angle) < - extractor::guidance::FUZZY_ANGLE_DIFFERENCE) - { - // We limit perceptive angles to narrow turns. If the total turn is going to - // be not-narrow, we assume it to be more than a simple curve. - return angularDeviation(bearing_turn_angle, - extractor::guidance::STRAIGHT_ANGLE) < - extractor::guidance::NARROW_TURN_ANGLE; - } - // if one of the angles is a left turn and the other one is a right turn, we - // nearly reverse the angle - else if ((first_angle > extractor::guidance::STRAIGHT_ANGLE) != - (second_angle > extractor::guidance::STRAIGHT_ANGLE)) - { - // since we are not in a curve, we can check for a shift. If we are going - // nearly straight, we call it a shift. - return angularDeviation(bearing_turn_angle, - extractor::guidance::STRAIGHT_ANGLE) < - extractor::guidance::NARROW_TURN_ANGLE; - } - else - { - return false; - } - }(); - - // if the angles continue similar, it looks like we might be in a normal curve - if (is_shift_or_curve) - steps[step_index].maneuver.instruction.direction_modifier = - DirectionModifier::Straight; - else - steps[step_index].maneuver.instruction.direction_modifier = - getTurnDirection(bearing_turn_angle); - - // if the total direction of this turn is now straight, we can keep it suppressed/as - // a new name. Else we have to interpret it as a turn. - if (!is_shift_or_curve) - steps[step_index].maneuver.instruction.type = TurnType::Turn; - else - steps[step_index].maneuver.instruction.type = TurnType::NewName; - } - else - { - const auto total_angle = findTotalTurnAngle(steps[one_back_index], current_step); - steps[step_index].maneuver.instruction.direction_modifier = - getTurnDirection(total_angle); - } - } - - steps[two_back_index].ElongateBy(one_back_step); - // If the previous instruction asked to continue, the name change will have to - // be changed into a turn - steps[one_back_index].Invalidate(); - } - // very short segment after turn, turn location remains at one_back_step - else if (isDelayedTurn( - one_back_step, current_step, steps[two_back_index])) // checks for compatibility - { - steps[one_back_index].ElongateBy(steps[step_index]); - // TODO check for lanes (https://github.com/Project-OSRM/osrm-backend/issues/2553) - if (TurnType::Continue == one_back_step.maneuver.instruction.type && - isNoticeableNameChange(steps[two_back_index], current_step)) - { - if (current_step.maneuver.instruction.type == TurnType::OnRamp || - current_step.maneuver.instruction.type == TurnType::OffRamp) - steps[one_back_index].maneuver.instruction.type = - current_step.maneuver.instruction.type; - else - steps[one_back_index].maneuver.instruction.type = TurnType::Turn; - } - else if (TurnType::Turn == one_back_step.maneuver.instruction.type && - !isNoticeableNameChange(steps[two_back_index], current_step)) - { - steps[one_back_index].maneuver.instruction.type = TurnType::Continue; - - const auto getBearing = [](bool in, const RouteStep &step) { - const auto index = - in ? step.intersections.front().in : step.intersections.front().out; - return step.intersections.front().bearings[index]; - }; - - // If we Merge onto the same street, we end up with a u-turn in some cases - if (bearingsAreReversed(util::bearing::reverse(getBearing(true, one_back_step)), - getBearing(false, current_step))) - { - steps[one_back_index].maneuver.instruction.direction_modifier = - DirectionModifier::UTurn; - } - steps[one_back_index].AdaptStepSignage(current_step); - } - else if (TurnType::NewName == one_back_step.maneuver.instruction.type || - (TurnType::NewName == current_step.maneuver.instruction.type && - steps[one_back_index].maneuver.instruction.type == TurnType::Suppressed)) - steps[one_back_index].maneuver.instruction.type = TurnType::Turn; - - if (TurnType::Merge == one_back_step.maneuver.instruction.type && - current_step.maneuver.instruction.type != - TurnType::Suppressed) // This suppressed is a check for highways. We might - // need a highway-suppressed to get the turn onto a - // highway... - { - steps[one_back_index].maneuver.instruction.direction_modifier = mirrorDirectionModifier( - steps[one_back_index].maneuver.instruction.direction_modifier); - } - // on non merge-types, we check for a combined turn angle - else if (TurnType::Merge != one_back_step.maneuver.instruction.type) - { - const auto combined_angle = findTotalTurnAngle(one_back_step, current_step); - steps[one_back_index].maneuver.instruction.direction_modifier = - getTurnDirection(combined_angle); - } - - steps[one_back_index].name = current_step.name; - steps[one_back_index].name_id = current_step.name_id; - steps[step_index].Invalidate(); - } - else if (TurnType::Suppressed == current_step.maneuver.instruction.type && - !isNoticeableNameChange(one_back_step, current_step) && - compatible(one_back_step, current_step)) - { - steps[one_back_index].ElongateBy(current_step); - const auto angle = findTotalTurnAngle(one_back_step, current_step); - steps[one_back_index].maneuver.instruction.direction_modifier = getTurnDirection(angle); - steps[step_index].Invalidate(); - } - else if (TurnType::Turn == one_back_step.maneuver.instruction.type && - TurnType::OnRamp == current_step.maneuver.instruction.type && - compatible(one_back_step, current_step)) - { - // turning onto a ramp makes the first turn into a ramp - steps[one_back_index].ElongateBy(current_step); - steps[one_back_index].maneuver.instruction.type = TurnType::OnRamp; - const auto angle = findTotalTurnAngle(one_back_step, current_step); - steps[one_back_index].maneuver.instruction.direction_modifier = getTurnDirection(angle); - - steps[one_back_index].AdaptStepSignage(current_step); - steps[step_index].Invalidate(); - } -} - -// Staggered intersection are very short zig-zags of a few meters. -// We do not want to announce these short left-rights or right-lefts: -// -// * -> b a -> * -// | or | becomes a -> b -// a -> * * -> b -// -bool isStaggeredIntersection(const std::vector &steps, - const std::size_t ¤t_index, - const std::size_t &previous_index) -{ - const RouteStep previous = steps[previous_index]; - const RouteStep current = steps[current_index]; - - // don't touch roundabouts - if (entersRoundabout(previous.maneuver.instruction) || - entersRoundabout(current.maneuver.instruction)) - return false; - // Base decision on distance since the zig-zag is a visual clue. - // If adjusted, make sure to check validity of the is_right/is_left classification below - const constexpr auto MAX_STAGGERED_DISTANCE = 3; // debatable, but keep short to be on safe side - - const auto angle = [](const RouteStep &step) { - const auto &intersection = step.intersections.front(); - const auto entry_bearing = intersection.bearings[intersection.in]; - const auto exit_bearing = intersection.bearings[intersection.out]; - return util::bearing::angleBetween(entry_bearing, exit_bearing); - }; - - // Instead of using turn modifiers (e.g. as in isRightTurn) we want to be more strict here. - // We do not want to trigger e.g. on sharp uturn'ish turns or going straight "turns". - // Therefore we use the turn angle to derive 90 degree'ish right / left turns. - // This more closely resembles what we understand as Staggered Intersection. - // We have to be careful in cases with larger MAX_STAGGERED_DISTANCE values. If the distance - // gets large, sharper angles might be not obvious enough to consider them a staggered - // intersection. We might need to consider making the decision here dependent on the actual turn - // angle taken. To do so, we could scale the angle-limits by a factor depending on the distance - // between the turns. - const auto is_right = [](const double angle) { return angle > 45 && angle < 135; }; - const auto is_left = [](const double angle) { return angle > 225 && angle < 315; }; - - const auto left_right = is_left(angle(previous)) && is_right(angle(current)); - const auto right_left = is_right(angle(previous)) && is_left(angle(current)); - - // A RouteStep holds distance/duration from the maneuver to the subsequent step. - // We are only interested in the distance between the first and the second. - const auto is_short = previous.distance < MAX_STAGGERED_DISTANCE; - - auto intermediary_mode_change = false; - if (current_index > 1) - { - const auto &two_back_index = getPreviousIndex(previous_index, steps); - const auto two_back_step = steps[two_back_index]; - intermediary_mode_change = - two_back_step.mode == current.mode && previous.mode != current.mode; - } - - // previous step maneuver intersections should be length 1 to indicate that - // there are no intersections between the two potentially collapsible turns - const auto no_intermediary_intersections = previous.intersections.size() == 1; - - return is_short && (left_right || right_left) && !intermediary_mode_change && - no_intermediary_intersections; -} - } // namespace -// A check whether two instructions can be treated as one. This is only the case for very short -// maneuvers that can, in some form, be seen as one. Lookahead of one step. -bool collapsable(const RouteStep &step, const RouteStep &next) -{ - const auto is_short_step = step.distance < MAX_COLLAPSE_DISTANCE; - const auto instruction_can_be_collapsed = isCollapsableInstruction(step.maneuver.instruction); - - const auto is_use_lane = step.maneuver.instruction.type == TurnType::UseLane; - const auto lanes_dont_change = - step.intersections.front().lanes == next.intersections.front().lanes; - - if (is_short_step && instruction_can_be_collapsed) - return true; - - // Prevent collapsing away important lane change steps - if (is_short_step && is_use_lane && lanes_dont_change) - return true; - - return false; -} - -// Post processing can invalidate some instructions. For example StayOnRoundabout -// is turned into exit counts. These instructions are removed by the following function - -std::vector removeNoTurnInstructions(std::vector steps) -{ - // finally clean up the post-processed instructions. - // Remove all invalid instructions from the set of instructions. - // An instruction is invalid, if its NO_TURN and has WaypointType::None. - // Two valid NO_TURNs exist in each leg in the form of Depart/Arrive - - // keep valid instructions - const auto not_is_valid = [](const RouteStep &step) { - return step.maneuver.instruction == TurnInstruction::NO_TURN() && - step.maneuver.waypoint_type == WaypointType::None; - }; - - boost::remove_erase_if(steps, not_is_valid); - - // the steps should still include depart and arrive at least - BOOST_ASSERT(steps.size() >= 2); - - BOOST_ASSERT(steps.front().intersections.size() >= 1); - BOOST_ASSERT(steps.front().intersections.front().bearings.size() == 1); - BOOST_ASSERT(steps.front().intersections.front().entry.size() == 1); - BOOST_ASSERT(steps.front().maneuver.waypoint_type == WaypointType::Depart); - - BOOST_ASSERT(steps.back().intersections.size() == 1); - BOOST_ASSERT(steps.back().intersections.front().bearings.size() == 1); - BOOST_ASSERT(steps.back().intersections.front().entry.size() == 1); - BOOST_ASSERT(steps.back().maneuver.waypoint_type == WaypointType::Arrive); - - return steps; -} - // Every Step Maneuver consists of the information until the turn. // This list contains a set of instructions, called silent, which should // not be part of the final output. @@ -942,259 +298,6 @@ std::vector postProcess(std::vector steps) return removeNoTurnInstructions(std::move(steps)); } -// Post Processing to collapse unnecessary sets of combined instructions into a single one -std::vector collapseTurns(std::vector steps) -{ - if (steps.size() <= 2) - return steps; - - const auto getPreviousNameIndex = [&steps](std::size_t index) { - BOOST_ASSERT(index > 0); - BOOST_ASSERT(index < steps.size()); - --index; // make sure to skip the current name - while (index > 0 && steps[index].name_id == EMPTY_NAMEID) - { - --index; - } - return index; - }; - - // a series of turns is only possible to collapse if its only name changes and suppressed turns. - const auto canCollapseAll = [&steps](std::size_t index, const std::size_t end_index) { - BOOST_ASSERT(end_index <= steps.size()); - if (!compatible(steps[index], steps[index + 1])) - return false; - ++index; - for (; index < end_index; ++index) - { - if (steps[index].maneuver.instruction.type != TurnType::Suppressed && - steps[index].maneuver.instruction.type != TurnType::NewName) - return false; - if (index + 1 < end_index && !compatible(steps[index], steps[index + 1])) - return false; - } - return true; - }; - - // first and last instructions are waypoints that cannot be collapsed - for (std::size_t step_index = 1; step_index + 1 < steps.size(); ++step_index) - { - const auto ¤t_step = steps[step_index]; - const auto next_step_index = step_index + 1; - const auto one_back_index = getPreviousIndex(step_index, steps); - - BOOST_ASSERT(one_back_index < steps.size()); - - const auto &one_back_step = steps[one_back_index]; - if (hasRoundaboutType(current_step.maneuver.instruction) || - hasRoundaboutType(one_back_step.maneuver.instruction)) - continue; - - if (!hasManeuver(one_back_step, current_step)) - continue; - - // how long has a name change to be so that we announce it, even as a bridge? - const constexpr auto name_segment_cutoff_length = 100; - const auto isBasicNameChange = [](const RouteStep &step) { - return step.intersections.size() == 1 && - step.intersections.front().bearings.size() == 2 && - DirectionModifier::Straight == step.maneuver.instruction.direction_modifier; - }; - - // Handle sliproads from motorways in urban areas, save from modifying depart, since - // TurnType::Sliproad != TurnType::NoTurn - if (one_back_step.maneuver.instruction.type == TurnType::Sliproad) - { - if (current_step.maneuver.instruction.type == TurnType::Suppressed && - compatible(one_back_step, current_step) && current_step.intersections.size() == 1 && - current_step.intersections.front().entry.size() == 2) - { - // Traffic light on the sliproad, the road itself will be handled in the next - // iteration, when one-back-index again points to the sliproad. - steps[one_back_index].ElongateBy(steps[step_index]); - steps[step_index].Invalidate(); - } - else - { - // Handle possible u-turns between highways that look like slip-roads - if (steps[getPreviousIndex(one_back_index, steps)].name_id == - steps[step_index].name_id && - steps[step_index].name_id != EMPTY_NAMEID) - { - steps[one_back_index].maneuver.instruction.type = TurnType::Continue; - } - else - { - steps[one_back_index].maneuver.instruction.type = TurnType::Turn; - } - if (compatible(one_back_step, current_step)) - { - // Turn Types in the response depend on whether we find the same road name - // (sliproad indcating a u-turn) or if we are turning onto a different road, in - // which case we use a turn. - if (!isNoticeableNameChange(steps[getPreviousIndex(one_back_index, steps)], - current_step) && - current_step.name_id != EMPTY_NAMEID) - steps[one_back_index].maneuver.instruction.type = TurnType::Continue; - else - steps[one_back_index].maneuver.instruction.type = TurnType::Turn; - - steps[one_back_index].ElongateBy(steps[step_index]); - - steps[one_back_index].AdaptStepSignage(steps[step_index]); - // the turn lanes for this turn are on the sliproad itself, so we have to - // remember them - steps[one_back_index].intersections.front().lanes = - current_step.intersections.front().lanes; - steps[one_back_index].intersections.front().lane_description = - current_step.intersections.front().lane_description; - - const auto angle = findTotalTurnAngle(one_back_step, current_step); - steps[one_back_index].maneuver.instruction.direction_modifier = - getTurnDirection(angle); - steps[step_index].Invalidate(); - } - else - { - // the sliproad turn is incompatible. So we handle it as a turn - steps[one_back_index].maneuver.instruction.type = TurnType::Turn; - } - } - } - // Due to empty segments, we can get name-changes from A->A - // These have to be handled in post-processing - else if (isCollapsableInstruction(current_step.maneuver.instruction) && - current_step.maneuver.instruction.type != TurnType::Suppressed && - !isNoticeableNameChange(steps[getPreviousNameIndex(step_index)], current_step) && - // canCollapseAll is also checking for compatible(step,step+1) for all indices - canCollapseAll(getPreviousNameIndex(step_index), next_step_index)) - { - BOOST_ASSERT(step_index > 0); - const std::size_t last_available_name_index = getPreviousNameIndex(step_index); - - for (std::size_t index = last_available_name_index + 1; index <= step_index; ++index) - { - steps[last_available_name_index].ElongateBy(steps[index]); - steps[index].Invalidate(); - } - } - // If we look at two consecutive name changes, we can check for a name oscillation. - // A name oscillation changes from name A shortly to name B and back to A. - // In these cases, the name change will be suppressed. - else if (one_back_index > 0 && compatible(current_step, one_back_step) && - ((isCollapsableInstruction(current_step.maneuver.instruction) && - isCollapsableInstruction(one_back_step.maneuver.instruction)) || - isStaggeredIntersection(steps, step_index, one_back_index))) - { - const auto two_back_index = getPreviousIndex(one_back_index, steps); - BOOST_ASSERT(two_back_index < steps.size()); - // valid, since one_back is collapsable or a turn and therefore not depart: - if (!isNoticeableNameChange(steps[two_back_index], current_step)) - { - if (compatible(one_back_step, steps[two_back_index])) - { - steps[two_back_index] - .ElongateBy(steps[one_back_index]) - .ElongateBy(steps[step_index]); - steps[one_back_index].Invalidate(); - steps[step_index].Invalidate(); - } - // TODO discuss: we could think about changing the new-name to a pure notification - // about mode changes - } - else if (nameSegmentLength(one_back_index, steps) < name_segment_cutoff_length && - isBasicNameChange(one_back_step) && isBasicNameChange(current_step)) - { - if (compatible(steps[two_back_index], steps[one_back_index])) - { - steps[two_back_index].ElongateBy(steps[one_back_index]); - steps[one_back_index].Invalidate(); - if (nameSegmentLength(step_index, steps) < name_segment_cutoff_length && - compatible(steps[two_back_index], steps[step_index])) - { - steps[two_back_index].ElongateBy(steps[step_index]); - steps[step_index].Invalidate(); - } - } - } - else if (step_index + 2 < steps.size() && - current_step.maneuver.instruction.type == TurnType::NewName && - steps[next_step_index].maneuver.instruction.type == TurnType::NewName && - !isNoticeableNameChange(one_back_step, steps[next_step_index])) - { - if (compatible(steps[step_index], steps[next_step_index])) - { - // if we are crossing an intersection and go immediately after into a name - // change, - // we don't wan't to collapse the initial intersection. - // a - b ---BRIDGE -- c - steps[one_back_index] - .ElongateBy(steps[step_index]) - .ElongateBy(steps[next_step_index]); - steps[step_index].Invalidate(); - steps[next_step_index].Invalidate(); - } - } - else if (choiceless(current_step, one_back_step) || - one_back_step.distance <= MAX_COLLAPSE_DISTANCE) - { - // check for one of the multiple collapse scenarios and, if possible, collapse the - // turn - const auto two_back_index = getPreviousIndex(one_back_index, steps); - BOOST_ASSERT(two_back_index < steps.size()); - collapseTurnAt(steps, two_back_index, one_back_index, step_index); - } - } - else if (one_back_index > 0 && - (one_back_step.distance <= MAX_COLLAPSE_DISTANCE || - choiceless(current_step, one_back_step) || - isLinkroad( - steps[getPreviousIndex(one_back_index, steps)], one_back_step, current_step))) - { - // check for one of the multiple collapse scenarios and, if possible, collapse the turn - const auto two_back_index = getPreviousIndex(one_back_index, steps); - BOOST_ASSERT(two_back_index < steps.size()); - // all turns that are handled lower down are also compatible - collapseTurnAt(steps, two_back_index, one_back_index, step_index); - } - - if (steps[step_index].maneuver.instruction.type == TurnType::Turn) - { - const auto u_turn_one_back_index = getPreviousIndex(step_index, steps); - if (u_turn_one_back_index > 0) - { - const auto u_turn_two_back_index = getPreviousIndex(u_turn_one_back_index, steps); - if (isUTurn(steps[u_turn_one_back_index], - steps[step_index], - steps[u_turn_two_back_index])) - { - collapseUTurn(steps, u_turn_two_back_index, u_turn_one_back_index, step_index); - } - } - } - } - - // handle final sliproad - if (steps.size() >= 3 && - steps[getPreviousIndex(steps.size() - 1, steps)].maneuver.instruction.type == - TurnType::Sliproad) - { - steps[getPreviousIndex(steps.size() - 1, steps)].maneuver.instruction.type = TurnType::Turn; - } - - BOOST_ASSERT(steps.front().intersections.size() >= 1); - BOOST_ASSERT(steps.front().intersections.front().bearings.size() == 1); - BOOST_ASSERT(steps.front().intersections.front().entry.size() == 1); - BOOST_ASSERT(steps.front().maneuver.waypoint_type == WaypointType::Depart); - - BOOST_ASSERT(steps.back().intersections.size() == 1); - BOOST_ASSERT(steps.back().intersections.front().bearings.size() == 1); - BOOST_ASSERT(steps.back().intersections.front().entry.size() == 1); - BOOST_ASSERT(steps.back().maneuver.waypoint_type == WaypointType::Arrive); - - return removeNoTurnInstructions(std::move(steps)); -} - // 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 @@ -1483,7 +586,7 @@ std::vector buildIntersections(std::vector steps) const auto instruction = step.maneuver.instruction; if (instruction.type == TurnType::Suppressed) { - BOOST_ASSERT(compatible(steps[last_valid_instruction], step)); + BOOST_ASSERT(steps[last_valid_instruction].mode == step.mode); // count intersections. We cannot use exit, since intersections can follow directly // after a roundabout steps[last_valid_instruction].ElongateBy(step); @@ -1514,51 +617,6 @@ std::vector buildIntersections(std::vector steps) return removeNoTurnInstructions(std::move(steps)); } -// `useLane` steps are only returned on `straight` maneuvers when there -// are surrounding lanes also tagged as `straight`. If there are no other `straight` -// lanes, it is not an ambiguous maneuver, and we can collapse the `useLane` step. -std::vector collapseUseLane(std::vector steps) -{ - const auto containsTag = [](const extractor::guidance::TurnLaneType::Mask mask, - const extractor::guidance::TurnLaneType::Mask tag) { - return (mask & tag) != extractor::guidance::TurnLaneType::empty; - }; - - const auto canCollapseUseLane = [containsTag](const RouteStep &step) { - // the lane description is given left to right, lanes are counted from the right. - // Therefore we access the lane description using the reverse iterator - - auto right_most_lanes = step.LanesToTheRight(); - if (!right_most_lanes.empty() && containsTag(right_most_lanes.front(), - (extractor::guidance::TurnLaneType::straight | - extractor::guidance::TurnLaneType::none))) - return false; - - auto left_most_lanes = step.LanesToTheLeft(); - if (!left_most_lanes.empty() && containsTag(left_most_lanes.back(), - (extractor::guidance::TurnLaneType::straight | - extractor::guidance::TurnLaneType::none))) - return false; - - return true; - }; - - for (std::size_t step_index = 1; step_index < steps.size(); ++step_index) - { - const auto &step = steps[step_index]; - if (step.maneuver.instruction.type == TurnType::UseLane && canCollapseUseLane(step)) - { - const auto previous = getPreviousIndex(step_index, steps); - if (compatible(steps[previous], step)) - { - steps[previous].ElongateBy(steps[step_index]); - steps[step_index].Invalidate(); - } - } - } - return removeNoTurnInstructions(std::move(steps)); -} - } // namespace guidance } // namespace engine } // namespace osrm diff --git a/src/engine/guidance/verbosity_reduction.cpp b/src/engine/guidance/verbosity_reduction.cpp new file mode 100644 index 000000000..4cee6b25b --- /dev/null +++ b/src/engine/guidance/verbosity_reduction.cpp @@ -0,0 +1,133 @@ +#include "engine/guidance/verbosity_reduction.hpp" +#include "engine/guidance/collapsing_utility.hpp" + +#include +#include + +namespace osrm +{ +namespace engine +{ +namespace guidance +{ +std::vector suppressShortNameSegments(std::vector steps) +{ + // guard against empty routes, even though they shouldn't happen + if (steps.empty()) + return steps; + + // we remove only name changes that don't offer additional information + const auto name_change_without_lanes = [](const RouteStep &step) { + return hasTurnType(step, TurnType::NewName) && !hasLanes(step); + }; + + // check if the next step is not important enough to announce + const auto can_be_extended_to = [](const RouteStep &step) { + const auto is_not_arrive = !hasWaypointType(step); + const auto is_silent = !hasTurnType(step) || hasTurnType(step, TurnType::Suppressed); + + return is_not_arrive && is_silent; + }; + + const auto suppress = [](RouteStep &from_step, RouteStep &onto_step) { + from_step.ElongateBy(onto_step); + onto_step.Invalidate(); + }; + + // suppresses name segments that announce already known names or announce a name that will be + // only available for a very short time + const auto reduce_verbosity_if_possible = [suppress, can_be_extended_to]( + RouteStepIterator ¤t_turn_itr, RouteStepIterator &previous_turn_itr) { + if (haveSameName(*previous_turn_itr, *current_turn_itr)) + suppress(*previous_turn_itr, *current_turn_itr); + else + { + // remember the location of the name change so we can advance the previous turn + const auto location_of_name_change = current_turn_itr; + auto distance = current_turn_itr->distance; + // sum up all distances that can be relevant to the name change + while (can_be_extended_to(*(current_turn_itr + 1)) && + distance < NAME_SEGMENT_CUTOFF_LENGTH) + { + ++current_turn_itr; + distance += current_turn_itr->distance; + } + + if (distance < NAME_SEGMENT_CUTOFF_LENGTH) + suppress(*previous_turn_itr, *current_turn_itr); + else + previous_turn_itr = location_of_name_change; + } + }; + + BOOST_ASSERT(!hasTurnType(steps.back()) && hasWaypointType(steps.back())); + for (auto previous_turn_itr = steps.begin(), current_turn_itr = std::next(previous_turn_itr); + !hasWaypointType(*current_turn_itr); + ++current_turn_itr) + { + BOOST_ASSERT(hasTurnType(*current_turn_itr) && + !hasTurnType(*current_turn_itr, TurnType::Suppressed)); + if (name_change_without_lanes(*current_turn_itr) && + haveSameMode(*previous_turn_itr, *current_turn_itr)) + { + // check if the name can be reduced, also sets previous_turn_itr if update is necessary + reduce_verbosity_if_possible(current_turn_itr, previous_turn_itr); + } + else + { + // remember the current (non-suppressed) item as a new start of a segment + previous_turn_itr = current_turn_itr; + } + } + return removeNoTurnInstructions(std::move(steps)); +} + +// `useLane` steps are only returned on `straight` maneuvers when there +// are surrounding lanes also tagged as `straight`. If there are no other `straight` +// lanes, it is not an ambiguous maneuver, and we can collapse the `useLane` step. +std::vector collapseUseLane(std::vector steps) +{ + const auto containsTag = [](const extractor::guidance::TurnLaneType::Mask mask, + const extractor::guidance::TurnLaneType::Mask tag) { + return (mask & tag) != extractor::guidance::TurnLaneType::empty; + }; + + const auto canCollapseUseLane = [containsTag](const RouteStep &step) { + // the lane description is given left to right, lanes are counted from the right. + // Therefore we access the lane description using the reverse iterator + + auto right_most_lanes = step.LanesToTheRight(); + if (!right_most_lanes.empty() && containsTag(right_most_lanes.front(), + (extractor::guidance::TurnLaneType::straight | + extractor::guidance::TurnLaneType::none))) + return false; + + auto left_most_lanes = step.LanesToTheLeft(); + if (!left_most_lanes.empty() && containsTag(left_most_lanes.back(), + (extractor::guidance::TurnLaneType::straight | + extractor::guidance::TurnLaneType::none))) + return false; + + return true; + }; + + BOOST_ASSERT(steps.size() > 1); + for (auto step_itr = steps.begin() + 1; step_itr != steps.end(); ++step_itr) + { + if (step_itr->maneuver.instruction.type == TurnType::UseLane && + canCollapseUseLane(*step_itr)) + { + auto previous_turn_itr = findPreviousTurn(step_itr); + if (haveSameMode(*previous_turn_itr, *step_itr)) + { + previous_turn_itr->ElongateBy(*step_itr); + step_itr->Invalidate(); + } + } + } + return removeNoTurnInstructions(std::move(steps)); +} + +} // namespace guidance +} // namespace engine +} // namespace osrm diff --git a/src/extractor/guidance/intersection_handler.cpp b/src/extractor/guidance/intersection_handler.cpp index 771feae24..081840960 100644 --- a/src/extractor/guidance/intersection_handler.cpp +++ b/src/extractor/guidance/intersection_handler.cpp @@ -146,8 +146,11 @@ TurnInstruction IntersectionHandler::getInstructionForObvious(const std::size_t // name has not changed, suppress a turn here or indicate mode change else { - return {in_mode == out_mode ? TurnType::Suppressed : TurnType::Notification, - getTurnDirection(road.angle)}; + if (in_mode != out_mode) + return {TurnType::Notification, getTurnDirection(road.angle)}; + else + return {num_roads == 2 ? TurnType::NoTurn : TurnType::Suppressed, + getTurnDirection(road.angle)}; } } BOOST_ASSERT(type == TurnType::Continue); diff --git a/src/extractor/guidance/intersection_normalizer.cpp b/src/extractor/guidance/intersection_normalizer.cpp index 52f58efd9..218e19859 100644 --- a/src/extractor/guidance/intersection_normalizer.cpp +++ b/src/extractor/guidance/intersection_normalizer.cpp @@ -120,12 +120,39 @@ IntersectionShapeData IntersectionNormalizer::MergeRoads(const IntersectionShape IntersectionShapeData IntersectionNormalizer::MergeRoads(const IntersectionNormalizationOperation direction, const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs) const + const IntersectionShapeData &rhs, + const double opposite_bearing) const { + // In some intersections, turning roads can introduce artificial turns if we merge here. + // Consider a scenario like: + //  + // a . g - f + // | . + // | . + // |. + // d-b--------e + // | + // c + //  + // Merging `bgf` and `be` would introduce an angle, even though d-b-e is perfectly straight + // We don't change the angle, if such an opposite road exists if (direction.merged_eid == lhs.eid) - return MergeRoads(rhs, lhs); + { + // change the angle only if the opposite direction is not nearly straight + if (angularDeviation(opposite_bearing, rhs.bearing) > + (STRAIGHT_ANGLE - MAXIMAL_ALLOWED_NO_TURN_DEVIATION)) + return rhs; + else + return MergeRoads(rhs, lhs); + } else - return MergeRoads(lhs, rhs); + { + if (angularDeviation(opposite_bearing, lhs.bearing) > + (STRAIGHT_ANGLE - MAXIMAL_ALLOWED_NO_TURN_DEVIATION)) + return lhs; + else + return MergeRoads(lhs, rhs); + } } /* @@ -163,7 +190,8 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node, // end up in the end. We only store what we have merged into other edges. std::vector merging_map; const auto merge = [this, &merging_map](const IntersectionShapeData &first, - const IntersectionShapeData &second) { + const IntersectionShapeData &second, + const double opposite_bearing) { const auto direction = DetermineMergeDirection(first, second); BOOST_ASSERT( @@ -171,13 +199,27 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node, return pair.merged_eid == direction.merged_eid; }) == merging_map.end()); merging_map.push_back(direction); - return MergeRoads(direction, first, second); + return MergeRoads(direction, first, second, opposite_bearing); }; if (intersection.size() <= 1) return {intersection, merging_map}; const auto intersection_copy = intersection; + const auto opposite_bearing = [this, intersection_copy](const IntersectionShapeData &lhs, + const IntersectionShapeData &rhs) { + if (node_based_graph.GetEdgeData(lhs.eid).reversed) + { + return intersection_copy.FindClosestBearing(util::bearing::reverse(rhs.bearing)) + ->bearing; + } + else + { + BOOST_ASSERT(node_based_graph.GetEdgeData(rhs.eid).reversed); + return intersection_copy.FindClosestBearing(util::bearing::reverse(lhs.bearing)) + ->bearing; + } + }; // check for merges including the basic u-turn // these result in an adjustment of all other angles. This is due to how these angles are // perceived. Considering the following example: @@ -213,14 +255,16 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node, if (CanMerge(intersection_node, intersection, intersection.size() - 1, 0)) { // moving `a` to the left - intersection[0] = merge(intersection.front(), intersection.back()); + const auto opposite = opposite_bearing(intersection.front(), intersection.back()); + intersection[0] = merge(intersection.front(), intersection.back(), opposite); // FIXME if we have a left-sided country, we need to switch this off and enable it // below intersection.pop_back(); } else if (CanMerge(intersection_node, intersection, 0, 1)) { - intersection[0] = merge(intersection.front(), intersection[1]); + const auto opposite = opposite_bearing(intersection.front(), intersection[1]); + intersection[0] = merge(intersection.front(), intersection[1], opposite); intersection.erase(intersection.begin() + 1); } @@ -230,8 +274,10 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node, { if (CanMerge(intersection_node, intersection, getRight(index), index)) { + const auto opposite = + opposite_bearing(intersection[getRight(index)], intersection[index]); intersection[getRight(index)] = - merge(intersection[getRight(index)], intersection[index]); + merge(intersection[getRight(index)], intersection[index], opposite); intersection.erase(intersection.begin() + index); --index; } diff --git a/src/extractor/guidance/mergable_road_detector.cpp b/src/extractor/guidance/mergable_road_detector.cpp index 68a3061e9..a9ea1fd1b 100644 --- a/src/extractor/guidance/mergable_road_detector.cpp +++ b/src/extractor/guidance/mergable_road_detector.cpp @@ -251,6 +251,7 @@ bool MergableRoadDetector::IsNarrowTriangle(const NodeID intersection_node, connector_turn->eid, connect_accumulator, selector); + // the if both items are connected return node_based_graph.GetTarget(connect_accumulator.via_edge_id) == node_based_graph.GetTarget(right_accumulator.via_edge_id); diff --git a/src/extractor/guidance/sliproad_handler.cpp b/src/extractor/guidance/sliproad_handler.cpp index 25301c577..cfded86c7 100644 --- a/src/extractor/guidance/sliproad_handler.cpp +++ b/src/extractor/guidance/sliproad_handler.cpp @@ -248,6 +248,20 @@ operator()(const NodeID /*nid*/, const EdgeID source_edge_id, Intersection inter sliproad_edge = intersection_finder.via_edge_id; const auto target_intersection = intersection_finder.intersection; + if (target_intersection.isDeadEnd()) + continue; + + const auto find_valid = [](const IntersectionView &view) { + // according to our current sliproad idea, there should only be one valid turn + auto itr = std::find_if( + view.begin(), view.end(), [](const auto &road) { return road.entry_allowed; }); + BOOST_ASSERT(itr != view.end()); + return itr; + }; + + // require all to be same mode, don't allow changes + if (!allSameMode(source_edge_id, sliproad.eid, find_valid(target_intersection)->eid)) + continue; // Constrain the Sliproad's target to sliproad, outgoing, incoming from main intersection if (target_intersection.size() != 3) @@ -673,6 +687,16 @@ bool SliproadHandler::isValidSliproadLink(const IntersectionViewData &sliproad, return true; } +bool SliproadHandler::allSameMode(const EdgeID from, + const EdgeID sliproad_candidate, + const EdgeID target_road) const +{ + return node_based_graph.GetEdgeData(from).travel_mode == + node_based_graph.GetEdgeData(sliproad_candidate).travel_mode && + node_based_graph.GetEdgeData(sliproad_candidate).travel_mode == + node_based_graph.GetEdgeData(target_road).travel_mode; +} + bool SliproadHandler::canBeTargetOfSliproad(const IntersectionView &intersection) { // Example to handle: