diff --git a/.gitignore b/.gitignore index 17e0757e9..c72377e48 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ Thumbs.db /example/build/ /test/data/monaco* /cmake/postinst +.bundle/ # Eclipse related files # ######################### diff --git a/CHANGELOG.md b/CHANGELOG.md index c667b1efa..75b44e295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 5.3.0 + - API + - Introduces new `TurnType` in the form of `use lane`. The type indicates that you have to stick to a lane without turning + - Introduces lanes to the route response, indicating which lanes are to be used on a turn + + - Infrastructure + - BREAKING: The new turn type changes the turn-type order. This breaks the **data format**. + - BREAKING: Turn lane data introduces a new file (osrm.tld). This breaks the fileformat for older versions. + # 5.2.5 - Bugfixes - Fixes a segfault caused by incorrect trimming logic for very short steps. diff --git a/features/bicycle/mode.feature b/features/bicycle/mode.feature index 852402a72..98d23c474 100644 --- a/features/bicycle/mode.feature +++ b/features/bicycle/mode.feature @@ -46,14 +46,17 @@ Feature: Bike - Mode flag Scenario: Bike - Mode when pushing bike against oneways Given the node map - | a | b | | - | | c | d | + | a | b | e | + | f | c | d | And the ways | nodes | highway | oneway | | ab | primary | | | bc | primary | yes | | cd | primary | | + | be | primary | | + | cf | primary | | + When I route I should get | from | to | route | modes | diff --git a/features/bicycle/oneway.feature b/features/bicycle/oneway.feature index 1d363ebc5..17e4a251a 100644 --- a/features/bicycle/oneway.feature +++ b/features/bicycle/oneway.feature @@ -18,8 +18,8 @@ Feature: Bike - Oneway streets Scenario: Bike - Around the Block Given the node map - | a | b | - | d | c | + | | a | b | | + | f | d | c | e | And the ways | nodes | oneway | foot | @@ -27,6 +27,8 @@ Feature: Bike - Oneway streets | bc | | no | | cd | | no | | da | | no | + | df | | no | + | ce | | no | When I route I should get | from | to | route | diff --git a/features/bicycle/pushing.feature b/features/bicycle/pushing.feature index d67282168..d06a96df6 100644 --- a/features/bicycle/pushing.feature +++ b/features/bicycle/pushing.feature @@ -87,14 +87,16 @@ Feature: Bike - Accessability of different way types Scenario: Bike - Instructions when pushing bike on oneways Given the node map - | a | b | | - | | c | d | + | a | b | e | + | f | c | d | And the ways | nodes | highway | oneway | | ab | primary | | | bc | primary | yes | | cd | primary | | + | be | primary | | + | cf | primary | | When I route I should get | from | to | route | modes | diff --git a/features/car/oneway.feature b/features/car/oneway.feature index d048afb2a..72fd3940d 100644 --- a/features/car/oneway.feature +++ b/features/car/oneway.feature @@ -35,8 +35,8 @@ Feature: Car - Oneway streets Scenario: Car - Around the Block Given the node map - | a | b | - | d | c | + | | a | b | | + | f | d | c | e | And the ways | nodes | oneway | @@ -44,6 +44,8 @@ Feature: Car - Oneway streets | bc | | | cd | | | da | | + | ce | | + | df | | When I route I should get | from | to | route | diff --git a/features/guidance/fork.feature b/features/guidance/fork.feature index 835b77299..2c8d59217 100644 --- a/features/guidance/fork.feature +++ b/features/guidance/fork.feature @@ -296,3 +296,21 @@ Feature: Fork Instructions | waypoints | route | turns | | a,c | abc,abc | depart,arrive | | a,d | abc,bd,bd | depart,turn slight right,arrive | + + Scenario: Fork on motorway links - don't fork on through + Given the node map + | i | | | | | a | + | j | | c | b | | x | + + And the ways + | nodes | name | highway | + | xb | xbcj | motorway_link | + | bc | xbcj | motorway_link | + | cj | xbcj | motorway_link | + | ci | off | motorway_link | + | ab | on | motorway_link | + + When I route I should get + | waypoints | route | turns | + | a,j | on,xbcj,xbcj | depart,merge slight left,arrive | + | a,i | on,xbcj,off,off | depart,merge slight left,turn slight right,arrive | diff --git a/features/guidance/turn-lanes.feature b/features/guidance/turn-lanes.feature new file mode 100644 index 000000000..60150a8f1 --- /dev/null +++ b/features/guidance/turn-lanes.feature @@ -0,0 +1,653 @@ +@routing @guidance @turn-lanes +Feature: Turn Lane Guidance + + Background: + Given the profile "car" + Given a grid size of 20 meters + + #requires https://github.com/cucumber/cucumber-js/issues/417 + #Due to this, we use & as a pipe character. Switch them out for \| when 417 is fixed + @bug @WORKAROUND-FIXME + Scenario: Basic Turn Lane 3-way Turn with empty lanes + Given the node map + | a | | b | | c | + | | | d | | | + + And the ways + | nodes | turn:lanes | turn:lanes:forward | turn:lanes:backward | name | + | ab | | through\|right | | in | + | bc | | | left\|through&& | straight | + | bd | | | left\|right | right | + + When I route I should get + | waypoints | route | turns | lanes | + | a,c | in,straight,straight | depart,new name straight,arrive | ,1, | + | a,d | in,right,right | depart,turn right,arrive | ,0, | + | c,a | straight,in,in | depart,new name straight,arrive | ,0 1 2, | + | c,d | straight,right,right | depart,turn left,arrive | ,3, | + + Scenario: Basic Turn Lane 4-Way Turn + Given the node map + | | | e | | | + | a | | b | | c | + | | | d | | | + + And the ways + | nodes | turn:lanes | turn:lanes:forward | turn:lanes:backward | name | + | ab | | \|right | | in | + | bc | | | | straight | + | bd | | | left\| | right | + | be | | | | left | + + When I route I should get + | waypoints | route | turns | lanes | + | a,c | in,straight,straight | depart,new name straight,arrive | ,1, | + | a,d | in,right,right | depart,turn right,arrive | ,0, | + | a,e | in,left,left | depart,turn left,arrive | ,1, | + | d,a | right,in,in | depart,turn left,arrive | ,1, | + | d,e | right,left,left | depart,new name straight,arrive | ,0, | + | d,c | right,straight,straight | depart,turn right,arrive | ,0, | + + Scenario: Basic Turn Lane 4-Way Turn using none + Given the node map + | | | e | | | + | a | | b | | c | + | | | d | | | + + And the ways + | nodes | turn:lanes | turn:lanes:forward | turn:lanes:backward | name | + | ab | | none\|right | | in | + | bc | | | | straight | + | bd | | | left\|none | right | + | be | | | | left | + + When I route I should get + | waypoints | route | turns | lanes | + | a,c | in,straight,straight | depart,new name straight,arrive | ,1, | + | a,d | in,right,right | depart,turn right,arrive | ,0, | + | a,e | in,left,left | depart,turn left,arrive | ,1, | + + Scenario: Basic Turn Lane 4-Way With U-Turn Lane + Given the node map + | | | e | | | + | a | 1 | b | | c | + | | | d | | | + + And the ways + | nodes | turn:lanes | turn:lanes:forward | name | + | ab | | reverse;left\|through;right | in | + | bc | | | straight | + | bd | | | right | + | be | | | left | + + When I route I should get + | from | to | bearings | route | turns | lanes | + | a | c | 180,180 180,180 | in,straight,straight | depart,new name straight,arrive | ,0, | + | a | d | 180,180 180,180 | in,right,right | depart,turn right,arrive | ,0, | + | a | e | 180,180 180,180 | in,left,left | depart,turn left,arrive | ,1, | + | 1 | a | 90,2 270,2 | in,in,in | depart,turn uturn,arrive | ,1, | + + + #this next test requires decision on how to announce lanes for going straight if there is no turn + @TODO @WORKAROUND-FIXME + Scenario: Turn with Bus-Lane + Given the node map + | a | | b | | c | + | | | | | | + | | | d | | | + + And the ways + | nodes | name | turn:lanes:forward | lanes:psv:forward | + | ab | road | through\|right& | 1 | + | bc | road | | | + | bd | turn | | | + + When I route I should get + | waypoints | route | turns | lanes | + | a,d | road,turn,turn | depart,turn right,arrive | ,0, | + | a,c | road,road,road | depart,use lane straight,arrive | ,1, | + + #turn lanes are often drawn at the incoming road, even though the actual turn requires crossing the intersection first + @TODO @WORKAROUND-FIXME + Scenario: Turn Lanes at Segregated Road + Given the node map + | | | i | l | | | + | | | | | | | + | h | | g | f | | e | + | a | | b | c | | d | + | | | | | | | + | | | j | k | | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | + | ab | road | left\|through&right | yes | + | bc | road | left\|through | yes | + | cd | road | | yes | + | ef | road | \|through&through;right | yes | + | fg | road | left;through\|through& | yes | + | gh | road | | yes | + | ig | cross | | yes | + | gb | cross | left\|through | yes | + | bj | cross | | yes | + | kc | cross | left\|through;right | yes | + | cf | cross | left\|through | yes | + | fl | cross | | yes | + + When I route I should get + | waypoints | route | turns | lanes | # | + | a,j | road,cross,cross | depart,turn right,arrive | ,0, | | + | a,d | road,road,road | depart,use lane straight,arrive | ,1, | #post-processing reduction | + | a,l | road,cross,cross | depart,turn left,arrive | ,2, | | + | a,h | road,road,road | depart,continue uturn,arrive | ,2, | | + | k,d | cross,road,road | depart,turn right,arrive | ,0, | | + | k,l | cross,cross,cross | depart,use lane straight,arrive | ,0, | | + | k,h | cross,road,road | depart,turn left,arrive | ,1, | | + | k,j | cross,cross,cross | depart,continue uturn,arrive | ,1, | | + | e,l | road,cross,cross | depart,turn right,arrive | ,0, | | + | e,h | road,road | depart,arrive | , | | + | e,j | road,cross,cross | depart,turn left,arrive | ,2, | | + | e,d | road,road,road | depart,continue uturn,arrive | ,2, | | + | i,h | cross,road,road | depart,turn right,arrive | ,, | | + | i,j | cross,cross,cross | depart,use lane straight,arrive | ,0, | | + | i,d | cross,road,road | depart,turn left,arrive | ,1, | | + | i,l | cross,cross,cross | depart,continue uturn,arrive | ,1, | | + + Scenario: Turn Lanes at Segregated Road + Given the node map + | | | g | f | | | + | a | | b | c | | d | + | | | | | | | + | | | j | k | | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | + | ab | road | left\|through&right | yes | + | bc | road | | yes | + | cd | road | | yes | + | gb | cross | | yes | + | bj | cross | | yes | + | kc | cross | | yes | + | cf | cross | | yes | + + When I route I should get + | waypoints | route | turns | lanes | + | a,j | road,cross,cross | depart,turn right,arrive | ,0, | + + #this can happen due to traffic lights / lanes not drawn up to the intersection itself + Scenario: Turn Lanes Given earlier than actual turn + Given the node map + | a | | b | c | | d | + | | | | | | | + | | | | e | | | + + And the ways + | nodes | name | turn:lanes:forward | + | ab | road | \|right | + | bc | road | | + | cd | road | | + | ce | turn | | + + When I route I should get + | waypoints | route | turns | lanes | + | a,e | road,turn,turn | depart,turn right,arrive | ,0, | + | a,d | road,road,road | depart,use lane straight,arrive | ,1, | + + Scenario: Turn Lanes Given earlier than actual turn + Given the node map + | a | | b | c | d | | e | | f | g | h | | i | + | | | j | | | | | | | | k | | | + + And the ways + | nodes | name | turn:lanes:forward | turn:lanes:backward | + | abc | road | | | + | cd | road | | left\| | + | def | road | | | + | fg | road | \|right | | + | ghi | road | | | + | bj | first-turn | | | + | hk | second-turn | | | + + When I route I should get + | waypoints | route | turns | lanes | + | a,k | road,second-turn,second-turn | depart,turn right,arrive | ,0, | + | a,i | road,road,road | depart,use lane straight,arrive | ,1, | + | i,j | road,first-turn,first-turn | depart,turn left,arrive | ,1, | + | i,a | road,road,road | depart,use lane straight,arrive | ,0, | + + Scenario: Passing a one-way street + Given the node map + | e | | | f | | + | a | | b | c | d | + + And the ways + | nodes | name | turn:lanes:forward | oneway | + | ab | road | left\|through | no | + | bcd | road | | no | + | eb | owi | | yes | + | cf | turn | | | + + When I route I should get + | waypoints | route | turns | lanes | + | a,f | road,turn,turn | depart,turn left,arrive | ,1, | + + Scenario: Passing a one-way street, partly pulled back lanes + Given the node map + | e | | | f | | + | a | | b | c | d | + | | | g | | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | + | ab | road | left\|through;right | no | + | bcd | road | | no | + | eb | owi | | yes | + | cf | turn | | no | + | bg | right | | no | + + When I route I should get + | waypoints | route | turns | lanes | + | a,f | road,turn,turn | depart,turn left,arrive | ,1, | + | a,g | road,right,right | depart,turn right,arrive | ,0, | + + Scenario: Passing a one-way street, partly pulled back lanes, no through + Given the node map + | e | | | f | + | a | | b | c | + | | | g | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | + | ab | road | left\|right | no | + | bc | road | | no | + | eb | owi | | yes | + | cf | turn | | no | + | bg | right | | no | + + When I route I should get + | waypoints | route | turns | lanes | + | a,f | road,turn,turn | depart,turn left,arrive | ,1, | + | a,g | road,right,right | depart,turn right,arrive | ,0, | + + Scenario: Narrowing Turn Lanes + Given the node map + | | | | | g | | + | | | | | | | + | a | | b | c | d | e | + | | | | f | | | + + And the ways + | nodes | name | turn:lanes:forward | + | ab | road | left\|through&right | + | bc | road | | + | cd | road | left\|through | + | de | through | | + | dg | left | | + | cf | right | | + + When I route I should get + | waypoints | route | turns | lanes | + | a,g | road,left,left | depart,turn left,arrive | ,2, | + | a,e | road,through,through | depart,new name straight,arrive | ,1, | + | a,f | road,right,right | depart,turn right,arrive | ,0, | + + Scenario: Anticipate Lane Change + Given the node map + | a | | b | | x | + | | | | | | + | | | c | | d | + | | | | | | + | | | y | | | + + And the ways + | nodes | turn:lanes:forward | turn:lanes:backward | + | ab | through\|right&right | | + | bx | | left\|left&through | + | bc | left\|through | left\|right | + | cd | | left\|right | + | cy | | | + + When I route I should get + | waypoints | route | turns | lanes | + | d,a | cd,bc,ab,ab | depart,end of road right,end of road left,arrive | ,0,1, | + + Scenario: Turn at a traffic light + Given the node map + | a | b | c | d | + | | | e | | + + And the nodes + | node | highway | + | b | traffic_signals | + + And the ways + | nodes | name | turn:lanes:forward | + | ab | road | through\|right | + | bc | road | | + | cd | road | | + | ce | turn | | + + When I route I should get + | waypoints | route | turns | lanes | + | a,d | road,road,road | depart,use lane straight,arrive | ,1, | + | a,e | road,turn,turn | depart,turn right,arrive | ,0, | + + + Scenario: Theodor Heuss Platz + Given the node map + | | | | i | o | | | l | | + | | | b | | | | a | | m | + | | c | | | | | | | | + | | | | | | | | h | | + | | | | | | | | | | + | j | | | | | | | | | + | | | | | | | | g | | + | | | | | | | | | | + | | d | | | | | | | | + | | | e | | | | f | | | + | | | | | k | | | | n | + + And the nodes + | node | highway | + | g | traffic_signals | + + And the ways + | nodes | name | turn:lanes:forward | junction | oneway | highway | + | abcdef | roundabout | | roundabout | yes | primary | + | gha | roundabout | | roundabout | yes | primary | + | fg | roundabout | slight_left\|slight_left;slight_right&slight_right&slight_right | roundabout | yes | primary | + | aoib | top | | | yes | primary | + | cjd | left | | | yes | primary | + | ekf | bottom | | | yes | primary | + | fng | bottom-right | | | yes | primary | + | hma | top-right | | | yes | primary | + | hl | top-right-out | | | yes | secondary | + + When I route I should get + | waypoints | route | turns | lanes | + | i,m | top,top-right,top-right | depart,roundabout-exit-4,arrive | ,0 1 2, | + | i,l | top,top-right-out,top-right-out | depart,roundabout-exit-4,arrive | ,2 3, | + | i,o | top,top,top | depart,roundabout-exit-5,arrive | ,, | + + Scenario: Turn Lanes Breaking up + Given the node map + | | | | g | | + | | | | | | + | | | | c | | + | a | b | | d | e | + | | | | | | + | | | | f | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | highway | + | ab | road | left\|left&through&through | yes | primary | + | bd | road | through\|through | yes | primary | + | bc | road | left\|left | yes | primary | + | de | road | | yes | primary | + | fdcg | cross | | | secondary | + + And the relations + | type | way:from | way:to | node:via | restriction | + | restriction | bd | fdcg | d | no_left_turn | + | restriction | bc | fdcg | c | no_right_turn | + + When I route I should get + | waypoints | route | turns | lanes | + | a,g | road,cross,cross | depart,turn left,arrive | ,2 3, | + | a,e | road,road,road | depart,use lane straight,arrive | ,0 1, | + + Scenario: U-Turn Road at Intersection + Given the node map + | | | | | | h | | + | | | | | f | e | j | + | a | b | | | | | | + | | | | | c | d | i | + | | | | | | g | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | highway | + | ab | road | | no | primary | + | di | road | | yes | primary | + | bc | road | \|through&right | yes | primary | + | cd | road | \|through&right | yes | primary | + | fc | road | | no | tertiary | + | jefb | road | | yes | primary | + | gdeh | cross | | no | primary | + + When I route I should get + | from | to | bearings | route | turns | lanes | + | a | g | 180,180 180,180 | road,cross,cross | depart,turn right,arrive | ,0, | + | a | h | 180,180 180,180 | road,cross,cross | depart,turn left,arrive | ,2, | + | a | i | 180,180 180,180 | road,road,road | depart,use lane straight,arrive | ,1 2, | + | b | a | 90,2 270,2 | road,road,road | depart,continue uturn,arrive | ,2, | + + Scenario: Segregated Intersection Merges With Lanes + Given the node map + | | | | | | | f | + | | | | | | | | + | e | | | d | | | | + | | | | | | c | g | + | a | | | b | | | | + | | | | | | | | + | | | | | | h | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | highway | + | abc | road | left\|left&left&through&through | yes | primary | + | cde | road | | yes | primary | + | hc | cross | | yes | secondary | + | cg | straight | | no | tertiary | + | cf | left | | yes | primary | + + When I route I should get + | waypoints | route | turns | lanes | + | a,f | road,left,left | depart,turn left,arrive | ,2 3 4, | + | a,e | road,road,road | depart,turn uturn,arrive | ,4, | + | a,g | road,straight,straight | depart,new name straight,arrive | ,0 1, | + + Scenario: Passing Through a Roundabout + Given the node map + | | | h | | g | | | + | | a | | | | f | k | + | i | | | | | | | + | | | | | | | | + | | b | | | | e | | + | | | c | | d | | | + | | | | | j | | | + + And the ways + | nodes | name | turn:lanes:forward | oneway | highway | junction | + | efgha | round | | yes | primary | roundabout | + | ab | round | | yes | primary | roundabout | + | bc | round | slight_left\|slight_left&slight_right | yes | primary | roundabout | + | cd | round | | yes | primary | roundabout | + | de | round | slight_left\|slight_right | yes | primary | roundabout | + | ib | left | slight_left\|slight_left&slight_right | yes | primary | | + | cj | bottom | | yes | primary | | + | ek | right | | yes | primary | | + + When I route I should get + | waypoints | route | turns | lanes | + | i,j | left,bottom,bottom | depart,round-exit-1,arrive | ,0, | + | i,k | left,right,right | depart,round-exit-2,arrive | ,1, | + + Scenario: Crossing Traffic Light + Given the node map + | a | | b | | c | | d | + | | | | | | | e | + + And the nodes + | node | highway | + | b | traffic_signals | + + And the ways + | nodes | name | turn:lanes:forward | highway | + | abc | road | through\|through&through;slight_right&slight_right | primary | + | cd | road | | primary | + | ce | cross | | primary | + + When I route I should get + | waypoints | route | turns | lanes | + | a,d | road,road,road | depart,use lane straight,arrive | ,1 2 3, | + | a,e | road,cross,cross | depart,turn slight right,arrive | ,0 1, | + + Scenario: Highway Ramp + Given the node map + | a | | b | | c | | d | + | | | | | | | e | + + And the ways + | nodes | name | turn:lanes:forward | highway | + | abc | hwy | through\|through&through;slight_right&slight_right | motorway | + | cd | hwy | | motorway | + | ce | ramp | | motorway_link | + + When I route I should get + | waypoints | route | turns | lanes | + | a,d | hwy,hwy,hwy | depart,use lane straight,arrive | ,1 2 3, | + | a,e | hwy,ramp,ramp | depart,off ramp slight right,arrive | ,0 1, | + + Scenario: Turning Off Ramp + Given the node map + | | a | | + | d | c | b | + | e | f | g | + | | h | | + + And the ways + | nodes | name | turn:lanes:forward | highway | oneway | + | ac | off | left\|right | motorway_link | yes | + | bcd | road | | primary | yes | + | cf | road | | primary | | + | efg | road | | primary | yes | + | fh | on | | motorway_link | yes | + + When I route I should get + | waypoints | route | turns | lanes | + | a,d | off,road,road | depart,turn_right,arrive | ,0, | + | a,g | off,road,road | depart,turn_left,arrive | ,1, | + | a,h | | | | + + Scenario: Off Ramp In a Turn + Given the node map + | a | | | | | | | | | | | | + | | | | | | | | | | | | | + | | | | | | b | | | | | | c | + | | | | | | | | | | | d | | + + And the ways + | nodes | name | turn:lanes:forward | highway | oneway | + | ab | hwy | through\|through&slight_right | motorway | yes | + | bc | hwy | | motorway | yes | + | bd | ramp | | motorway_link | yes | + + When I route I should get + | waypoints | route | turns | lanes | + | a,c | hwy,hwy,hwy | depart,use lane slight left,arrive | ,1 2, | + | a,d | hwy,ramp,ramp | depart,off ramp slight right,arrive | ,0, | + + Scenario: Reverse Lane in Segregated Road + Given the node map + | h | | | | | g | | | | | | f | + | | | | | | | | e | | | | | + | | | | | | | | d | | | | | + | a | | | | | b | | | | | | c | + + And the ways + | nodes | name | turn:lanes:forward | highway | oneway | + | ab | road | reverse\|through&through | primary | yes | + | bc | road | | primary | yes | + | bdeg | road | | primary_link | yes | + | fgh | road | | primary | yes | + + When I route I should get + | waypoints | route | turns | lanes | + | a,h | road,road,road | depart,continue uturn,arrive | ,2, | + + Scenario: Reverse Lane in Segregated Road with none + Given the node map + | h | | | | | g | | | | | | f | + | | | | | | | | e | | | | | + | | | | | | | | d | | | | | + | a | | | | | b | | | | | | c | + + And the ways + | nodes | name | turn:lanes:forward | highway | oneway | + | ab | road | reverse\|through&none | primary | yes | + | bc | road | | primary | yes | + | bdeg | road | | primary_link | yes | + | fgh | road | | primary | yes | + + When I route I should get + | waypoints | route | turns | lanes | + | a,h | road,road,road | depart,continue uturn,arrive | ,2, | + + Scenario: Reverse Lane in Segregated Road with none, Service Turn Prior + Given the node map + | h | | | | | g | | | | | | f | + | | | | | | | | e | | | | | + | | | | | | | | d | | | | | + | a | | j | | | b | | | | | | c | + | | | i | | | | | | | | | | + + And the ways + | nodes | name | turn:lanes:forward | highway | oneway | + | ajb | road | reverse\|through&none | primary | yes | + | bc | road | | primary | yes | + | bdeg | road | | primary_link | yes | + | fgh | road | | primary | yes | + | ji | park | | service | no | + + When I route I should get + | waypoints | route | turns | lanes | + | a,h | road,road,road | depart,continue uturn,arrive | ,2, | + + Scenario: Don't collapse everything to u-turn / too wide + Given the node map + | a | | b | | e | + | | | | | | + | d | | c | | f | + + And the ways + | nodes | highway | name | turn:lanes:forward | + | ab | primary | road | through\|right | + | bc | primary | road | | + | dc | primary | road | left\|through | + | be | secondary | top | | + | cf | secondary | bottom | | + + When I route I should get + | waypoints | turns | route | lanes | + | a,d | depart,continue right,end of road right,arrive | road,road,road,road | ,0,, | + | d,a | depart,continue left,end of road left,arrive | road,road,road,road | ,1,, | + + Scenario: Merge Lanes Onto Freeway + Given the node map + | a | | | b | c | + | | d | | | | + + And the ways + | nodes | highway | name | turn:lanes:forward | + | abc | motorway | Hwy | | + | db | motorway_link | ramp | slight_right\|slight_right | + + When I route I should get + | waypoints | turns | route | lanes | + | d,c | depart,merge slight left,arrive | ramp,Hwy,Hwy | ,0 1, | + + Scenario: Fork on motorway links - don't fork on through but use lane + Given the node map + | i | | | | | a | + | j | | c | b | | x | + + And the ways + | nodes | name | highway | turn:lanes:forward | + | xb | xbcj | motorway_link | | + | bc | xbcj | motorway_link | none\|slight_right | + | cj | xbcj | motorway_link | | + | ci | off | motorway_link | | + | ab | on | motorway_link | | + + When I route I should get + | waypoints | route | turns | lanes | + | a,j | on,xbcj,xbcj,xbcj | depart,merge slight left,use lane straight,arrive | ,,1, | + | a,i | on,xbcj,off,off | depart,merge slight left,turn slight right,arrive | ,,0, | diff --git a/features/support/route.js b/features/support/route.js index ac214fcaf..20e052b5a 100644 --- a/features/support/route.js +++ b/features/support/route.js @@ -168,6 +168,10 @@ module.exports = function () { return instructions.legs.map(l => l.annotation.nodes.map(n => n.toString()).join(',')).join(','); }; + this.lanesList = (instructions) => { + return this.extractInstructionList(instructions, instruction => ('lanes' in instruction.maneuver ? instruction.maneuver.lanes.join(' ') : '')); + }; + this.turnList = (instructions) => { return instructions.legs.reduce((m, v) => m.concat(v.steps), []) .map(v => { diff --git a/features/support/shared_steps.js b/features/support/shared_steps.js index a6783bf96..6ffe62886 100644 --- a/features/support/shared_steps.js +++ b/features/support/shared_steps.js @@ -33,7 +33,7 @@ module.exports = function () { var afterRequest = (err, res, body) => { if (err) return cb(err); if (body && body.length) { - let destinations, pronunciations, instructions, bearings, turns, modes, times, distances, summary, intersections; + let destinations, pronunciations, instructions, bearings, turns, modes, times, distances, summary, intersections, lanes; let json = JSON.parse(body); @@ -49,6 +49,7 @@ module.exports = function () { modes = this.modeList(json.routes[0]); times = this.timeList(json.routes[0]); distances = this.distanceList(json.routes[0]); + lanes = this.lanesList(json.routes[0]); summary = this.summary(json.routes[0]); } @@ -102,6 +103,12 @@ module.exports = function () { got.time = instructions ? util.format('%ds', time) : ''; } + + if (headers.has('lanes')) { + got.lanes = (lanes || '').trim(); + } + + if (headers.has('speed')) { if (row.speed !== '' && instructions) { if (!row.speed.match(/\d+ km\/h/)) diff --git a/features/testbot/bearing.feature b/features/testbot/bearing.feature index b8aa4936e..8ca7e918b 100644 --- a/features/testbot/bearing.feature +++ b/features/testbot/bearing.feature @@ -59,10 +59,10 @@ Feature: Compass bearing Scenario: Bearing in a roundabout Given the node map - | | d | c | | + | k | d | c | j | | e | | | b | | f | | | a | - | | g | h | | + | l | g | h | i | And the ways | nodes | oneway | @@ -74,6 +74,14 @@ Feature: Compass bearing | fg | yes | | gh | yes | | ha | yes | + | dk | no | + | ke | no | + | fl | no | + | lg | no | + | hi | no | + | ia | no | + | bj | no | + | cj | no | When I route I should get | from | to | route | bearing | @@ -82,8 +90,10 @@ Feature: Compass bearing Scenario: Bearing should stay constant when zig-zagging Given the node map + | i | j | k | | | b | d | f | h | | a | c | e | g | + | | m | n | o | And the ways | nodes | @@ -94,6 +104,12 @@ Feature: Compass bearing | ef | | fg | | gh | + | bi | + | cm | + | dj | + | en | + | fk | + | go | When I route I should get | from | to | route | bearing | diff --git a/features/testbot/bearing_param.feature b/features/testbot/bearing_param.feature index c0c4072de..8650398c4 100644 --- a/features/testbot/bearing_param.feature +++ b/features/testbot/bearing_param.feature @@ -14,11 +14,11 @@ Feature: Bearing parameter | ad | When I route I should get - | from | to | bearings | route | bearing | + | from | to | bearings | route | bearing | | b | c | 90 90 | ad,ad | 0->90,90->0| - | b | c | 180 90 | | | + | b | c | 180 90 | | | | b | c | 80 100 | ad,ad | 0->90,90->0| - | b | c | 79 100 | | | + | b | c | 79 100 | | | | b | c | 79,11 100 | ad,ad | 0->90,90->0| Scenario: Testbot - Intial bearing in simple case @@ -33,13 +33,13 @@ Feature: Bearing parameter | bc | When I route I should get - | from | to | bearings | route | bearing | - | 0 | c | 0 0 | | | - | 0 | c | 45 45 | bc,bc | 0->44,44->0 +- 1| - | 0 | c | 85 85 | | | - | 0 | c | 95 95 | | | + | from | to | bearings | route | bearing | + | 0 | c | 0 0 | | | + | 0 | c | 45 45 | bc,bc | 0->44,44->0 +- 1 | + | 0 | c | 85 85 | | | + | 0 | c | 95 95 | | | | 0 | c | 135 135 | ac,ac | 0->135,135->0 +- 1| - | 0 | c | 180 180 | | | + | 0 | c | 180 180 | | | Scenario: Testbot - Initial bearing on split way Given the node map @@ -58,21 +58,21 @@ 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 | + | 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 | # The returned bearing is wrong here, it's based on the snapped # coordinates, not the acutal edge bearing. This should be # fixed one day, but it's only a problem when we snap two vias # to the same point - DP - #| 0 | b | 170 170 | da | 180 | - #| 0 | b | 189 189 | da | 180 | - | 0 | 1 | 90 270 | ab,bc,cd,cd | 0->90,90->0,0->270,270->0 | - | 1 | d | 10 10 | bc,bc | 0->0,0->0 | + #| 0 | b | 170 170 | da | 180 | + #| 0 | b | 189 189 | da | 180 | + | 0 | 1 | 90 270 | ab,bc,cd,cd | 0->90,90->0,0->270,270->0 | + | 1 | d | 10 10 | bc,bc | 0->0,0->0 | | 1 | d | 90 90 | ab,bc,cd,da,da | 0->90,90->0,0->270,270->180,180->0 | - | 1 | 0 | 189 189 | da,da | 0->180,180->0 | - | 1 | d | 270 270 | cd,cd | 0->270,270->0 | - | 1 | d | 349 349 | | | + | 1 | 0 | 189 189 | da,da | 0->180,180->0 | + | 1 | d | 270 270 | cd,cd | 0->270,270->0 | + | 1 | d | 349 349 | | | Scenario: Testbot - Initial bearing in all direction Given the node map diff --git a/features/testbot/continue_straight.feature b/features/testbot/continue_straight.feature index d761eadb6..df435bd84 100644 --- a/features/testbot/continue_straight.feature +++ b/features/testbot/continue_straight.feature @@ -3,7 +3,7 @@ Feature: U-turns at via points Background: Given the profile "testbot" - Given a grid size of 100 meters + Given a grid size of 250 meters Scenario: Continue straight at waypoints enabled by default Given the node map diff --git a/features/testbot/loop.feature b/features/testbot/loop.feature index f035ab0fb..d3d19bb42 100644 --- a/features/testbot/loop.feature +++ b/features/testbot/loop.feature @@ -75,12 +75,12 @@ Feature: Avoid weird loops caused by rounding errors And the node map | a | | | | b | e | | - | | | 1 | + | h | | 1 | | | | | | | | 2 | - | | | | + | g | | | | | c | f | - | | d | | + | d | | | And the ways | nodes | highway | @@ -90,6 +90,8 @@ Feature: Avoid weird loops caused by rounding errors | be | primary | | ef | primary | | cf | primary | + | cg | primary | + | bh | primary | When I route I should get | waypoints | route | diff --git a/features/testbot/oneway.feature b/features/testbot/oneway.feature index 6e2326d54..5165f2bd1 100644 --- a/features/testbot/oneway.feature +++ b/features/testbot/oneway.feature @@ -3,6 +3,7 @@ Feature: Testbot - oneways Background: Given the profile "testbot" + Given a grid size of 250 meters Scenario: Routing on a oneway roundabout Given the node map @@ -59,8 +60,8 @@ Feature: Testbot - oneways Scenario: Testbot - Around the Block Given the node map - | a | b | - | d | c | + | | a | b | | + | e | d | c | f | And the ways | nodes | oneway | foot | @@ -68,6 +69,8 @@ Feature: Testbot - oneways | bc | | no | | cd | | no | | da | | no | + | de | | no | + | cf | | no | When I route I should get | from | to | route | diff --git a/features/testbot/time.feature b/features/testbot/time.feature index 834b13b20..6fa32e3e7 100644 --- a/features/testbot/time.feature +++ b/features/testbot/time.feature @@ -145,14 +145,16 @@ Feature: Estimation of travel time Scenario: Time of travel on a series of ways Given the node map - | a | b | | - | | c | d | + | a | b | e | + | f | c | d | And the ways | nodes | highway | | ab | primary | | bc | primary | | cd | primary | + | be | primary | + | cf | primary | When I route I should get | from | to | route | time | diff --git a/features/testbot/via.feature b/features/testbot/via.feature index fd4a87214..08a684a5d 100644 --- a/features/testbot/via.feature +++ b/features/testbot/via.feature @@ -85,13 +85,16 @@ Feature: Via points Scenario: Via points on ring of oneways # xa it to avoid only having a single ring, which cna trigger edge cases Given the node map - | x | | | | | | | - | a | 1 | b | 2 | c | 3 | d | - | f | | | | | | e | + | | x | | | | | | g | | + | | a | 1 | b | 2 | c | 3 | d | | + | i | f | | | | | | e | h | And the ways | nodes | oneway | | xa | | + | if | | + | gd | | + | eh | | | ab | yes | | bc | yes | | cd | yes | @@ -110,13 +113,16 @@ Feature: Via points Scenario: Via points on ring on the same oneway # xa it to avoid only having a single ring, which cna trigger edge cases Given the node map - | x | | | | | - | a | 1 | 2 | 3 | b | - | d | | | | c | + | | x | | | | e | | + | | a | 1 | 2 | 3 | b | | + | g | d | | | | c | f | And the ways | nodes | oneway | | xa | | + | eb | | + | cf | | + | dg | | | ab | yes | | bc | yes | | cd | yes | diff --git a/include/engine/guidance/toolkit.hpp b/include/engine/guidance/toolkit.hpp index 056201381..452828787 100644 --- a/include/engine/guidance/toolkit.hpp +++ b/include/engine/guidance/toolkit.hpp @@ -63,12 +63,6 @@ inline extractor::guidance::DirectionModifier::Enum angleToDirectionModifier(con return extractor::guidance::DirectionModifier::Left; } -inline double angularDeviation(const double angle, const double from) -{ - const double deviation = std::abs(angle - from); - return std::min(360 - deviation, deviation); -} - } // namespace guidance } // namespace engine } // namespace osrm diff --git a/include/extractor/edge_based_graph_factory.hpp b/include/extractor/edge_based_graph_factory.hpp index afe52ea79..373c41111 100644 --- a/include/extractor/edge_based_graph_factory.hpp +++ b/include/extractor/edge_based_graph_factory.hpp @@ -55,7 +55,8 @@ class EdgeBasedGraphFactory std::shared_ptr restriction_map, const std::vector &node_info_list, ProfileProperties profile_properties, - const util::NameTable &name_table); + const util::NameTable &name_table, + const util::NameTable &turn_lanes); void Run(const std::string &original_edge_data_filename, lua_State *lua_state, @@ -117,6 +118,7 @@ class EdgeBasedGraphFactory ProfileProperties profile_properties; const util::NameTable &name_table; + const util::NameTable &turn_lanes; void CompressGeometry(); unsigned RenumberEdges(); diff --git a/include/extractor/extraction_containers.hpp b/include/extractor/extraction_containers.hpp index ca27e8dba..a03d3637c 100644 --- a/include/extractor/extraction_containers.hpp +++ b/include/extractor/extraction_containers.hpp @@ -37,7 +37,9 @@ class ExtractionContainers void WriteNodes(std::ofstream &file_out_stream) const; void WriteRestrictions(const std::string &restrictions_file_name) const; void WriteEdges(std::ofstream &file_out_stream) const; - void WriteNames(const std::string &names_file_name) const; + void WriteCharData(const std::string &file_name, + const stxxl::vector &offests, + const stxxl::vector &char_data) const; public: using STXXLNodeIDVector = stxxl::vector; @@ -51,6 +53,8 @@ class ExtractionContainers STXXLEdgeVector all_edges_list; stxxl::vector name_char_data; stxxl::vector name_lengths; + stxxl::vector turn_lane_char_data; + stxxl::vector turn_lane_lengths; STXXLRestrictionsVector restrictions_list; STXXLWayIDStartEndVector way_start_end_id_list; std::unordered_map external_to_internal_node_id_map; @@ -61,6 +65,7 @@ class ExtractionContainers void PrepareData(const std::string &output_file_name, const std::string &restrictions_file_name, const std::string &names_file_name, + const std::string &turn_lane_file_name, lua_State *segment_state); }; } diff --git a/include/extractor/extraction_helper_functions.hpp b/include/extractor/extraction_helper_functions.hpp index aefac6699..9fb5477d7 100644 --- a/include/extractor/extraction_helper_functions.hpp +++ b/include/extractor/extraction_helper_functions.hpp @@ -7,6 +7,8 @@ #include #include +#include "extractor/guidance/toolkit.hpp" + namespace osrm { namespace extractor @@ -98,6 +100,12 @@ inline unsigned parseDuration(const std::string &s) return !s.empty() && iter == s.end() ? duration : std::numeric_limits::max(); } + +inline std::string +trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t count_right) +{ + return extractor::guidance::trimLaneString(std::move(lane_string), count_left, count_right); +} } } diff --git a/include/extractor/extraction_way.hpp b/include/extractor/extraction_way.hpp index 437d17c11..cae6e8faf 100644 --- a/include/extractor/extraction_way.hpp +++ b/include/extractor/extraction_way.hpp @@ -3,6 +3,7 @@ #include "extractor/travel_mode.hpp" #include "util/typedefs.hpp" +#include "util/guidance/turn_lanes.hpp" #include #include @@ -35,6 +36,8 @@ struct ExtractionWay destinations.clear(); forward_travel_mode = TRAVEL_MODE_INACCESSIBLE; backward_travel_mode = TRAVEL_MODE_INACCESSIBLE; + turn_lanes_forward.clear(); + turn_lanes_backward.clear(); } // These accessors exists because it's not possible to take the address of a bitfield, @@ -50,6 +53,8 @@ struct ExtractionWay std::string name; std::string pronunciation; std::string destinations; + std::string turn_lanes_forward; + std::string turn_lanes_backward; bool roundabout; bool is_access_restricted; bool is_startpoint; diff --git a/include/extractor/extractor_callbacks.hpp b/include/extractor/extractor_callbacks.hpp index 120884dbf..2c5314a5f 100644 --- a/include/extractor/extractor_callbacks.hpp +++ b/include/extractor/extractor_callbacks.hpp @@ -38,6 +38,7 @@ class ExtractorCallbacks using MapKey = std::pair; using MapVal = unsigned; std::unordered_map> string_map; + std::unordered_map lane_map; ExtractionContainers &external_memory; public: diff --git a/include/extractor/extractor_config.hpp b/include/extractor/extractor_config.hpp index ec4d08631..fda782213 100644 --- a/include/extractor/extractor_config.hpp +++ b/include/extractor/extractor_config.hpp @@ -61,6 +61,7 @@ struct ExtractorConfig output_file_name = basepath + ".osrm"; restriction_file_name = basepath + ".osrm.restrictions"; names_file_name = basepath + ".osrm.names"; + turn_lane_file_name = basepath + ".osrm.tld"; timestamp_file_name = basepath + ".osrm.timestamp"; geometry_output_path = basepath + ".osrm.geometry"; node_output_path = basepath + ".osrm.nodes"; @@ -82,6 +83,7 @@ struct ExtractorConfig std::string output_file_name; std::string restriction_file_name; std::string names_file_name; + std::string turn_lane_file_name; std::string timestamp_file_name; std::string geometry_output_path; std::string edge_output_path; diff --git a/include/extractor/guidance/debug.hpp b/include/extractor/guidance/debug.hpp new file mode 100644 index 000000000..dd87ca515 --- /dev/null +++ b/include/extractor/guidance/debug.hpp @@ -0,0 +1,46 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_DEBUG_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_DEBUG_HPP_ + +#include +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +inline void print(const LaneDataVector &turn_lane_data) +{ + std::cout << " Tags:\n"; + for (auto entry : turn_lane_data) + std::cout << "\t" << entry.tag << " from: " << static_cast(entry.from) + << " to: " << static_cast(entry.to) << "\n"; + std::cout << std::flush; +} + +inline void printTurnAssignmentData(const NodeID at, + const LaneDataVector &turn_lane_data, + const Intersection &intersection, + const std::vector &node_info_list) +{ + std::cout << "[Turn Assignment Progress]\nLocation:"; + auto coordinate = node_info_list[at]; + std::cout << std::setprecision(12) << toFloating(coordinate.lat) << " " + << toFloating(coordinate.lon) << "\n"; + + std::cout << " Intersection:\n"; + for (const auto &road) + std::cout << "\t" << toString(road) << "\n"; + + //flushes as well + print(turn_lane_data); +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /* OSRM_EXTRACTOR_GUIDANCE_DEBUG_HPP_ */ diff --git a/include/extractor/guidance/intersection.hpp b/include/extractor/guidance/intersection.hpp index 8b350e24d..6665f91b1 100644 --- a/include/extractor/guidance/intersection.hpp +++ b/include/extractor/guidance/intersection.hpp @@ -59,6 +59,9 @@ std::string toString(const ConnectedRoad &road); typedef std::vector Intersection; +Intersection::const_iterator findClosestTurn(const Intersection &intersection, const double angle); +Intersection::iterator findClosestTurn(Intersection &intersection, const double angle); + } // namespace guidance } // namespace extractor } // namespace osrm diff --git a/include/extractor/guidance/intersection_generator.hpp b/include/extractor/guidance/intersection_generator.hpp index be99a71c9..e2a72934e 100644 --- a/include/extractor/guidance/intersection_generator.hpp +++ b/include/extractor/guidance/intersection_generator.hpp @@ -7,6 +7,7 @@ #include "extractor/restriction_map.hpp" #include "util/node_based_graph.hpp" #include "util/typedefs.hpp" +#include "util/name_table.hpp" #include #include diff --git a/include/extractor/guidance/motorway_handler.hpp b/include/extractor/guidance/motorway_handler.hpp index 75abe88a8..e8830aa74 100644 --- a/include/extractor/guidance/motorway_handler.hpp +++ b/include/extractor/guidance/motorway_handler.hpp @@ -2,7 +2,6 @@ #define OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_ #include "extractor/guidance/intersection.hpp" -#include "extractor/guidance/intersection_generator.hpp" #include "extractor/guidance/intersection_handler.hpp" #include "extractor/query_node.hpp" @@ -26,8 +25,7 @@ class MotorwayHandler : public IntersectionHandler MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph, const std::vector &node_info_list, const util::NameTable &name_table, - const SuffixTable &street_name_suffix_table, - const IntersectionGenerator &intersection_generator); + const SuffixTable &street_name_suffix_table); ~MotorwayHandler() override final; // check whether the handler can actually handle the intersection @@ -47,8 +45,6 @@ class MotorwayHandler : public IntersectionHandler Intersection fromRamp(const EdgeID via_edge, Intersection intersection) const; Intersection fallback(Intersection intersection) const; - - const IntersectionGenerator &intersection_generator; }; } // namespace guidance diff --git a/include/extractor/guidance/toolkit.hpp b/include/extractor/guidance/toolkit.hpp index bb5281e48..f44820ab7 100644 --- a/include/extractor/guidance/toolkit.hpp +++ b/include/extractor/guidance/toolkit.hpp @@ -476,6 +476,77 @@ inline bool hasRoundaboutType(const TurnInstruction instruction) return std::find(valid_types, valid_end, instruction.type) != valid_end; } +// Public service vehicle lanes and similar can introduce additional lanes into the lane string that +// are not specifically marked for left/right turns. This function can be used from the profile to +// trim the lane string appropriately +// +// left|throught| +// in combination with lanes:psv:forward=1 +// will be corrected to left|throught, since the final lane is not drivable. +// This is in contrast to a situation with lanes:psv:forward=0 (or not set) where left|through| +// represents left|through|through +inline std::string +trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t count_right) +{ + if (count_left) + { + bool sane = count_left < static_cast(lane_string.size()); + for (std::int32_t i = 0; i < count_left; ++i) + // this is adjusted for our fake pipe. The moment cucumber can handle multiple escaped + // pipes, the '&' part can be removed + if (lane_string[i] != '|' && lane_string[i] != '&') + { + sane = false; + break; + } + + if (sane) + { + lane_string.erase(lane_string.begin(), lane_string.begin() + count_left); + } + } + if (count_right) + { + bool sane = count_right < static_cast(lane_string.size()); + for (auto itr = lane_string.rbegin(); + itr != lane_string.rend() && itr != lane_string.rbegin() + count_right; + ++itr) + { + if (*itr != '|' && *itr != '&') + { + sane = false; + break; + } + } + if (sane) + lane_string.resize(lane_string.size() - count_right); + } + return lane_string; +} + +inline bool entersRoundabout(const extractor::guidance::TurnInstruction instruction) +{ + return (instruction.type == extractor::guidance::TurnType::EnterRoundabout || + instruction.type == extractor::guidance::TurnType::EnterRotary || + instruction.type == extractor::guidance::TurnType::EnterRoundaboutIntersection || + instruction.type == extractor::guidance::TurnType::EnterRoundaboutAtExit || + instruction.type == extractor::guidance::TurnType::EnterRotaryAtExit || + instruction.type == extractor::guidance::TurnType::EnterRoundaboutIntersectionAtExit || + instruction.type == extractor::guidance::TurnType::EnterAndExitRoundabout || + instruction.type == extractor::guidance::TurnType::EnterAndExitRotary || + instruction.type == extractor::guidance::TurnType::EnterAndExitRotary); +} + +inline bool leavesRoundabout(const extractor::guidance::TurnInstruction instruction) +{ + return (instruction.type == extractor::guidance::TurnType::ExitRoundabout || + instruction.type == extractor::guidance::TurnType::ExitRotary || + instruction.type == extractor::guidance::TurnType::ExitRoundaboutIntersection || + instruction.type == extractor::guidance::TurnType::EnterAndExitRoundabout || + instruction.type == extractor::guidance::TurnType::EnterAndExitRotary || + instruction.type == extractor::guidance::TurnType::EnterAndExitRoundaboutIntersection); +} + } // namespace guidance } // namespace extractor } // namespace osrm diff --git a/include/extractor/guidance/turn_analysis.hpp b/include/extractor/guidance/turn_analysis.hpp index cade181ad..d1a880e1d 100644 --- a/include/extractor/guidance/turn_analysis.hpp +++ b/include/extractor/guidance/turn_analysis.hpp @@ -44,10 +44,14 @@ class TurnAnalysis const SuffixTable &street_name_suffix_table); // the entry into the turn analysis - std::vector getTurns(const NodeID from_node, const EdgeID via_eid) const; - - // access to the intersection representation for classification purposes Intersection getIntersection(const NodeID from_node, const EdgeID via_eid) const; + Intersection + assignTurnTypes(const NodeID from_node, const EdgeID via_eid, Intersection intersection) const; + + std::vector + transformIntersectionIntoTurns(const Intersection &intersection) const; + + const IntersectionGenerator& getGenerator() const; private: const util::NodeBasedDynamicGraph &node_based_graph; diff --git a/include/extractor/guidance/turn_discovery.hpp b/include/extractor/guidance/turn_discovery.hpp new file mode 100644 index 000000000..3a36d3d57 --- /dev/null +++ b/include/extractor/guidance/turn_discovery.hpp @@ -0,0 +1,38 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_DISCOVERY_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_TURN_DISCOVERY_HPP_ + +#include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/turn_analysis.hpp" +#include "util/typedefs.hpp" + +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +// OSRM processes edges by looking at a via_edge, coming into an intersection. For turn lanes, we +// might require to actually look back a turn. We do so in the hope that the turn lanes match up at +// the previous intersection for all incoming lanes. +bool findPreviousIntersection( + const NodeID node, + const EdgeID via_edge, + const Intersection intersection, + const TurnAnalysis &turn_analysis, // to generate other intersections + const util::NodeBasedDynamicGraph &node_based_graph, // query edge data + // output parameters, will be in an arbitrary state on failure + NodeID &result_node, + EdgeID &result_via_edge, + Intersection &result_intersection); + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /*OSRM_EXTRACTOR_GUIDANCE_TURN_DISCOVERY_HPP_*/ diff --git a/include/extractor/guidance/turn_instruction.hpp b/include/extractor/guidance/turn_instruction.hpp index dc3ef5a70..d451697ec 100644 --- a/include/extractor/guidance/turn_instruction.hpp +++ b/include/extractor/guidance/turn_instruction.hpp @@ -6,6 +6,8 @@ #include #include "extractor/guidance/roundabout_type.hpp" +#include "util/guidance/turn_lanes.hpp" +#include "util/typedefs.hpp" namespace osrm { @@ -53,48 +55,47 @@ const constexpr Enum EnterRotary = 12; // Enter a rotary const constexpr Enum EnterAndExitRotary = 13; // Touching a rotary const constexpr Enum EnterRoundaboutIntersection = 14; // Entering a small Roundabout const constexpr Enum EnterAndExitRoundaboutIntersection = 15; // Touching a roundabout +const constexpr Enum UseLane = 16; // No Turn, but you need to stay on a given lane! // Values below here are silent instructions -const constexpr Enum NoTurn = 16; // end of segment without turn/middle of a segment -const constexpr Enum Suppressed = 17; // location that suppresses a turn -const constexpr Enum EnterRoundaboutAtExit = 18; // Entering a small Roundabout at a countable exit -const constexpr Enum ExitRoundabout = 19; // Exiting a small Roundabout -const constexpr Enum EnterRotaryAtExit = 20; // Enter A Rotary at a countable exit -const constexpr Enum ExitRotary = 21; // Exit a rotary +const constexpr Enum NoTurn = 17; // end of segment without turn/middle of a segment +const constexpr Enum Suppressed = 18; // location that suppresses a turn +const constexpr Enum EnterRoundaboutAtExit = 19; // Entering a small Roundabout at a countable exit +const constexpr Enum ExitRoundabout = 20; // Exiting a small Roundabout +const constexpr Enum EnterRotaryAtExit = 21; // Enter A Rotary at a countable exit +const constexpr Enum ExitRotary = 22; // Exit a rotary const constexpr Enum EnterRoundaboutIntersectionAtExit = - 22; // Entering a small Roundabout at a countable exit -const constexpr Enum ExitRoundaboutIntersection = 23; // Exiting a small Roundabout -const constexpr Enum StayOnRoundabout = 24; // Continue on Either a small or a large Roundabout + 23; // Entering a small Roundabout at a countable exit +const constexpr Enum ExitRoundaboutIntersection = 24; // Exiting a small Roundabout +const constexpr Enum StayOnRoundabout = 25; // Continue on Either a small or a large Roundabout const constexpr Enum Sliproad = - 25; // Something that looks like a ramp, but is actually just a small sliproad + 26; // Something that looks like a ramp, but is actually just a small sliproad } // turn angle in 1.40625 degree -> 128 == 180 degree struct TurnInstruction { + using LaneTupel = util::guidance::LaneTupel; TurnInstruction(const TurnType::Enum type = TurnType::Invalid, - const DirectionModifier::Enum direction_modifier = DirectionModifier::Straight) - : type(type), direction_modifier(direction_modifier) + const DirectionModifier::Enum direction_modifier = DirectionModifier::Straight, + const LaneTupel lane_tupel = {0, INVALID_LANEID}) + : type(type), direction_modifier(direction_modifier), lane_tupel(lane_tupel) { } TurnType::Enum type : 5; DirectionModifier::Enum direction_modifier : 3; + // the lane tupel that is used for the turn + LaneTupel lane_tupel; - static TurnInstruction INVALID() - { - return TurnInstruction(TurnType::Invalid, DirectionModifier::UTurn); - } + static TurnInstruction INVALID() { return {TurnType::Invalid, DirectionModifier::UTurn}; } - static TurnInstruction NO_TURN() - { - return TurnInstruction(TurnType::NoTurn, DirectionModifier::UTurn); - } + static TurnInstruction NO_TURN() { return {TurnType::NoTurn, DirectionModifier::UTurn}; } static TurnInstruction REMAIN_ROUNDABOUT(const RoundaboutType, const DirectionModifier::Enum modifier) { - return TurnInstruction(TurnType::StayOnRoundabout, modifier); + return {TurnType::StayOnRoundabout, modifier}; } static TurnInstruction ENTER_ROUNDABOUT(const RoundaboutType roundabout_type, @@ -146,16 +147,18 @@ struct TurnInstruction } }; -static_assert(sizeof(TurnInstruction) == 1, "TurnInstruction does not fit one byte"); +static_assert(sizeof(TurnInstruction) == 3, "TurnInstruction does not fit three byte"); inline bool operator!=(const TurnInstruction lhs, const TurnInstruction rhs) { - return lhs.type != rhs.type || lhs.direction_modifier != rhs.direction_modifier; + return lhs.type != rhs.type || lhs.direction_modifier != rhs.direction_modifier || + lhs.lane_tupel != rhs.lane_tupel; } inline bool operator==(const TurnInstruction lhs, const TurnInstruction rhs) { - return lhs.type == rhs.type && lhs.direction_modifier == rhs.direction_modifier; + return lhs.type == rhs.type && lhs.direction_modifier == rhs.direction_modifier && + lhs.lane_tupel == rhs.lane_tupel; } } // namespace guidance diff --git a/include/extractor/guidance/turn_lane_augmentation.hpp b/include/extractor/guidance/turn_lane_augmentation.hpp new file mode 100644 index 000000000..81de8f999 --- /dev/null +++ b/include/extractor/guidance/turn_lane_augmentation.hpp @@ -0,0 +1,24 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_AUGMENTATION_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_AUGMENTATION_HPP_ + +#include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/turn_lane_data.hpp" + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data, + const Intersection &intersection); + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /* OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_AUGMENTATION_HPP_ */ diff --git a/include/extractor/guidance/turn_lane_data.hpp b/include/extractor/guidance/turn_lane_data.hpp new file mode 100644 index 000000000..b35c5ccd5 --- /dev/null +++ b/include/extractor/guidance/turn_lane_data.hpp @@ -0,0 +1,42 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_DATA_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_DATA_HPP_ + +#include "util/typedefs.hpp" +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +struct TurnLaneData +{ + std::string tag; + LaneID from; + LaneID to; + + bool operator<(const TurnLaneData &other) const; +}; +typedef std::vector LaneDataVector; + +// convertes a string given in the OSM format into a TurnLaneData vector +LaneDataVector laneDataFromString(std::string turn_lane_string); + +// Locate A Tag in a lane data vector +LaneDataVector::const_iterator findTag(const std::string &tag, const LaneDataVector &data); +LaneDataVector::iterator findTag(const std::string &tag, LaneDataVector &data); + +bool hasTag(const std::string &tag, const LaneDataVector &data); + +} // namespace lane_data_generation + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /* OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_DATA_HPP_ */ diff --git a/include/extractor/guidance/turn_lane_handler.hpp b/include/extractor/guidance/turn_lane_handler.hpp new file mode 100644 index 000000000..3320d6f79 --- /dev/null +++ b/include/extractor/guidance/turn_lane_handler.hpp @@ -0,0 +1,79 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_HANDLER_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_HANDLER_HPP_ + +#include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/turn_analysis.hpp" +#include "extractor/guidance/turn_lane_data.hpp" +#include "extractor/query_node.hpp" + +#include "util/guidance/turn_lanes.hpp" +#include "util/name_table.hpp" +#include "util/node_based_graph.hpp" +#include "util/typedefs.hpp" + +#include +#include +#include +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +// Given an Intersection, the graph to access the data and the turn lanes, the turn lane matcher +// assigns appropriate turn tupels to the different turns. +namespace lanes +{ +class TurnLaneHandler +{ + public: + typedef std::vector LaneDataVector; + + TurnLaneHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const util::NameTable &turn_lane_strings, + const std::vector &node_info_list, + const TurnAnalysis &turn_analysis); + + Intersection + assignTurnLanes(const NodeID at, const EdgeID via_edge, Intersection intersection) const; + + private: + using LaneTupel = util::guidance::LaneTupel; + std::unordered_map lane_tupels; + + // we need to be able to look at previous intersections to, in some cases, find the correct turn + // lanes for a turn + const util::NodeBasedDynamicGraph &node_based_graph; + const util::NameTable &turn_lane_strings; + const std::vector &node_info_list; + const TurnAnalysis &turn_analysis; + + // check whether we can handle an intersection + bool isSimpleIntersection(const LaneDataVector &turn_lane_data, + const Intersection &intersection) const; + + // in case of a simple intersection, assign the lane entries + Intersection simpleMatchTuplesToTurns(Intersection intersection, + const LaneDataVector &lane_data) const; + + // partition lane data into lane data relevant at current turn and at next turn + std::pair partitionLaneData( + const NodeID at, LaneDataVector turn_lane_data, const Intersection &intersection) const; + + // if the current intersections turn string is empty, we check whether there is an incoming + // intersection whose turns might be related to this current intersection + Intersection handleTurnAtPreviousIntersection(const NodeID at, + const EdgeID via_edge, + Intersection intersection) const; +}; + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif // OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_HANDLER_HPP_ diff --git a/include/extractor/guidance/turn_lane_matcher.hpp b/include/extractor/guidance/turn_lane_matcher.hpp new file mode 100644 index 000000000..5b2635339 --- /dev/null +++ b/include/extractor/guidance/turn_lane_matcher.hpp @@ -0,0 +1,54 @@ +#ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_MATCHER_HPP_ +#define OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_MATCHER_HPP_ + +#include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/turn_instruction.hpp" +#include "extractor/guidance/turn_lane_data.hpp" + +#include "util/guidance/turn_lanes.hpp" +#include "util/node_based_graph.hpp" + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +// Translate Turn Lane Tags into a matching modifier +DirectionModifier::Enum getMatchingModifier(const std::string &tag); + +// check whether a match of a given tag and a turn instruction can be seen as valid +bool isValidMatch(const std::string &tag, const TurnInstruction instruction); + +// Every tag is somewhat idealized in form of the expected angle. A through lane should go straight +// (or follow a 180 degree turn angle between in/out segments.) The following function tries to find +// the best possible match for every tag in a given intersection, considering a few corner cases +// introduced to OSRM handling u-turns +typename Intersection::const_iterator findBestMatch(const std::string &tag, + const Intersection &intersection); +// Reverse is a special case, because it requires access to the leftmost tag. It has its own +// matching function as a result of that. The leftmost tag is required, since u-turns are disabled +// by default in OSRM. Therefor we cannot check whether a turn is allowed, since it could be +// possible that it is forbidden. In addition, the best u-turn angle does not necessarily represent +// the u-turn, since it could be a sharp-left turn instead on a road with a middle island. +typename Intersection::const_iterator findBestMatchForReverse(const std::string &leftmost_tag, + const Intersection &intersection); + +// a match is trivial if all turns can be associated with their best match in a valid way and the +// matches occur in order +bool canMatchTrivially(const Intersection &intersection, const LaneDataVector &lane_data); + +// perform a trivial match on the turn lanes +Intersection triviallyMatchLanesToTurns(Intersection intersection, + const LaneDataVector &lane_data, + const util::NodeBasedDynamicGraph &node_based_graph); + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif /*OSRM_EXTRACTOR_GUIDANCE_TURN_LANE_MATCHER_HPP_*/ diff --git a/include/extractor/internal_extractor_edge.hpp b/include/extractor/internal_extractor_edge.hpp index 14ad1981d..5a892170d 100644 --- a/include/extractor/internal_extractor_edge.hpp +++ b/include/extractor/internal_extractor_edge.hpp @@ -29,7 +29,6 @@ struct InternalExtractorEdge struct WeightData { - WeightData() : duration(0.0), type(WeightType::INVALID) {} union { @@ -51,6 +50,7 @@ struct InternalExtractorEdge true, TRAVEL_MODE_INACCESSIBLE, false, + INVALID_LANE_STRINGID, guidance::RoadClassificationData()) { } @@ -66,6 +66,7 @@ struct InternalExtractorEdge bool startpoint, TravelMode travel_mode, bool is_split, + const LaneStringID lane_id, guidance::RoadClassificationData road_classification) : result(OSMNodeID(source), OSMNodeID(target), @@ -78,6 +79,7 @@ struct InternalExtractorEdge startpoint, travel_mode, is_split, + lane_id, std::move(road_classification)), weight_data(std::move(weight_data)) { @@ -104,6 +106,7 @@ struct InternalExtractorEdge true, TRAVEL_MODE_INACCESSIBLE, false, + INVALID_LANE_STRINGID, guidance::RoadClassificationData()); } static InternalExtractorEdge max_osm_value() @@ -119,6 +122,7 @@ struct InternalExtractorEdge true, TRAVEL_MODE_INACCESSIBLE, false, + INVALID_LANE_STRINGID, guidance::RoadClassificationData()); } diff --git a/include/extractor/node_based_edge.hpp b/include/extractor/node_based_edge.hpp index 47090e6f7..4e050df85 100644 --- a/include/extractor/node_based_edge.hpp +++ b/include/extractor/node_based_edge.hpp @@ -26,6 +26,7 @@ struct NodeBasedEdge bool startpoint, TravelMode travel_mode, bool is_split, + const LaneStringID lane_id, guidance::RoadClassificationData road_classification); bool operator<(const NodeBasedEdge &other) const; @@ -41,6 +42,7 @@ struct NodeBasedEdge bool startpoint : 1; bool is_split : 1; TravelMode travel_mode : 4; + LaneStringID lane_string_id; guidance::RoadClassificationData road_classification; }; @@ -57,6 +59,7 @@ struct NodeBasedEdgeWithOSM : NodeBasedEdge bool startpoint, TravelMode travel_mode, bool is_split, + const LaneStringID lane_string_id, guidance::RoadClassificationData road_classification); OSMNodeID osm_source_id; @@ -68,7 +71,7 @@ struct NodeBasedEdgeWithOSM : NodeBasedEdge inline NodeBasedEdge::NodeBasedEdge() : source(SPECIAL_NODEID), target(SPECIAL_NODEID), name_id(0), weight(0), forward(false), backward(false), roundabout(false), access_restricted(false), startpoint(true), - is_split(false), travel_mode(false) + is_split(false), travel_mode(false), lane_string_id(0) { } @@ -83,10 +86,11 @@ inline NodeBasedEdge::NodeBasedEdge(NodeID source, bool startpoint, TravelMode travel_mode, bool is_split, + const LaneStringID lane_string_id, guidance::RoadClassificationData road_classification) : source(source), target(target), name_id(name_id), weight(weight), forward(forward), backward(backward), roundabout(roundabout), access_restricted(access_restricted), - startpoint(startpoint), is_split(is_split), travel_mode(travel_mode), + startpoint(startpoint), is_split(is_split), travel_mode(travel_mode), lane_string_id(lane_string_id), road_classification(std::move(road_classification)) { } @@ -120,6 +124,7 @@ inline NodeBasedEdgeWithOSM::NodeBasedEdgeWithOSM( bool startpoint, TravelMode travel_mode, bool is_split, + const LaneStringID lane_string_id, guidance::RoadClassificationData road_classification) : NodeBasedEdge(SPECIAL_NODEID, SPECIAL_NODEID, @@ -132,6 +137,7 @@ inline NodeBasedEdgeWithOSM::NodeBasedEdgeWithOSM( startpoint, travel_mode, is_split, + lane_string_id, std::move(road_classification)), osm_source_id(std::move(source)), osm_target_id(std::move(target)) { diff --git a/include/util/guidance/toolkit.hpp b/include/util/guidance/toolkit.hpp index 2c458abca..9d13ecc99 100644 --- a/include/util/guidance/toolkit.hpp +++ b/include/util/guidance/toolkit.hpp @@ -65,6 +65,42 @@ mirrorDirectionModifier(const extractor::guidance::DirectionModifier::Enum modif return results[modifier]; } +inline bool hasLeftModifier(const extractor::guidance::TurnInstruction instruction) +{ + return instruction.direction_modifier == extractor::guidance::DirectionModifier::SharpLeft || + instruction.direction_modifier == extractor::guidance::DirectionModifier::Left || + instruction.direction_modifier == extractor::guidance::DirectionModifier::SlightLeft; +} + +inline bool hasRightModifier(const extractor::guidance::TurnInstruction instruction) +{ + return instruction.direction_modifier == extractor::guidance::DirectionModifier::SharpRight || + instruction.direction_modifier == extractor::guidance::DirectionModifier::Right || + instruction.direction_modifier == extractor::guidance::DirectionModifier::SlightRight; +} + +inline bool isLeftTurn(const extractor::guidance::TurnInstruction instruction) +{ + switch (instruction.type) + { + case extractor::guidance::TurnType::Merge: + return hasRightModifier(instruction); + default: + return hasLeftModifier(instruction); + } +} + +inline bool isRightTurn(const extractor::guidance::TurnInstruction instruction) +{ + switch (instruction.type) + { + case extractor::guidance::TurnType::Merge: + return hasLeftModifier(instruction); + default: + return hasRightModifier(instruction); + } +} + } // namespace guidance } // namespace util } // namespace osrm diff --git a/include/util/guidance/turn_lanes.hpp b/include/util/guidance/turn_lanes.hpp new file mode 100644 index 000000000..ce466132a --- /dev/null +++ b/include/util/guidance/turn_lanes.hpp @@ -0,0 +1,85 @@ +#ifndef OSRM_UTIL_GUIDANCE_TURN_LANES_HPP +#define OSRM_UTIL_GUIDANCE_TURN_LANES_HPP + +#include +#include +#include +#include + +#include "util/typedefs.hpp" + +#include + +namespace osrm +{ +namespace util +{ +namespace guidance +{ +class LaneTupel; +} // namespace guidance +} // namespace util +} // namespace osrm + +namespace std +{ +template <> struct hash<::osrm::util::guidance::LaneTupel> +{ + inline std::size_t operator()(const ::osrm::util::guidance::LaneTupel &bearing_class) const; +}; +} // namespace std + +namespace osrm +{ +namespace util +{ +namespace guidance +{ + +// The mapping of turn lanes can be done two values. We describe every turn by the number of +// contributing lanes and the first lane from the right.. +// Given a road like this: +// | | | +// | | | +// ----------- | +// -^ | +// ----------- ------------- +// -^ -> +// -------------------------------- +// -v | +// ---------- | +// | | +// +// we generate a set of tuples in the form of: +// (2,1), (1,1), (1,0) for left, through and right respectively +class LaneTupel +{ + public: + LaneTupel(); + LaneTupel(const LaneID lanes_in_turn, const LaneID first_lane_from_the_right); + + bool operator==(const LaneTupel other) const; + bool operator!=(const LaneTupel other) const; + bool operator<(const LaneTupel other) const; + + LaneID lanes_in_turn; + LaneID first_lane_from_the_right; + + friend std::size_t std::hash::operator()(const LaneTupel &) const; +}; + +} // namespace guidance +} // namespace util +} // namespace osrm + +// make Bearing Class hasbable +namespace std +{ +inline size_t hash<::osrm::util::guidance::LaneTupel>:: +operator()(const ::osrm::util::guidance::LaneTupel &lane_tupel) const +{ + return boost::hash_value(*reinterpret_cast(&lane_tupel)); +} +} // namespace std + +#endif /* OSRM_UTIL_GUIDANCE_TURN_LANES_HPP */ diff --git a/include/util/node_based_graph.hpp b/include/util/node_based_graph.hpp index 801e9b61c..41882201c 100644 --- a/include/util/node_based_graph.hpp +++ b/include/util/node_based_graph.hpp @@ -20,7 +20,7 @@ struct NodeBasedEdgeData NodeBasedEdgeData() : distance(INVALID_EDGE_WEIGHT), edge_id(SPECIAL_NODEID), name_id(std::numeric_limits::max()), access_restricted(false), reversed(false), - roundabout(false), travel_mode(TRAVEL_MODE_INACCESSIBLE) + roundabout(false), travel_mode(TRAVEL_MODE_INACCESSIBLE), lane_string_id(INVALID_LANE_STRINGID) { } @@ -31,10 +31,11 @@ struct NodeBasedEdgeData bool reversed, bool roundabout, bool startpoint, - extractor::TravelMode travel_mode) + extractor::TravelMode travel_mode, + const LaneStringID lane_string_id) : distance(distance), edge_id(edge_id), name_id(name_id), access_restricted(access_restricted), reversed(reversed), roundabout(roundabout), - startpoint(startpoint), travel_mode(travel_mode) + startpoint(startpoint), travel_mode(travel_mode), lane_string_id(lane_string_id) { } @@ -46,6 +47,7 @@ struct NodeBasedEdgeData bool roundabout : 1; bool startpoint : 1; extractor::TravelMode travel_mode : 4; + LaneStringID lane_string_id; extractor::guidance::RoadClassificationData road_classification; bool IsCompatibleTo(const NodeBasedEdgeData &other) const @@ -80,6 +82,7 @@ NodeBasedDynamicGraphFromEdges(NodeID number_of_nodes, output_edge.data.travel_mode = input_edge.travel_mode; output_edge.data.startpoint = input_edge.startpoint; output_edge.data.road_classification = input_edge.road_classification; + output_edge.data.lane_string_id = input_edge.lane_string_id; }); tbb::parallel_sort(edges_list.begin(), edges_list.end()); diff --git a/include/util/typedefs.hpp b/include/util/typedefs.hpp index 37b832a2f..ef4c9aedf 100644 --- a/include/util/typedefs.hpp +++ b/include/util/typedefs.hpp @@ -59,13 +59,18 @@ using EdgeID = std::uint32_t; using NameID = std::uint32_t; using EdgeWeight = std::int32_t; +using LaneStringID = std::uint16_t; +using LaneID = std::uint8_t; +static const LaneID INVALID_LANEID = std::numeric_limits::max(); +static const LaneStringID INVALID_LANE_STRINGID = std::numeric_limits::max(); + using BearingClassID = std::uint32_t; -static const BearingClassID INVALID_BEARING_CLASSID = std::numeric_limits::max(); +static const BearingClassID INVALID_BEARING_CLASSID = std::numeric_limits::max(); using DiscreteBearing = std::uint16_t; using EntryClassID = std::uint16_t; -static const EntryClassID INVALID_ENTRY_CLASSID = std::numeric_limits::max(); +static const EntryClassID INVALID_ENTRY_CLASSID = std::numeric_limits::max(); static const NodeID SPECIAL_NODEID = std::numeric_limits::max(); static const NodeID SPECIAL_SEGMENTID = std::numeric_limits::max() >> 1; diff --git a/profiles/car.lua b/profiles/car.lua index becf502c0..78c51bb7a 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -166,6 +166,56 @@ function get_exceptions(vector) end end +-- returns forward,backward psv lane count +local function getPSVCounts(way) + local psv = way:get_value_by_key("lanes:psv") + local psv_forward = way:get_value_by_key("lanes:psv:forward"); + local psv_backward = way:get_value_by_key("lanes:psv:backward"); + + local fw = 0; + local bw = 0; + if( psv and psv ~= "" ) then + fw = tonumber(psv) + if( fw == nil ) then + fw = 0 + end + end + if( psv_forward and psv_forward ~= "" ) then + fw = tonumber(psv_forward) + if( fw == nil ) then + fw = 0 + end + end + if( psv_backward and psv_backward ~= "" ) then + bw = tonumber(psv_backward); + if( bw == nil ) then + fw = 0 + end + end + return fw, bw +end + +-- this is broken for left-sided driving. It needs to switch left and right in case of left-sided driving +local function getTurnLanes(way) + local fw_psv = 0 + local bw_psv = 0 + fw_psv, bw_psv = getPSVCounts(way) + + local turn_lanes = way:get_value_by_key("turn:lanes") + local turn_lanes_fw = way:get_value_by_key("turn:lanes:forward") + local turn_lanes_bw = way:get_value_by_key("turn:lanes:backward") + + if( fw_psv ~= 0 or bw_psv ~= 0 ) then + turn_lanes = trimLaneString(turn_lanes, bw_psv, fw_psv ) + turn_lanes_fw = trimLaneString(turn_lanes_fw, bw_psv, fw_psv ) + --backwards turn lanes need to treat bw_psv as fw_psv and vice versa + turn_lanes_bw = trimLaneString(turn_lanes_bw, fw_psv, bw_psv ) + end + + return turn_lanes, turn_lanes_fw, turn_lanes_bw +end + + local function parse_maxspeed(source) if not source then return 0 @@ -372,6 +422,25 @@ function way_function (way, result) result.pronunciation = pronunciation end + local turn_lanes = "" + local turn_lanes_forward = "" + local turn_lanes_backward = "" + + turn_lanes, turn_lanes_forward, turn_lanes_backward = getTurnLanes(way) + if( turn_lanes ~= "" ) then + result.turn_lanes_forward = turn_lanes; + result.turn_lanes_backward = turn_lanes; + else + if( turn_lanes_forward ~= "" ) then + result.turn_lanes_forward = turn_lanes_forward; + end + + if( turn_lanes_backward ~= "" ) then + result.turn_lanes_backward = turn_lanes_backward; + end + end + + if junction and "roundabout" == junction then result.roundabout = true end diff --git a/profiles/testbot.lua b/profiles/testbot.lua index 8bad8100c..57ae76302 100644 --- a/profiles/testbot.lua +++ b/profiles/testbot.lua @@ -57,6 +57,7 @@ function way_function (way, result) if name then result.name = name end + result.forward_mode = mode.driving result.backward_mode = mode.driving diff --git a/src/contractor/contractor.cpp b/src/contractor/contractor.cpp index 6b826f9e0..a236483b6 100644 --- a/src/contractor/contractor.cpp +++ b/src/contractor/contractor.cpp @@ -76,7 +76,7 @@ int Contractor::Run() #ifdef WIN32 #pragma message("Memory consumption on Windows can be higher due to different bit packing") #else - static_assert(sizeof(extractor::NodeBasedEdge) == 20, + static_assert(sizeof(extractor::NodeBasedEdge) == 24, "changing extractor::NodeBasedEdge type has influence on memory consumption!"); static_assert(sizeof(extractor::EdgeBasedEdge) == 16, "changing EdgeBasedEdge type has influence on memory consumption!"); diff --git a/src/engine/api/json_factory.cpp b/src/engine/api/json_factory.cpp index 464003a2b..cd1b3d874 100644 --- a/src/engine/api/json_factory.cpp +++ b/src/engine/api/json_factory.cpp @@ -7,6 +7,7 @@ #include "util/guidance/bearing_class.hpp" #include "util/guidance/entry_class.hpp" #include "util/guidance/toolkit.hpp" +#include "util/typedefs.hpp" #include #include @@ -47,7 +48,7 @@ const constexpr char *turn_type_names[] = { "invalid", "new name", "continue", "turn", "merge", "on ramp", "off ramp", "fork", "end of road", "notification", "roundabout", "roundabout", "rotary", "rotary", "roundabout turn", - "roundabout turn", "invalid", "invalid", "invalid", "invalid", + "roundabout turn", "use lane", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid", "invalid"}; @@ -56,10 +57,13 @@ const constexpr char *waypoint_type_names[] = {"invalid", "arrive", "depart"}; // Check whether to include a modifier in the result of the API inline bool isValidModifier(const guidance::StepManeuver maneuver) { - if (maneuver.waypoint_type != guidance::WaypointType::None && - maneuver.instruction.direction_modifier == DirectionModifier::UTurn) - return false; - return true; + return (maneuver.waypoint_type == guidance::WaypointType::None || + maneuver.instruction.direction_modifier != DirectionModifier::UTurn); +} + +inline bool hasValidLanes(const guidance::StepManeuver maneuver) +{ + return maneuver.instruction.lane_tupel.lanes_in_turn > 0; } std::string instructionTypeToString(const TurnType::Enum type) @@ -67,6 +71,15 @@ std::string instructionTypeToString(const TurnType::Enum type) return turn_type_names[static_cast(type)]; } +util::json::Array laneArrayFromLaneTupe(const util::guidance::LaneTupel lane_tupel) +{ + BOOST_ASSERT(lane_tupel.lanes_in_turn >= 1); + util::json::Array result; + for (LaneID i = 0; i < lane_tupel.lanes_in_turn; ++i) + result.values.push_back(lane_tupel.first_lane_from_the_right + i); + return result; +} + std::string instructionModifierToString(const DirectionModifier::Enum modifier) { return modifier_names[static_cast(modifier)]; @@ -148,6 +161,10 @@ util::json::Object makeStepManeuver(const guidance::StepManeuver &maneuver) step_maneuver.values["modifier"] = detail::instructionModifierToString(maneuver.instruction.direction_modifier); + if (detail::hasValidLanes(maneuver)) + step_maneuver.values["lanes"] = + detail::laneArrayFromLaneTupe(maneuver.instruction.lane_tupel); + step_maneuver.values["location"] = detail::coordinateToLonLat(maneuver.location); step_maneuver.values["bearing_before"] = std::round(maneuver.bearing_before); step_maneuver.values["bearing_after"] = std::round(maneuver.bearing_after); diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index 45da7ee37..373ee13e0 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -21,6 +21,8 @@ namespace TurnType = osrm::extractor::guidance::TurnType; namespace DirectionModifier = osrm::extractor::guidance::DirectionModifier; using osrm::util::guidance::angularDeviation; using osrm::util::guidance::getTurnDirection; +using osrm::util::guidance::isLeftTurn; +using osrm::util::guidance::isRightTurn; namespace osrm { @@ -31,17 +33,19 @@ namespace guidance namespace { -const constexpr double MAX_COLLAPSE_DISTANCE = 25; const constexpr std::size_t MIN_END_OF_ROAD_INTERSECTIONS = std::size_t{2}; +const constexpr double MAX_COLLAPSE_DISTANCE = 30; 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. - return previous.distance < 3 * MAX_COLLAPSE_DISTANCE && - 1 >= std::count(step.intersections.front().entry.begin(), - step.intersections.front().entry.end(), - true); + 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; } // List of types that can be collapsed, if all other restrictions pass @@ -62,9 +66,9 @@ bool isCollapsableInstruction(const TurnInstruction instruction) // a possible u-turn. bool collapsable(const RouteStep &step) { - return step.distance < MAX_COLLAPSE_DISTANCE && - isCollapsableInstruction(step.maneuver.instruction); + (step.maneuver.instruction.type == TurnType::UseLane || + isCollapsableInstruction(step.maneuver.instruction)); } bool compatible(const RouteStep &lhs, const RouteStep &rhs) { return lhs.mode == rhs.mode; } @@ -137,6 +141,8 @@ RouteStep forwardInto(RouteStep destination, const RouteStep &source) destination.geometry_begin = std::min(destination.geometry_begin, source.geometry_begin); destination.geometry_end = std::max(destination.geometry_end, source.geometry_end); + destination.maneuver.exit = source.maneuver.exit; + return destination; } @@ -171,8 +177,7 @@ void fixFinalRoundabout(std::vector &steps) // instruction though. it is not contained somewhere until now steps[propagation_index - 1] = forwardInto(std::move(steps[propagation_index - 1]), propagation_step); - propagation_step.maneuver.instruction = - TurnInstruction::NO_TURN(); // mark intermediate instructions invalid + invalidateStep(propagation_step); } } } @@ -220,10 +225,8 @@ void closeOffRoundabout(const bool on_roundabout, const std::size_t step_index) { auto &step = steps[step_index]; - step.maneuver.exit += 1; if (!on_roundabout) { - // We reached a special case that requires the addition of a special route step in the // beginning. We started in a roundabout, so to announce the exit, we move use the exit // instruction and move it right to the beginning to make sure to immediately announce the @@ -255,6 +258,7 @@ void closeOffRoundabout(const bool on_roundabout, if (steps[1].maneuver.instruction.type == TurnType::EnterRotary) steps[1].rotary_name = steps[0].name; } + step.maneuver.exit += 1; // Normal exit from the roundabout, or exit from a previously fixed roundabout. Propagate the // index back to the entering location and prepare the current silent set of instructions for @@ -265,6 +269,8 @@ void closeOffRoundabout(const bool on_roundabout, // intersections are locations passed along the way const auto exit_intersection = steps[step_index].intersections.front(); const auto exit_bearing = exit_intersection.bearings[exit_intersection.out]; + const auto destination_name = step.name; + const auto destinatino_name_id = step.name_id; if (step_index > 1) { // The very first route-step is head, so we cannot iterate past that one @@ -299,22 +305,18 @@ void closeOffRoundabout(const bool on_roundabout, ::osrm::util::guidance::getTurnDirection(angle); } - propagation_step.name = step.name; - propagation_step.name_id = step.name_id; + propagation_step.name = destination_name; + ; + propagation_step.name_id = destinatino_name_id; + invalidateStep(steps[propagation_index + 1]); break; } else { - BOOST_ASSERT(propagation_step.maneuver.instruction.type == - TurnType::StayOnRoundabout || - propagation_step.maneuver.instruction.type == TurnType::Suppressed || - propagation_step.maneuver.instruction.type == TurnType::NoTurn); - propagation_step.maneuver.instruction = - TurnInstruction::NO_TURN(); // mark intermediate instructions invalid + invalidateStep(steps[propagation_index + 1]); } } // remove exit - step.maneuver.instruction = TurnInstruction::NO_TURN(); } } @@ -364,9 +366,15 @@ void collapseTurnAt(std::vector &steps, const auto &one_back_step = steps[one_back_index]; + // This function assumes driving on the right hand side of the streat const auto bearingsAreReversed = [](const double bearing_in, const double bearing_out) { // Nearly perfectly reversed angles have a difference close to 180 degrees (straight) - return angularDeviation(bearing_in, bearing_out) > 170; + 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; }; BOOST_ASSERT(!one_back_step.intersections.empty() && !current_step.intersections.empty()); @@ -396,7 +404,6 @@ void collapseTurnAt(std::vector &steps, DirectionModifier::Straight && one_back_step.intersections.front().bearings.size() > 2) steps[step_index].maneuver.instruction.type = TurnType::Turn; - steps[two_back_index] = elongate(std::move(steps[two_back_index]), one_back_step); // If the previous instruction asked to continue, the name change will have to // be changed into a turn @@ -407,6 +414,7 @@ void collapseTurnAt(std::vector &steps, else if (one_back_step.distance <= MAX_COLLAPSE_DISTANCE && isCollapsableInstruction(current_step.maneuver.instruction)) { + // TODO check for lanes if (compatible(one_back_step, current_step)) { steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); @@ -467,7 +475,8 @@ void collapseTurnAt(std::vector &steps, // additionall collapse a name-change as well const bool continues_with_name_change = (step_index + 1 < steps.size()) && - isCollapsableInstruction(steps[step_index + 1].maneuver.instruction); + (steps[step_index + 1].maneuver.instruction.type == TurnType::UseLane || + isCollapsableInstruction(steps[step_index + 1].maneuver.instruction)); const bool u_turn_with_name_change = continues_with_name_change && steps[step_index + 1].name == steps[two_back_index].name; @@ -576,6 +585,8 @@ std::vector postProcess(std::vector steps) has_entered_roundabout = false; on_roundabout = false; } + else if (has_entered_roundabout) + steps[step_index + 1].maneuver.exit = step.maneuver.exit; } // unterminated roundabout diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index f3fbe2ef2..52ce83050 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -1,5 +1,5 @@ -#include "extractor/edge_based_graph_factory.hpp" #include "extractor/edge_based_edge.hpp" +#include "extractor/edge_based_graph_factory.hpp" #include "util/coordinate.hpp" #include "util/coordinate_calculation.hpp" #include "util/exception.hpp" @@ -11,6 +11,7 @@ #include "extractor/guidance/toolkit.hpp" #include "extractor/guidance/turn_analysis.hpp" +#include "extractor/guidance/turn_lane_handler.hpp" #include "extractor/suffix_table.hpp" #include @@ -39,12 +40,14 @@ EdgeBasedGraphFactory::EdgeBasedGraphFactory( std::shared_ptr restriction_map, const std::vector &node_info_list, ProfileProperties profile_properties, - const util::NameTable &name_table) + const util::NameTable &name_table, + const util::NameTable &turn_lanes) : m_max_edge_id(0), m_node_info_list(node_info_list), m_node_based_graph(std::move(node_based_graph)), m_restriction_map(std::move(restriction_map)), m_barrier_nodes(barrier_nodes), m_traffic_lights(traffic_lights), m_compressed_edge_container(compressed_edge_container), - profile_properties(std::move(profile_properties)), name_table(name_table) + profile_properties(std::move(profile_properties)), name_table(name_table), + turn_lanes(turn_lanes) { } @@ -339,6 +342,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( m_compressed_edge_container, name_table, street_name_suffix_table); + guidance::lanes::TurnLaneHandler turn_lane_handler( + *m_node_based_graph, turn_lanes, m_node_info_list, turn_analysis); bearing_class_by_node_based_node.resize(m_node_based_graph->GetNumberOfNodes(), std::numeric_limits::max()); @@ -353,19 +358,23 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( continue; } - ++node_based_edge_counter; - auto possible_turns = turn_analysis.getTurns(node_u, edge_from_u); - const NodeID node_v = m_node_based_graph->GetTarget(edge_from_u); + ++node_based_edge_counter; + auto intersection = turn_analysis.getIntersection(node_u, edge_from_u); + intersection = + turn_analysis.assignTurnTypes(node_u, edge_from_u, std::move(intersection)); + + intersection = + turn_lane_handler.assignTurnLanes(node_u, edge_from_u, std::move(intersection)); + const auto possible_turns = turn_analysis.transformIntersectionIntoTurns(intersection); // the entry class depends on the turn, so we have to classify the interesction for // every edge - const auto turn_classification = - classifyIntersection(node_v, - turn_analysis.getIntersection(node_u, edge_from_u), - *m_node_based_graph, - m_compressed_edge_container, - m_node_info_list); + const auto turn_classification = classifyIntersection(node_v, + intersection, + *m_node_based_graph, + m_compressed_edge_container, + m_node_info_list); const auto entry_class_id = [&](const util::guidance::EntryClass entry_class) { if (0 == entry_class_hash.count(entry_class)) diff --git a/src/extractor/extraction_containers.cpp b/src/extractor/extraction_containers.cpp index e8348da4e..76fc36c1d 100644 --- a/src/extractor/extraction_containers.cpp +++ b/src/extractor/extraction_containers.cpp @@ -51,6 +51,9 @@ ExtractionContainers::ExtractionContainers() name_lengths.push_back(0); name_lengths.push_back(0); name_lengths.push_back(0); + name_lengths.push_back(0); + name_lengths.push_back(0); + turn_lane_lengths.push_back(0); } /** @@ -66,6 +69,7 @@ ExtractionContainers::ExtractionContainers() void ExtractionContainers::PrepareData(const std::string &output_file_name, const std::string &restrictions_file_name, const std::string &name_file_name, + const std::string &turn_lane_file_name, lua_State *segment_state) { try @@ -83,7 +87,8 @@ void ExtractionContainers::PrepareData(const std::string &output_file_name, PrepareRestrictions(); WriteRestrictions(restrictions_file_name); - WriteNames(name_file_name); + WriteCharData(name_file_name,name_lengths,name_char_data); + WriteCharData(turn_lane_file_name,turn_lane_lengths,turn_lane_char_data); } catch (const std::exception &e) { @@ -91,44 +96,46 @@ void ExtractionContainers::PrepareData(const std::string &output_file_name, } } -void ExtractionContainers::WriteNames(const std::string &names_file_name) const +void ExtractionContainers::WriteCharData(const std::string &file_name, + const stxxl::vector &offsets, + const stxxl::vector &char_data) const { std::cout << "[extractor] writing street name index ... " << std::flush; - TIMER_START(write_name_index); - boost::filesystem::ofstream name_file_stream(names_file_name, std::ios::binary); + TIMER_START(write_index); + boost::filesystem::ofstream file_stream(file_name, std::ios::binary); unsigned total_length = 0; - for (const auto name_length : name_lengths) + for (const auto length : offsets) { - total_length += name_length; + total_length += length; } // builds and writes the index - util::RangeTable<> name_index_range(name_lengths); - name_file_stream << name_index_range; + util::RangeTable<> index_range(offsets); + file_stream << index_range; - name_file_stream.write((char *)&total_length, sizeof(unsigned)); + file_stream.write((char *)&total_length, sizeof(unsigned)); // write all chars consecutively char write_buffer[WRITE_BLOCK_BUFFER_SIZE]; unsigned buffer_len = 0; - for (const auto c : name_char_data) + for (const auto c : char_data) { write_buffer[buffer_len++] = c; if (buffer_len >= WRITE_BLOCK_BUFFER_SIZE) { - name_file_stream.write(write_buffer, WRITE_BLOCK_BUFFER_SIZE); + file_stream.write(write_buffer, WRITE_BLOCK_BUFFER_SIZE); buffer_len = 0; } } - name_file_stream.write(write_buffer, buffer_len); + file_stream.write(write_buffer, buffer_len); - TIMER_STOP(write_name_index); - std::cout << "ok, after " << TIMER_SEC(write_name_index) << "s" << std::endl; + TIMER_STOP(write_index); + std::cout << "ok, after " << TIMER_SEC(write_index) << "s" << std::endl; } void ExtractionContainers::PrepareNodes() diff --git a/src/extractor/extractor.cpp b/src/extractor/extractor.cpp index 1afcd2090..7ff1c8e88 100644 --- a/src/extractor/extractor.cpp +++ b/src/extractor/extractor.cpp @@ -239,6 +239,7 @@ int Extractor::run() extraction_containers.PrepareData(config.output_file_name, config.restriction_file_name, config.names_file_name, + config.turn_lane_file_name, main_context.state); WriteProfileProperties(config.profile_properties_output_path, main_context.properties); @@ -503,6 +504,7 @@ Extractor::BuildEdgeExpandedGraph(lua_State *lua_state, compressed_edge_container.SerializeInternalVector(config.geometry_output_path); util::NameTable name_table(config.names_file_name); + util::NameTable turn_lanes(config.turn_lane_file_name); EdgeBasedGraphFactory edge_based_graph_factory( node_based_graph, @@ -512,7 +514,8 @@ Extractor::BuildEdgeExpandedGraph(lua_State *lua_state, std::const_pointer_cast(restriction_map), internal_to_external_node_map, profile_properties, - name_table); + name_table, + turn_lanes); edge_based_graph_factory.Run(config.edge_output_path, lua_state, diff --git a/src/extractor/extractor_callbacks.cpp b/src/extractor/extractor_callbacks.cpp index 09ef699c4..70af37687 100644 --- a/src/extractor/extractor_callbacks.cpp +++ b/src/extractor/extractor_callbacks.cpp @@ -1,13 +1,16 @@ #include "extractor/extraction_containers.hpp" #include "extractor/extraction_node.hpp" #include "extractor/extraction_way.hpp" +#include "extractor/extractor_callbacks.hpp" #include "extractor/external_memory_node.hpp" #include "extractor/restriction.hpp" #include "util/for_each_pair.hpp" +#include "util/guidance/turn_lanes.hpp" #include "util/simple_logger.hpp" #include "extractor/extractor_callbacks.hpp" +#include #include #include @@ -29,6 +32,7 @@ ExtractorCallbacks::ExtractorCallbacks(ExtractionContainers &extraction_containe { // we reserved 0, 1, 2 for the empty case string_map[MapKey("", "")] = 0; + lane_map[""] = 0; } /** @@ -145,6 +149,30 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti // Otherwise fetches the id based on the name and returns it without insertion. const constexpr auto MAX_STRING_LENGTH = 255u; + const auto requestId = [this,MAX_STRING_LENGTH](const std::string turn_lane_string) { + if( turn_lane_string == "" ) + return INVALID_LANE_STRINGID; + const auto &lane_map_iterator = lane_map.find(turn_lane_string); + if (lane_map.end() == lane_map_iterator) + { + LaneStringID turn_lane_id = + boost::numeric_cast(external_memory.turn_lane_lengths.size()); + auto turn_lane_length = std::min(MAX_STRING_LENGTH, turn_lane_string.size()); + std::copy(turn_lane_string.c_str(), + turn_lane_string.c_str() + turn_lane_length, + std::back_inserter(external_memory.turn_lane_char_data)); + external_memory.turn_lane_lengths.push_back(turn_lane_length); + lane_map.insert(std::make_pair(turn_lane_string, turn_lane_id)); + return turn_lane_id; + } + else + { + return lane_map_iterator->second; + } + }; + + const auto turn_lane_id_forward = requestId(parsed_way.turn_lanes_forward); + const auto turn_lane_id_backward = requestId(parsed_way.turn_lanes_backward); // Get the unique identifier for the street name // Get the unique identifier for the street name and destination @@ -191,7 +219,8 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti (parsed_way.backward_speed > 0) && (TRAVEL_MODE_INACCESSIBLE != parsed_way.backward_travel_mode) && ((parsed_way.forward_speed != parsed_way.backward_speed) || - (parsed_way.forward_travel_mode != parsed_way.backward_travel_mode)); + (parsed_way.forward_travel_mode != parsed_way.backward_travel_mode) || + (turn_lane_id_forward != turn_lane_id_backward)); external_memory.used_node_id_list.reserve(external_memory.used_node_id_list.size() + input_way.nodes().size()); @@ -224,6 +253,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti parsed_way.is_startpoint, parsed_way.backward_travel_mode, false, + turn_lane_id_backward, road_classification)); }); @@ -254,6 +284,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti parsed_way.is_startpoint, parsed_way.forward_travel_mode, split_edge, + turn_lane_id_forward, road_classification)); }); if (split_edge) @@ -275,6 +306,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti parsed_way.is_startpoint, parsed_way.backward_travel_mode, true, + turn_lane_id_backward, road_classification)); }); } diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp index a30684e82..1d3606ed6 100644 --- a/src/extractor/graph_compressor.cpp +++ b/src/extractor/graph_compressor.cpp @@ -113,10 +113,8 @@ void GraphCompressor::Compress(const std::unordered_set &barrier_nodes, // traffic signals in the `traffic_lights` list, which EdgeData // doesn't have access to. const bool has_node_penalty = traffic_lights.find(node_v) != traffic_lights.end(); - if (has_node_penalty) - { + if( has_node_penalty ) continue; - } // Get distances before graph is modified const int forward_weight1 = graph.GetEdgeData(forward_e1).distance; @@ -139,6 +137,42 @@ void GraphCompressor::Compress(const std::unordered_set &barrier_nodes, graph.SetTarget(forward_e1, node_w); graph.SetTarget(reverse_e1, node_u); + /* + * Remember Lane Data for compressed parts. This handles scenarios where lane-data is + * only kept up until a traffic light. + * + * | | + * ---------------- | + * -^ | | + * ----------- | + * -v | | + * --------------- | + * | | + * + * u ------- v ---- w + * + * Since the edge is compressable, we can transfer: + * "left|right" (uv) and "" (uw) into a string with "left|right" (uw) for the compressed + * edge. + * Doing so, we might mess up the point from where the lanes are shown. It should be + * reasonable, since the announcements have to come early anyhow. So there is a + * potential danger in here, but it saves us from adding a lot of additional edges for + * turn-lanes. Without this,we would have to treat any turn-lane beginning/ending just + * like a barrier. + */ + const auto selectLaneID = [](const LaneStringID front, const LaneStringID back) { + // A lane has tags: u - (front) - v - (back) - w + // During contraction, we keep only one of the tags. Usually the one closer to the + // intersection is preferred. If its empty, however, we keep the non-empty one + if (back == INVALID_LANE_STRINGID) + return front; + return back; + }; + graph.GetEdgeData(forward_e1).lane_string_id = + selectLaneID(graph.GetEdgeData(forward_e1).lane_string_id, fwd_edge_data2.lane_string_id); + graph.GetEdgeData(reverse_e1).lane_string_id = + selectLaneID(graph.GetEdgeData(reverse_e1).lane_string_id, rev_edge_data2.lane_string_id); + // remove e2's (if bidir, otherwise only one) graph.DeleteEdge(node_v, forward_e2); graph.DeleteEdge(node_v, reverse_e2); diff --git a/src/extractor/guidance/intersection.cpp b/src/extractor/guidance/intersection.cpp index 8902ba4a0..3d48015d7 100644 --- a/src/extractor/guidance/intersection.cpp +++ b/src/extractor/guidance/intersection.cpp @@ -1,4 +1,5 @@ #include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/toolkit.hpp" namespace osrm { @@ -21,11 +22,34 @@ std::string toString(const ConnectedRoad &road) result += " angle: "; result += std::to_string(road.turn.angle); result += " instruction: "; - result += std::to_string(static_cast(road.turn.instruction.type)) + " " + - std::to_string(static_cast(road.turn.instruction.direction_modifier)); + result += + std::to_string(static_cast(road.turn.instruction.type)) + " " + + std::to_string(static_cast(road.turn.instruction.direction_modifier)) + " " + + std::to_string(static_cast(road.turn.instruction.lane_tupel.lanes_in_turn)) + + " " + std::to_string(static_cast( + road.turn.instruction.lane_tupel.first_lane_from_the_right)); return result; } +Intersection::iterator findClosestTurn(Intersection &intersection, const double angle) +{ + return std::min_element(intersection.begin(), + intersection.end(), + [angle](const ConnectedRoad &lhs, const ConnectedRoad &rhs) { + return angularDeviation(lhs.turn.angle, angle) < + angularDeviation(rhs.turn.angle, angle); + }); +} +Intersection::const_iterator findClosestTurn(const Intersection &intersection, const double angle) +{ + return std::min_element(intersection.cbegin(), + intersection.cend(), + [angle](const ConnectedRoad &lhs, const ConnectedRoad &rhs) { + return angularDeviation(lhs.turn.angle, angle) < + angularDeviation(rhs.turn.angle, angle); + }); +} + } // namespace guidance } // namespace extractor } // namespace osrm diff --git a/src/extractor/guidance/intersection_generator.cpp b/src/extractor/guidance/intersection_generator.cpp index f11da13e5..7c73c519a 100644 --- a/src/extractor/guidance/intersection_generator.cpp +++ b/src/extractor/guidance/intersection_generator.cpp @@ -1,5 +1,5 @@ -#include "extractor/guidance/intersection_generator.hpp" #include "extractor/guidance/constants.hpp" +#include "extractor/guidance/intersection_generator.hpp" #include "extractor/guidance/toolkit.hpp" #include diff --git a/src/extractor/guidance/motorway_handler.cpp b/src/extractor/guidance/motorway_handler.cpp index 4bf02fc87..b1269aa67 100644 --- a/src/extractor/guidance/motorway_handler.cpp +++ b/src/extractor/guidance/motorway_handler.cpp @@ -42,10 +42,8 @@ inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_base MotorwayHandler::MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph, const std::vector &node_info_list, const util::NameTable &name_table, - const SuffixTable &street_name_suffix_table, - const IntersectionGenerator &intersection_generator) - : IntersectionHandler(node_based_graph, node_info_list, name_table, street_name_suffix_table), - intersection_generator(intersection_generator) + const SuffixTable &street_name_suffix_table) + : IntersectionHandler(node_based_graph, node_info_list, name_table, street_name_suffix_table) { } diff --git a/src/extractor/guidance/roundabout_handler.cpp b/src/extractor/guidance/roundabout_handler.cpp index c481d4c7b..602326339 100644 --- a/src/extractor/guidance/roundabout_handler.cpp +++ b/src/extractor/guidance/roundabout_handler.cpp @@ -373,7 +373,13 @@ Intersection RoundaboutHandler::handleRoundabouts(const RoundaboutType roundabou if (1 == node_based_graph.GetDirectedOutDegree(node_v)) { // No turn possible. - turn.instruction = TurnInstruction::NO_TURN(); + if( intersection.size() == 2 ) + turn.instruction = TurnInstruction::NO_TURN(); + else + { + turn.instruction.type = TurnType::Suppressed; //make sure to report intersection + turn.instruction.direction_modifier = getTurnDirection(turn.angle); + } } else { diff --git a/src/extractor/guidance/turn_analysis.cpp b/src/extractor/guidance/turn_analysis.cpp index 0d7b02538..07024c501 100644 --- a/src/extractor/guidance/turn_analysis.cpp +++ b/src/extractor/guidance/turn_analysis.cpp @@ -48,19 +48,15 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph, compressed_edge_container, name_table, street_name_suffix_table), - motorway_handler(node_based_graph, - node_info_list, - name_table, - street_name_suffix_table, - intersection_generator), + motorway_handler(node_based_graph, node_info_list, name_table, street_name_suffix_table), turn_handler(node_based_graph, node_info_list, name_table, street_name_suffix_table) { } -std::vector TurnAnalysis::getTurns(const NodeID from_nid, const EdgeID via_eid) const +Intersection TurnAnalysis::assignTurnTypes(const NodeID from_nid, + const EdgeID via_eid, + Intersection intersection) const { - auto intersection = intersection_generator(from_nid, via_eid); - // Roundabouts are a main priority. If there is a roundabout instruction present, we process the // turn as a roundabout if (roundabout_handler.canProcess(from_nid, via_eid, intersection)) @@ -81,7 +77,6 @@ std::vector TurnAnalysis::getTurns(const NodeID from_nid, const E intersection = turn_handler(from_nid, via_eid, std::move(intersection)); } } - // Handle sliproads intersection = handleSliproads(via_eid, std::move(intersection)); @@ -94,6 +89,12 @@ std::vector TurnAnalysis::getTurns(const NodeID from_nid, const E }); } + return intersection; +} + +std::vector +TurnAnalysis::transformIntersectionIntoTurns(const Intersection &intersection) const +{ std::vector turns; for (auto road : intersection) if (road.entry_allowed) @@ -234,6 +235,8 @@ Intersection TurnAnalysis::handleSliproads(const EdgeID source_edge_id, return intersection; } +const IntersectionGenerator &TurnAnalysis::getGenerator() const { return intersection_generator; } + } // namespace guidance } // namespace extractor } // namespace osrm diff --git a/src/extractor/guidance/turn_discovery.cpp b/src/extractor/guidance/turn_discovery.cpp new file mode 100644 index 000000000..4f71ae135 --- /dev/null +++ b/src/extractor/guidance/turn_discovery.cpp @@ -0,0 +1,85 @@ +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/turn_discovery.hpp" + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +bool findPreviousIntersection(const NodeID node_v, + const EdgeID via_edge, + const Intersection intersection, + const TurnAnalysis &turn_analysis, + const util::NodeBasedDynamicGraph &node_based_graph, + // output parameters + NodeID &result_node, + EdgeID &result_via_edge, + Intersection &result_intersection) +{ + /* We need to find the intersection that is located prior to via_edge. + + * + * NODE_U -> PREVIOUS_ID -> NODE_V -> VIA_EDGE -> NODE_W:INTERSECTION + * NODE_U? <- STRAIGHTMOST <- NODE_V <- UTURN + * NODE_U? -> UTURN == PREVIOUSE_ID? -> NODE_V -> VIA_EDGE + * + * To do so, we first get the intersection atNODE and find the straightmost turn from that + * node. This will result in NODE_X. The uturn in the intersection at NODE_X should be + * PREVIOUS_ID. To verify that find, we check the intersection using our PREVIOUS_ID candidate + * to check the intersection at NODE for via_edge + */ + const constexpr double COMBINE_DISTANCE_CUTOFF = 30; + + // we check if via-edge is too short. In this case the previous turn cannot influence the turn + // at via_edge and the intersection at NODE_W + if (node_based_graph.GetEdgeData(via_edge).distance > COMBINE_DISTANCE_CUTOFF) + return false; + + // Node -> Via_Edge -> Intersection[0 == UTURN] -> reverse_of(via_edge) -> Intersection at node + // (looking at the reverse direction). + const auto node_w = node_based_graph.GetTarget(via_edge); + const auto u_turn_at_node_w = intersection[0].turn.eid; + const auto node_v_reverse_intersection = + turn_analysis.getIntersection(node_w, u_turn_at_node_w); + + // Continue along the straightmost turn. If there is no straight turn, we cannot find a valid + // previous intersection. + const auto straightmost_at_v_in_reverse = + findClosestTurn(node_v_reverse_intersection, STRAIGHT_ANGLE); + if (angularDeviation(straightmost_at_v_in_reverse->turn.angle, STRAIGHT_ANGLE) > + FUZZY_ANGLE_DIFFERENCE) + return false; + + const auto node_u = node_based_graph.GetTarget(straightmost_at_v_in_reverse->turn.eid); + const auto node_u_reverse_intersection = + turn_analysis.getIntersection(node_v, straightmost_at_v_in_reverse->turn.eid); + + // now check that the u-turn at the given intersection connects to via-edge + // The u-turn at the now found intersection should, hopefully, represent the previous edge. + result_node = node_u; + result_via_edge = node_u_reverse_intersection[0].turn.eid; + + // if the edge is not traversable, we obviously don't have a previous intersection or couldn't + // find it. + if (node_based_graph.GetEdgeData(result_via_edge).reversed) + return false; + + result_intersection = turn_analysis.getIntersection(node_u, result_via_edge); + const auto check_via_edge = findClosestTurn(result_intersection, STRAIGHT_ANGLE)->turn.eid; + if (check_via_edge != via_edge) + return false; + + result_intersection = + turn_analysis.assignTurnTypes(node_u, result_via_edge, std::move(result_intersection)); + + return true; +} + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/turn_handler.cpp b/src/extractor/guidance/turn_handler.cpp index 44b4b61de..dff2fa424 100644 --- a/src/extractor/guidance/turn_handler.cpp +++ b/src/extractor/guidance/turn_handler.cpp @@ -78,7 +78,8 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection const auto &first_data = node_based_graph.GetEdgeData(intersection[1].turn.eid); const auto &second_data = node_based_graph.GetEdgeData(intersection[2].turn.eid); BOOST_ASSERT(intersection[0].turn.angle < 0.001); - const auto isObviousOfTwo = [this](const ConnectedRoad road, const ConnectedRoad other) { + const auto isObviousOfTwo = [this, in_data](const ConnectedRoad road, + const ConnectedRoad other) { const auto first_class = node_based_graph.GetEdgeData(road.turn.eid).road_classification.road_class; @@ -104,7 +105,8 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection const bool turn_is_perfectly_straight = angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < std::numeric_limits::epsilon(); - if (turn_is_perfectly_straight) + if (turn_is_perfectly_straight && in_data.name_id != EMPTY_NAMEID && + in_data.name_id == node_based_graph.GetEdgeData(road.turn.eid).name_id) return true; const bool is_much_narrower_than_other = diff --git a/src/extractor/guidance/turn_lane_augmentation.cpp b/src/extractor/guidance/turn_lane_augmentation.cpp new file mode 100644 index 000000000..c1a82f2ee --- /dev/null +++ b/src/extractor/guidance/turn_lane_augmentation.cpp @@ -0,0 +1,304 @@ +#include "extractor/guidance/turn_lane_augmentation.hpp" +#include "util/simple_logger.hpp" + +#include +#include +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +namespace +{ + +const constexpr char *tag_by_modifier[] = {"reverse", + "sharp_right", + "right", + "slight_right", + "through", + "slight_left", + "left", + "sharp_left"}; + +std::size_t getNumberOfTurns(const Intersection &intersection) +{ + return std::count_if(intersection.begin(), intersection.end(), [](const ConnectedRoad &road) { + return road.entry_allowed; + }); +} + +LaneDataVector augmentMultiple(const std::size_t none_index, + const std::size_t connection_count, + LaneDataVector lane_data, + const Intersection &intersection) +{ + + // a none-turn is allowing multiple turns. we have to add a lane-data entry for + // every possible turn. This should, hopefully, only be the case for single lane + // entries? + + // looking at the left side first + const auto range = [&]() { + if (none_index == 0) + { + // find first connection_count - lane_data.size() valid turns + std::size_t count = 0; + for (std::size_t intersection_index = 1; intersection_index < intersection.size(); + ++intersection_index) + { + + count += static_cast(intersection[intersection_index].entry_allowed); + if (count > connection_count - lane_data.size()) + return std::make_pair(std::size_t{1}, intersection_index + 1); + } + } + else if (none_index + 1 == lane_data.size()) + { + BOOST_ASSERT(!lane_data.empty()); + // find last connection-count - last_data.size() valid turns + std::size_t count = 0; + for (std::size_t intersection_index = intersection.size() - 1; intersection_index > 0; + --intersection_index) + { + count += static_cast(intersection[intersection_index].entry_allowed); + if (count > connection_count - lane_data.size()) + return std::make_pair(intersection_index, intersection.size()); + } + } + else + { + // skip the first #index valid turns, find next connection_count - + // lane_data.size() valid ones + + std::size_t begin = 1, count = 0, intersection_index; + for (intersection_index = 1; intersection_index < intersection.size(); + ++intersection_index) + { + count += static_cast(intersection[intersection_index].entry_allowed); + // if we reach the amount of + if (count >= none_index) + { + begin = intersection_index + 1; + break; + } + } + + // reset count to find the number of necessary entries + count = 0; + for (intersection_index = begin; intersection_index < intersection.size(); + ++intersection_index) + { + count += static_cast(intersection[intersection_index].entry_allowed); + if (count > connection_count - lane_data.size()) + { + return std::make_pair(begin, intersection_index + 1); + } + } + } + // this should, theoretically, never be reached + util::SimpleLogger().Write(logWARNING) << "Failed lane assignment. Reached bad situation."; + return std::make_pair(std::size_t{0}, std::size_t{0}); + }(); + for (auto intersection_index = range.first; intersection_index < range.second; + ++intersection_index) + { + if (intersection[intersection_index].entry_allowed) + { + // FIXME this probably can be only a subset of these turns here? + lane_data.push_back({tag_by_modifier[intersection[intersection_index] + .turn.instruction.direction_modifier], + lane_data[none_index].from, + lane_data[none_index].to}); + } + } + lane_data.erase(lane_data.begin() + none_index); + return std::move(lane_data); +} + +// Merging none-tag into its neighboring fields +// This handles situations like "left | | | right". +LaneDataVector mergeNoneTag(const std::size_t none_index, LaneDataVector lane_data) +{ + + if (none_index == 0 || none_index + 1 == lane_data.size()) + { + if (none_index == 0) + { + lane_data[1].from = lane_data[0].from; + } + else + { + lane_data[none_index - 1].to = lane_data[none_index].to; + } + lane_data.erase(lane_data.begin() + none_index); + } + else if (lane_data[none_index].to - lane_data[none_index].from <= 1) + { + lane_data[none_index - 1].to = lane_data[none_index].from; + lane_data[none_index + 1].from = lane_data[none_index].to; + + lane_data.erase(lane_data.begin() + none_index); + } + return std::move(lane_data); +} + +LaneDataVector handleRenamingSituations(const std::size_t none_index, + LaneDataVector lane_data, + const Intersection &intersection) +{ + bool has_right = false; + bool has_through = false; + bool has_left = false; + for (const auto &road : intersection) + { + if (!road.entry_allowed) + continue; + + const auto modifier = road.turn.instruction.direction_modifier; + has_right |= modifier == DirectionModifier::Right; + has_right |= modifier == DirectionModifier::SlightRight; + has_right |= modifier == DirectionModifier::SharpRight; + has_through |= modifier == DirectionModifier::Straight; + has_left |= modifier == DirectionModifier::Left; + has_left |= modifier == DirectionModifier::SlightLeft; + has_left |= modifier == DirectionModifier::SharpLeft; + } + + + // find missing tag and augment neighboring, if possible + if (none_index == 0) + { + if (has_right && + (lane_data.size() == 1 || (lane_data[none_index + 1].tag != "sharp_right" && + lane_data[none_index + 1].tag != "right"))) + { + lane_data[none_index].tag = "right"; + if (lane_data.size() > 1 && lane_data[none_index + 1].tag == "through") + { + lane_data[none_index + 1].from = lane_data[none_index].from; + // turning right through a possible through lane is not possible + lane_data[none_index].to = lane_data[none_index].from; + } + } + else if (has_through && + (lane_data.size() == 1 || lane_data[none_index + 1].tag != "through")) + { + lane_data[none_index].tag = "through"; + } + } + else if (none_index + 1 == lane_data.size()) + { + if (has_left && ((lane_data[none_index - 1].tag != "sharp_left" && + lane_data[none_index - 1].tag != "left"))) + { + lane_data[none_index].tag = "left"; + if (lane_data[none_index - 1].tag == "through") + { + lane_data[none_index - 1].to = lane_data[none_index].to; + // turning left through a possible through lane is not possible + lane_data[none_index].from = lane_data[none_index].to; + } + } + else if (has_through && lane_data[none_index - 1].tag != "through") + { + lane_data[none_index].tag = "through"; + } + } + else + { + if ((lane_data[none_index + 1].tag == "left" || + lane_data[none_index + 1].tag == "slight_left" || + lane_data[none_index + 1].tag == "sharp_left") && + (lane_data[none_index - 1].tag == "right" || + lane_data[none_index - 1].tag == "slight_right" || + lane_data[none_index - 1].tag == "sharp_right")) + { + lane_data[none_index].tag = "through"; + } + } + return std::move(lane_data); +} + +} // namespace + +/* + Lanes can have the tag none. While a nice feature for visibility, it is a terrible feature + for parsing. None can be part of neighboring turns, or not. We have to look at both the + intersection and the lane data to see what turns we have to augment by the none-lanes + */ +LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data, + const Intersection &intersection) +{ + const bool needs_no_processing = + (intersection.empty() || lane_data.empty() || !hasTag("none", lane_data)); + + if (needs_no_processing) + return std::move(lane_data); + + // FIXME all this needs to consider the number of lanes at the target to ensure that we + // augment lanes correctly, if the target lane allows for more turns + // + // ----------------- + // + // ----- ---- + // -v | + // ----- | + // | | | + // + // A situation like this would allow a right turn from the through lane. + // + // ----------------- + // + // ----- -------- + // -v | + // ----- | + // | | + // + // Here, the number of lanes in the right road would not allow turns from both lanes, but + // only from the right one. + + const std::size_t connection_count = + getNumberOfTurns(intersection) - + ((intersection[0].entry_allowed && lane_data.back().tag != "reverse") ? 1 : 0); + + // TODO check for impossible turns to see whether the turn lane is at the correct place + const std::size_t none_index = std::distance(lane_data.begin(), findTag("none", lane_data)); + BOOST_ASSERT(none_index != lane_data.size()); + // we have to create multiple turns + if (connection_count > lane_data.size()) + { + lane_data = + augmentMultiple(none_index, connection_count, std::move(lane_data), intersection); + } + // we have to reduce it, assigning it to neighboring turns + else if (connection_count < lane_data.size()) + { + // a prerequisite is simple turns. Larger differences should not end up here + // an additional line at the side is only reasonable if it is targeting public + // service vehicles. Otherwise, we should not have it + BOOST_ASSERT(connection_count + 1 == lane_data.size()); + + lane_data = mergeNoneTag(none_index, std::move(lane_data)); + } + // we have to rename and possibly augment existing ones. The pure count remains the + // same. + else + { + lane_data = handleRenamingSituations(none_index, std::move(lane_data), intersection); + } + // finally make sure we are still sorted + std::sort(lane_data.begin(), lane_data.end()); + return std::move(lane_data); +} + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/turn_lane_data.cpp b/src/extractor/guidance/turn_lane_data.cpp new file mode 100644 index 000000000..3aca1f8d7 --- /dev/null +++ b/src/extractor/guidance/turn_lane_data.cpp @@ -0,0 +1,145 @@ +#include "extractor/guidance/turn_lane_data.hpp" +#include "util/guidance/turn_lanes.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +bool TurnLaneData::operator<(const TurnLaneData &other) const +{ + if (from < other.from) + return true; + if (from > other.from) + return false; + + if (to < other.to) + return true; + if (to > other.to) + return false; + + const constexpr char *tag_by_modifier[] = {"sharp_right", + "right", + "slight_right", + "through", + "slight_left", + "left", + "sharp_left", + "reverse"}; + return std::find(tag_by_modifier, tag_by_modifier + 8, this->tag) < + std::find(tag_by_modifier, tag_by_modifier + 8, other.tag); +} + +LaneDataVector laneDataFromString(std::string turn_lane_string) +{ + typedef std::unordered_map> LaneMap; + + // FIXME this is a workaround due to https://github.com/cucumber/cucumber-js/issues/417, + // need to switch statements when fixed + // const auto num_lanes = std::count(turn_lane_string.begin(), turn_lane_string.end(), '|') + 1; + // count the number of lanes + const auto num_lanes = [](const std::string &turn_lane_string) { + return boost::numeric_cast( + std::count(turn_lane_string.begin(), turn_lane_string.end(), '|') + 1 + + std::count(turn_lane_string.begin(), turn_lane_string.end(), '&')); + }(turn_lane_string); + + const auto getNextTag = [](std::string &string, const char *separators) { + auto pos = string.find_last_of(separators); + auto result = pos != std::string::npos ? string.substr(pos + 1) : string; + + string.resize(pos == std::string::npos ? 0 : pos); + return result; + }; + + const auto setLaneData = [&](LaneMap &map, std::string lane, const LaneID current_lane) { + do + { + auto identifier = getNextTag(lane, ";"); + if (identifier.empty()) + identifier = "none"; + auto map_iterator = map.find(identifier); + if (map_iterator == map.end()) + map[identifier] = std::make_pair(current_lane, current_lane); + else + { + map_iterator->second.second = current_lane; + } + } while (!lane.empty()); + }; + + // check whether a given turn lane string resulted in valid lane data + const auto hasValidOverlaps = [](const LaneDataVector &lane_data) { + // Allow an overlap of at most one. Larger overlaps would result in crossing another turn, + // which is invalid + for (std::size_t index = 1; index < lane_data.size(); ++index) + { + if (lane_data[index - 1].to > lane_data[index].from) + return false; + } + return true; + }; + + LaneMap lane_map; + LaneID lane_nr = 0; + LaneDataVector lane_data; + if (turn_lane_string.empty()) + return lane_data; + + do + { + // FIXME this is a cucumber workaround, since escaping does not work properly in + // cucumber.js (see https://github.com/cucumber/cucumber-js/issues/417). Needs to be + // changed to "|" only, when the bug is fixed + auto lane = getNextTag(turn_lane_string, "|&"); + setLaneData(lane_map, lane, lane_nr); + ++lane_nr; + } while (lane_nr < num_lanes); + + for (const auto tag : lane_map) + { + lane_data.push_back({tag.first, tag.second.first, tag.second.second}); + } + + std::sort(lane_data.begin(), lane_data.end()); + if (!hasValidOverlaps(lane_data)) + { + lane_data.clear(); + } + + return lane_data; +} + +LaneDataVector::iterator findTag(const std::string &tag, LaneDataVector &data) +{ + return std::find_if(data.begin(), data.end(), [&](const TurnLaneData &lane_data) { + return tag == lane_data.tag; + }); +} +LaneDataVector::const_iterator findTag(const std::string &tag, const LaneDataVector &data) +{ + return std::find_if(data.cbegin(), data.cend(), [&](const TurnLaneData &lane_data) { + return tag == lane_data.tag; + }); +} + +bool hasTag(const std::string &tag, const LaneDataVector &data){ + return findTag(tag,data) != data.cend(); +} + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/turn_lane_handler.cpp b/src/extractor/guidance/turn_lane_handler.cpp new file mode 100644 index 000000000..373f8b48d --- /dev/null +++ b/src/extractor/guidance/turn_lane_handler.cpp @@ -0,0 +1,505 @@ +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/turn_discovery.hpp" +#include "extractor/guidance/turn_lane_handler.hpp" +#include "extractor/guidance/turn_lane_matcher.hpp" +#include "extractor/guidance/turn_lane_augmentation.hpp" +#include "util/simple_logger.hpp" +#include "util/typedefs.hpp" + +#include + +#include +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +namespace +{ +std::size_t getNumberOfTurns(const Intersection &intersection) +{ + return std::count_if(intersection.begin(), intersection.end(), [](const ConnectedRoad &road) { + return road.entry_allowed; + }); +} +} // namespace + +TurnLaneHandler::TurnLaneHandler(const util::NodeBasedDynamicGraph &node_based_graph, + const util::NameTable &turn_lane_strings, + const std::vector &node_info_list, + const TurnAnalysis &turn_analysis) + : node_based_graph(node_based_graph), turn_lane_strings(turn_lane_strings), + node_info_list(node_info_list), turn_analysis(turn_analysis) +{ +} + +/* + Turn lanes are given in the form of strings that closely correspond to the direction modifiers + we use for our turn types. However, we still cannot simply perform a 1:1 assignment. + + This function parses the turn_lane_strings of a format that describes an intersection as: + + ---------- + A -^ + ---------- + B -> -v + ---------- + C -v + ---------- + + with a string like |left|through;right|right| and performs an assignment onto the turns: + for example: (130, turn slight right), (180, ramp straight), (320, turn sharp left) + */ +Intersection TurnLaneHandler::assignTurnLanes(const NodeID at, + const EdgeID via_edge, + Intersection intersection) const +{ + // initialize to invalid + for (auto &road : intersection) + road.turn.instruction.lane_tupel = {0, INVALID_LANEID}; + const auto &data = node_based_graph.GetEdgeData(via_edge); + const auto turn_lane_string = data.lane_string_id != INVALID_LANE_STRINGID + ? turn_lane_strings.GetNameForID(data.lane_string_id) + : ""; + // going straight, due to traffic signals, we can have uncompressed geometry + if (intersection.size() == 2 && + ((data.lane_string_id != INVALID_LANE_STRINGID && + data.lane_string_id == + node_based_graph.GetEdgeData(intersection[1].turn.eid).lane_string_id) || + angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE)) + return std::move(intersection); + + auto lane_data = laneDataFromString(turn_lane_string); + + // if we see an invalid conversion, we stop immediately + if (!turn_lane_string.empty() && lane_data.empty()) + return std::move(intersection); + + // might be reasonable to handle multiple turns, if we know of a sequence of lanes + // e.g. one direction per lane, if three lanes and right, through, left available + if (!turn_lane_string.empty() && lane_data.size() == 1 && lane_data[0].tag == "none") + return std::move(intersection); + + const std::size_t possible_entries = getNumberOfTurns(intersection); + + // merge does not justify an instruction + const bool has_merge_lane = (hasTag("merge_to_left", lane_data) || + hasTag("merge_to_right", lane_data)); + + // Dead end streets that don't have any left-tag. This can happen due to the fallbacks for + // broken data/barriers. + const bool has_non_usable_u_turn = + (intersection[0].entry_allowed && !hasTag("none", lane_data) && + !hasTag("left", lane_data) && + !hasTag("sharp_left", lane_data) && + !hasTag("reverse", lane_data) && + lane_data.size() + 1 == possible_entries); + + if (has_merge_lane || has_non_usable_u_turn) + return std::move(intersection); + + if (!lane_data.empty() && canMatchTrivially(intersection, lane_data) && + lane_data.size() != + static_cast( + lane_data.back().tag != "reverse" && intersection[0].entry_allowed ? 1 : 0) + + possible_entries && + intersection[0].entry_allowed && !hasTag("none", lane_data)) + lane_data.push_back({"reverse", lane_data.back().to, lane_data.back().to}); + + bool is_simple = isSimpleIntersection(lane_data, intersection); + + // simple intersections can be assigned directly + if (is_simple) + { + lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection); + return simpleMatchTuplesToTurns(std::move(intersection), lane_data); + } + // if the intersection is not simple but we have lane data, we check for intersections with + // middle islands. We have two cases. The first one is providing lane data on the current + // segment and we only need to consider the part of the current segment. In this case we + // partition the data and only consider the first part. + else if (!lane_data.empty()) + { + if (lane_data.size() >= possible_entries) + { + lane_data = partitionLaneData(node_based_graph.GetTarget(via_edge), + std::move(lane_data), + intersection) + .first; + + // check if we were successfull in trimming + if (lane_data.size() == possible_entries && + isSimpleIntersection(lane_data, intersection)) + { + lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection); + return simpleMatchTuplesToTurns(std::move(intersection), lane_data); + } + } + } + // The second part does not provide lane data on the current segment, but on the segment prior + // to the turn. We try to partition the data and only consider the second part. + else if (turn_lane_string.empty()) + { + // acquire the lane data of a previous segment and, if possible, use it for the current + // intersection. + return handleTurnAtPreviousIntersection(at, via_edge, std::move(intersection)); + } + + return std::move(intersection); +} + +// At segregated intersections, turn lanes will often only be specified up until the first turn. To +// actually take the turn, we need to look back to the edge we drove onto the intersection with. +Intersection TurnLaneHandler::handleTurnAtPreviousIntersection(const NodeID at, + const EdgeID via_edge, + Intersection intersection) const +{ + NodeID previous_node = SPECIAL_NODEID; + Intersection previous_intersection; + EdgeID previous_id = SPECIAL_EDGEID; + + // Get the previous lane string. We only accept strings that stem from a not-simple intersection + // and are not empty. + const auto previous_lane_string = [&]() -> std::string { + if (!findPreviousIntersection(at, + via_edge, + intersection, + turn_analysis, + node_based_graph, + previous_node, + previous_id, + previous_intersection)) + return ""; + + const auto &previous_data = node_based_graph.GetEdgeData(previous_id); + auto previous_string = previous_data.lane_string_id != INVALID_LANE_STRINGID + ? turn_lane_strings.GetNameForID(previous_data.lane_string_id) + : ""; + if (previous_string.empty()) + return ""; + + previous_intersection = turn_analysis.assignTurnTypes( + previous_node, previous_id, std::move(previous_intersection)); + + auto previous_lane_data = laneDataFromString(previous_string); + + if (isSimpleIntersection(previous_lane_data, previous_intersection)) + return ""; + return previous_string; + }(); + + // no lane string, no problems + if (previous_lane_string.empty()) + return std::move(intersection); + + auto lane_data = laneDataFromString(previous_lane_string); + + // stop on invalid lane data conversion + if (lane_data.empty()) + return std::move(intersection); + + const auto is_simple = isSimpleIntersection(lane_data, intersection); + if (is_simple) + { + lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection); + return simpleMatchTuplesToTurns(std::move(intersection), lane_data); + } + else + { + if (lane_data.size() >= getNumberOfTurns(previous_intersection) && + previous_intersection.size() != 2) + { + lane_data = partitionLaneData(node_based_graph.GetTarget(previous_id), + std::move(lane_data), + previous_intersection) + .second; + + std::sort(lane_data.begin(), lane_data.end()); + + // check if we were successfull in trimming + if (lane_data.size() == getNumberOfTurns(intersection) && + isSimpleIntersection(lane_data, intersection)) + { + lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection); + return simpleMatchTuplesToTurns(std::move(intersection), lane_data); + } + } + } + return std::move(intersection); +} + + +/* A simple intersection does not depend on the next intersection coming up. This is important + * for turn lanes, since traffic signals and/or segregated a intersection can influence the + * interpretation of turn-lanes at a given turn. + * + * Here we check for a simple intersection. A simple intersection has a long enough segment + * followin the turn, offers no straight turn, or only non-trivial turn operations. + */ +bool TurnLaneHandler::isSimpleIntersection(const LaneDataVector &lane_data, + const Intersection &intersection) const +{ + if (lane_data.empty()) + return false; + // if we are on a straight road, turn lanes are only reasonable in connection to the next + // intersection, or in case of a merge. If not all but one (straight) are merges, we don't + // consider the intersection simple + if (intersection.size() == 2) + { + return std::count_if( + lane_data.begin(), + lane_data.end(), + [](const TurnLaneData &data) { return boost::starts_with(data.tag, "merge"); }) + + std::size_t{1} >= + lane_data.size(); + } + + // in case an intersection offers far more lane data items than actual turns, some of them + // have + // to be for another intersection. A single additional item can be for an invalid bus lane. + const auto num_turns = [&]() { + auto count = getNumberOfTurns(intersection); + if (count < lane_data.size() && !intersection[0].entry_allowed && + lane_data.back().tag == "reverse") + return count + 1; + return count; + }(); + + // more than two additional lane data entries -> lanes target a different intersection + if (num_turns + std::size_t{2} <= lane_data.size()) + { + return false; + } + + // single additional lane data entry is alright, if it is none at the side. This usually + // refers to a bus-lane + if (num_turns + std::size_t{1} == lane_data.size() && lane_data.front().tag != "none" && + lane_data.back().tag != "none") + { + return false; + } + + // more turns than lane data + if (num_turns > lane_data.size() && + lane_data.end() == + std::find_if(lane_data.begin(), lane_data.end(), [](const TurnLaneData &data) { + return data.tag == "none"; + })) + { + return false; + } + + if (num_turns > lane_data.size() && intersection[0].entry_allowed && + !( hasTag("reverse", lane_data) || + (lane_data.back().tag != "left" && lane_data.back().tag != "sharp_left"))) + { + return false; + } + + // check if we can find a valid 1:1 mapping in a straightforward manner + bool all_simple = true; + bool has_none = false; + std::unordered_set matched_indices; + for (const auto &data : lane_data) + { + if (data.tag == "none") + { + has_none = true; + continue; + } + + const auto best_match = [&]() { + if (data.tag != "reverse" || lane_data.size() == 1) + return findBestMatch(data.tag, intersection); + + // lane_data.size() > 1 + if (lane_data.back().tag == "reverse") + return findBestMatchForReverse(lane_data[lane_data.size() - 2].tag, intersection); + + BOOST_ASSERT(lane_data.front().tag == "reverse"); + return findBestMatchForReverse(lane_data[1].tag, intersection); + }(); + std::size_t match_index = std::distance(intersection.begin(), best_match); + all_simple &= (matched_indices.count(match_index) == 0); + matched_indices.insert(match_index); + // in case of u-turns, we might need to activate them first + all_simple &= (best_match->entry_allowed || match_index == 0); + all_simple &= isValidMatch(data.tag, best_match->turn.instruction); + } + + // either all indices are matched, or we have a single none-value + if (all_simple && (matched_indices.size() == lane_data.size() || + (matched_indices.size() + 1 == lane_data.size() && has_none))) + return true; + + // better save than sorry + return false; +} + +std::pair TurnLaneHandler::partitionLaneData( + const NodeID at, LaneDataVector turn_lane_data, const Intersection &intersection) const +{ + BOOST_ASSERT(turn_lane_data.size() >= getNumberOfTurns(intersection)); + /* + * A Segregated intersection can provide turn lanes for turns that are not yet possible. + * The straightforward example would be coming up to the following situation: + * (1) (2) + * | A | | A | + * | | | | ^ | + * | v | | | | + * ------- ----------- ------ + * B ->-^ B + * ------- ----------- ------ + * B ->-v B + * ------- ----------- ------ + * | A | | A | + * + * Traveling on road B, we have to pass A at (1) to turn left onto A at (2). The turn + * lane itself may only be specified prior to (1) and/or could be repeated between (1) + * and (2). To make sure to announce the lane correctly, we need to treat the (in this + * case left) turn lane as if it were to continue straight onto the intersection and + * look back between (1) and (2) to make sure we find the correct lane for the left-turn. + * + * Intersections like these have two parts. Turns that can be made at the first intersection and + * turns that have to be made at the second. The partitioning returns the lane data split into + * two parts, one for the first and one for the second intersection. + */ + + // Try and maitch lanes to available turns. For Turns that are not directly matchable, check + // whether we can match them at the upcoming intersection. + + const auto straightmost = findClosestTurn(intersection, STRAIGHT_ANGLE); + + BOOST_ASSERT(straightmost < intersection.cend()); + + // we need to be able to enter the straightmost turn + if (!straightmost->entry_allowed) + return {turn_lane_data, {}}; + + std::vector matched_at_first(turn_lane_data.size(), false); + std::vector matched_at_second(turn_lane_data.size(), false); + + // find out about the next intersection. To check for valid matches, we also need the turn types + auto next_intersection = turn_analysis.getIntersection(at, straightmost->turn.eid); + next_intersection = + turn_analysis.assignTurnTypes(at, straightmost->turn.eid, std::move(next_intersection)); + + // check where we can match turn lanes + std::size_t straightmost_tag_index = turn_lane_data.size(); + for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane) + { + if (turn_lane_data[lane].tag == "none" || turn_lane_data[lane].tag == "reverse") + continue; + + const auto best_match = findBestMatch(turn_lane_data[lane].tag, intersection); + if (isValidMatch(turn_lane_data[lane].tag, best_match->turn.instruction)) + { + matched_at_first[lane] = true; + + if (straightmost == best_match) + straightmost_tag_index = lane; + } + + const auto best_match_at_next_intersection = + findBestMatch(turn_lane_data[lane].tag, next_intersection); + if (isValidMatch(turn_lane_data[lane].tag, + best_match_at_next_intersection->turn.instruction)) + matched_at_second[lane] = true; + + // we need to match all items to either the current or the next intersection + if (!(matched_at_first[lane] || matched_at_second[lane])) + return {turn_lane_data, {}}; + } + + std::size_t none_index = std::distance(turn_lane_data.begin(),findTag("none", turn_lane_data)); + + // if the turn lanes are pull forward, we might have to add an additional straight tag + // did we find something that matches against the straightmost road? + if (straightmost_tag_index == turn_lane_data.size()) + { + if (none_index != turn_lane_data.size()) + straightmost_tag_index = none_index; + } + + // TODO handle reverse + + // handle none values + if (none_index != turn_lane_data.size()) + { + if (static_cast( + std::count(matched_at_first.begin(), matched_at_first.end(), true)) <= + getNumberOfTurns(intersection)) + matched_at_first[none_index] = true; + + if (static_cast( + std::count(matched_at_second.begin(), matched_at_second.end(), true)) <= + getNumberOfTurns(next_intersection)) + matched_at_second[none_index] = true; + } + + const auto augmentEntry = [&](TurnLaneData &data) { + for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane) + if (matched_at_second[lane]) + { + data.from = std::min(turn_lane_data[lane].from, data.from); + data.to = std::max(turn_lane_data[lane].to, data.to); + } + + }; + + LaneDataVector first, second; + for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane) + { + + if (matched_at_second[lane]) + second.push_back(turn_lane_data[lane]); + + // augment straightmost at this intersection to match all turns that happen at the next + if (lane == straightmost_tag_index) + { + augmentEntry(turn_lane_data[straightmost_tag_index]); + } + + if (matched_at_first[lane]) + first.push_back(turn_lane_data[lane]); + } + + if (straightmost_tag_index == turn_lane_data.size() && + static_cast( + std::count(matched_at_second.begin(), matched_at_second.end(), true)) == + getNumberOfTurns(next_intersection)) + { + TurnLaneData data = {"through", 255, 0}; + augmentEntry(data); + first.push_back(data); + std::sort(first.begin(), first.end()); + } + + // TODO augment straightmost turn + return {std::move(first), std::move(second)}; +} + +Intersection TurnLaneHandler::simpleMatchTuplesToTurns(Intersection intersection, + const LaneDataVector &lane_data) const +{ + if (lane_data.empty() || !canMatchTrivially(intersection, lane_data)) + return std::move(intersection); + + BOOST_ASSERT(!hasTag("none", lane_data)); + BOOST_ASSERT(std::count_if(lane_data.begin(), lane_data.end(), [](const TurnLaneData &data) { + return boost::starts_with(data.tag, "merge"); + }) == 0); + + return triviallyMatchLanesToTurns(std::move(intersection), lane_data, node_based_graph); +} + +} // namespace lanes +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/turn_lane_matcher.cpp b/src/extractor/guidance/turn_lane_matcher.cpp new file mode 100644 index 000000000..27ac6a7ff --- /dev/null +++ b/src/extractor/guidance/turn_lane_matcher.cpp @@ -0,0 +1,221 @@ +#include "extractor/guidance/toolkit.hpp" +#include "extractor/guidance/turn_lane_matcher.hpp" +#include "util/guidance/toolkit.hpp" + +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ +namespace lanes +{ + +// Translate Turn Tags into a Matching Direction Modifier +DirectionModifier::Enum getMatchingModifier(const std::string &tag) +{ + const constexpr char *tag_by_modifier[] = {"reverse", + "sharp_right", + "right", + "slight_right", + "through", + "slight_left", + "left", + "sharp_left", + "merge_to_left", + "merge_to_right"}; + const auto index = + std::distance(tag_by_modifier, std::find(tag_by_modifier, tag_by_modifier + 10, tag)); + + BOOST_ASSERT(index <= 10); + + const constexpr DirectionModifier::Enum modifiers[11] = { + DirectionModifier::UTurn, + DirectionModifier::SharpRight, + DirectionModifier::Right, + DirectionModifier::SlightRight, + DirectionModifier::Straight, + DirectionModifier::SlightLeft, + DirectionModifier::Left, + DirectionModifier::SharpLeft, + DirectionModifier::Straight, + DirectionModifier::Straight, + DirectionModifier::UTurn}; // fallback for invalid tags + + return modifiers[index]; +} + +// check whether a match of a given tag and a turn instruction can be seen as valid +bool isValidMatch(const std::string &tag, const TurnInstruction instruction) +{ + using util::guidance::hasLeftModifier; + using util::guidance::hasRightModifier; + const auto isMirroredModifier = [](const TurnInstruction instruction) { + return instruction.type == TurnType::Merge; + }; + + if (tag == "reverse") + { + return hasLeftModifier(instruction) || + instruction.direction_modifier == DirectionModifier::UTurn; + } + else if (tag == "sharp_right" || tag == "right" || tag == "slight_right") + { + if (isMirroredModifier(instruction)) + return hasLeftModifier(instruction); + else + // needs to be adjusted for left side driving + return leavesRoundabout(instruction) || hasRightModifier(instruction); + } + else if (tag == "through") + { + return instruction.direction_modifier == DirectionModifier::Straight || + instruction.type == TurnType::Suppressed || instruction.type == TurnType::NewName || + instruction.type == TurnType::StayOnRoundabout || entersRoundabout(instruction) || + (instruction.type == + TurnType::Fork && // Forks can be experienced, even for straight segments + (instruction.direction_modifier == DirectionModifier::SlightLeft || + instruction.direction_modifier == DirectionModifier::SlightRight)) || + (instruction.type == + TurnType::Continue && // Forks can be experienced, even for straight segments + (instruction.direction_modifier == DirectionModifier::SlightLeft || + instruction.direction_modifier == DirectionModifier::SlightRight)) || + instruction.type == TurnType::UseLane; + } + else if (tag == "slight_left" || tag == "left" || tag == "sharp_left") + { + if (isMirroredModifier(instruction)) + return hasRightModifier(instruction); + else + { + // Needs to be fixed for left side driving + return (instruction.type == TurnType::StayOnRoundabout) || hasLeftModifier(instruction); + } + } + return false; +} + +typename Intersection::const_iterator findBestMatch(const std::string &tag, + const Intersection &intersection) +{ + const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315}; + const auto idealized_angle = idealized_turn_angles[getMatchingModifier(tag)]; + return std::min_element( + intersection.begin(), + intersection.end(), + [idealized_angle, &tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) { + // prefer valid matches + if (isValidMatch(tag, lhs.turn.instruction) != isValidMatch(tag, rhs.turn.instruction)) + return isValidMatch(tag, lhs.turn.instruction); + // if the entry allowed flags don't match, we select the one with + // entry allowed set to true + if (lhs.entry_allowed != rhs.entry_allowed) + return lhs.entry_allowed; + + return angularDeviation(idealized_angle, lhs.turn.angle) < + angularDeviation(idealized_angle, rhs.turn.angle); + }); +} + +typename Intersection::const_iterator findBestMatchForReverse(const std::string &leftmost_tag, + const Intersection &intersection) +{ + const auto leftmost_itr = findBestMatch(leftmost_tag, intersection); + if (leftmost_itr + 1 == intersection.cend()) + return intersection.begin(); + + const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315}; + const std::string tag = "reverse"; + const auto idealized_angle = idealized_turn_angles[getMatchingModifier(tag)]; + return std::min_element( + intersection.begin() + std::distance(intersection.begin(), leftmost_itr), + intersection.end(), + [idealized_angle, &tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) { + // prefer valid matches + if (isValidMatch(tag, lhs.turn.instruction) != isValidMatch(tag, rhs.turn.instruction)) + return isValidMatch(tag, lhs.turn.instruction); + // if the entry allowed flags don't match, we select the one with + // entry allowed set to true + if (lhs.entry_allowed != rhs.entry_allowed) + return lhs.entry_allowed; + + return angularDeviation(idealized_angle, lhs.turn.angle) < + angularDeviation(idealized_angle, rhs.turn.angle); + }); +} + +bool canMatchTrivially(const Intersection &intersection, const LaneDataVector &lane_data) +{ + std::size_t road_index = 1, lane = 0; + for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index) + { + if (intersection[road_index].entry_allowed) + { + BOOST_ASSERT(lane_data[lane].from != INVALID_LANEID); + if (!isValidMatch(lane_data[lane].tag, intersection[road_index].turn.instruction)) + return false; + + if (findBestMatch(lane_data[lane].tag, intersection) != + intersection.begin() + road_index) + return false; + ++lane; + } + } + return lane == lane_data.size() || + (lane + 1 == lane_data.size() && lane_data.back().tag == "reverse"); +} + +Intersection triviallyMatchLanesToTurns(Intersection intersection, + const LaneDataVector &lane_data, + const util::NodeBasedDynamicGraph &node_based_graph) +{ + std::size_t road_index = 1, lane = 0; + for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index) + { + if (intersection[road_index].entry_allowed) + { + BOOST_ASSERT(lane_data[lane].from != INVALID_LANEID); + BOOST_ASSERT( + isValidMatch(lane_data[lane].tag, intersection[road_index].turn.instruction)); + BOOST_ASSERT(findBestMatch(lane_data[lane].tag, intersection) == + intersection.begin() + road_index); + + if (TurnType::Suppressed == intersection[road_index].turn.instruction.type) + intersection[road_index].turn.instruction.type = TurnType::UseLane; + + intersection[road_index].turn.instruction.lane_tupel = { + LaneID(lane_data[lane].to - lane_data[lane].from + 1), lane_data[lane].from}; + ++lane; + } + } + + // handle reverse tag, if present + if (lane + 1 == lane_data.size() && lane_data.back().tag == "reverse") + { + std::size_t u_turn = 0; + if (node_based_graph.GetEdgeData(intersection[0].turn.eid).reversed) + { + if (intersection.back().entry_allowed || + intersection.back().turn.instruction.direction_modifier != + DirectionModifier::SharpLeft) + { + // cannot match u-turn in a valid way + return std::move(intersection); + } + u_turn = intersection.size() - 1; + } + intersection[u_turn].entry_allowed = true; + intersection[u_turn].turn.instruction.type = TurnType::Turn; + intersection[u_turn].turn.instruction.direction_modifier = DirectionModifier::UTurn; + intersection[u_turn].turn.instruction.lane_tupel = { + LaneID(lane_data.back().to - lane_data.back().from + 1), lane_data.back().from}; + } + return std::move(intersection); +} + +} // namespace lane_matching +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/scripting_environment.cpp b/src/extractor/scripting_environment.cpp index cfdbb5745..899537e44 100644 --- a/src/extractor/scripting_environment.cpp +++ b/src/extractor/scripting_environment.cpp @@ -77,6 +77,7 @@ void ScriptingEnvironment::InitContext(ScriptingEnvironment::Context &context) luabind::module(context.state) [luabind::def("durationIsValid", durationIsValid), luabind::def("parseDuration", parseDuration), + luabind::def("trimLaneString", trimLaneString), luabind::class_("mode").enum_( "enums")[luabind::value("inaccessible", TRAVEL_MODE_INACCESSIBLE), luabind::value("driving", TRAVEL_MODE_DRIVING), @@ -141,6 +142,8 @@ void ScriptingEnvironment::InitContext(ScriptingEnvironment::Context &context) .def_readwrite("is_access_restricted", &ExtractionWay::is_access_restricted) .def_readwrite("is_startpoint", &ExtractionWay::is_startpoint) .def_readwrite("duration", &ExtractionWay::duration) + .def_readwrite("turn_lanes_forward", &ExtractionWay::turn_lanes_forward) + .def_readwrite("turn_lanes_backward", &ExtractionWay::turn_lanes_backward) .property( "forward_mode", &ExtractionWay::get_forward_mode, &ExtractionWay::set_forward_mode) .property("backward_mode", diff --git a/src/util/guidance/turn_lanes.cpp b/src/util/guidance/turn_lanes.cpp new file mode 100644 index 000000000..a9859ee08 --- /dev/null +++ b/src/util/guidance/turn_lanes.cpp @@ -0,0 +1,47 @@ +#include "util/guidance/turn_lanes.hpp" + +#include +#include + +#include + +namespace osrm +{ +namespace util +{ +namespace guidance +{ +LaneTupel::LaneTupel() + : lanes_in_turn(0), first_lane_from_the_right(INVALID_LANEID) +{ + // basic constructor, set everything to zero +} + +LaneTupel::LaneTupel(const LaneID lanes_in_turn, const LaneID first_lane_from_the_right) + : lanes_in_turn(lanes_in_turn), first_lane_from_the_right(first_lane_from_the_right) +{ +} + +// comparation based on interpretation as unsigned 32bit integer +bool LaneTupel::operator==(const LaneTupel other) const +{ + static_assert(sizeof(LaneTupel) == sizeof(std::uint16_t), + "Comparation requires LaneTupel to be the of size 16Bit"); + return *reinterpret_cast(this) == + *reinterpret_cast(&other); +} + +bool LaneTupel::operator!=(const LaneTupel other) const { return !(*this == other); } + +// comparation based on interpretation as unsigned 32bit integer +bool LaneTupel::operator<(const LaneTupel other) const +{ + static_assert(sizeof(LaneTupel) == sizeof(std::uint16_t), + "Comparation requires LaneTupel to be the of size 16Bit"); + return *reinterpret_cast(this) < + *reinterpret_cast(&other); +} + +} // namespace guidance +} // namespace util +} // namespace osrm diff --git a/src/util/name_table.cpp b/src/util/name_table.cpp index 1a2e4077b..0c3d942ad 100644 --- a/src/util/name_table.cpp +++ b/src/util/name_table.cpp @@ -36,11 +36,11 @@ NameTable::NameTable(const std::string &filename) } else { - util::SimpleLogger().Write(logWARNING) << "list of street names is empty"; + util::SimpleLogger().Write(logINFO) << "list of street names is empty in construction of name table from: \"" << filename << "\""; } if (!name_stream) throw exception("Failed to read " + std::to_string(number_of_chars) + - " characters from file."); + " characters from " + filename); } std::string NameTable::GetNameForID(const unsigned name_id) const diff --git a/taginfo.json b/taginfo.json index 8d3df9d38..f83dc0d52 100644 --- a/taginfo.json +++ b/taginfo.json @@ -200,6 +200,36 @@ "object_types": [ "way" ], "description": "Name of road for navigation instructions." }, + { + "key": "turn:lanes", + "object_types": [ "way" ], + "description": "Turn Lanes for lane guidance." + }, + { + "key": "turn:lanes:forward", + "object_types": [ "way" ], + "description": "Turn Lanes for lane guidance." + }, + { + "key": "turn:lanes:backward", + "object_types": [ "way" ], + "description": "Turn Lanes for lane guidance." + }, + { + "key": "lanes:psv", + "object_types": [ "way" ], + "description": "Turn Lanes for lane guidance." + }, + { + "key": "lanes:psv:forward", + "object_types": [ "way" ], + "description": "Turn Lanes for lane guidance." + }, + { + "key": "lanes:psv:backward", + "object_types": [ "way" ], + "description": "Turn Lanes for lane guidance." + }, { "key": "ref", "object_types": [ "way" ], diff --git a/unit_tests/extractor/graph_compressor.cpp b/unit_tests/extractor/graph_compressor.cpp index 98dd33f6c..b7d27e549 100644 --- a/unit_tests/extractor/graph_compressor.cpp +++ b/unit_tests/extractor/graph_compressor.cpp @@ -30,14 +30,14 @@ BOOST_AUTO_TEST_CASE(long_road_test) std::vector edges = { // src, tgt, dist, edge_id, name_id, access_restricted, fwd, bkwd, roundabout, travel_mode - {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {2, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {3, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {3, 4, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {4, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}}; + {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {2, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {3, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {3, 4, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {4, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}}; BOOST_ASSERT(edges[0].data.IsCompatibleTo(edges[2].data)); BOOST_ASSERT(edges[2].data.IsCompatibleTo(edges[4].data)); @@ -69,18 +69,18 @@ BOOST_AUTO_TEST_CASE(loop_test) std::vector edges = { // src, tgt, dist, edge_id, name_id, access_restricted, fwd, bkwd, roundabout, travel_mode - {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {0, 5, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {2, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {3, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {3, 4, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {4, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {4, 5, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {5, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {5, 4, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, + {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {0, 5, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {2, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {3, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {3, 4, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {4, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {4, 5, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {5, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {5, 4, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, }; BOOST_ASSERT(edges.size() == 12); @@ -124,12 +124,12 @@ BOOST_AUTO_TEST_CASE(t_intersection) std::vector edges = { // src, tgt, dist, edge_id, name_id, access_restricted, fwd, bkwd, roundabout, travel_mode - {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {3, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, + {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 3, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {3, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, }; BOOST_ASSERT(edges[0].data.IsCompatibleTo(edges[1].data)); @@ -160,10 +160,10 @@ BOOST_AUTO_TEST_CASE(street_name_changes) std::vector edges = { // src, tgt, dist, edge_id, name_id, access_restricted, fwd, bkwd, roundabout, travel_mode - {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 2, 1, SPECIAL_EDGEID, 1, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {2, 1, 1, SPECIAL_EDGEID, 1, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, + {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 0, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 2, 1, SPECIAL_EDGEID, 1, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {2, 1, 1, SPECIAL_EDGEID, 1, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, }; BOOST_ASSERT(edges[0].data.IsCompatibleTo(edges[1].data)); @@ -190,10 +190,10 @@ BOOST_AUTO_TEST_CASE(direction_changes) std::vector edges = { // src, tgt, dist, edge_id, name_id, access_restricted, fwd, bkwd, roundabout, travel_mode - {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 0, 1, SPECIAL_EDGEID, 0, false, true, false, true, TRAVEL_MODE_INACCESSIBLE}, - {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, - {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE}, + {0, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 0, 1, SPECIAL_EDGEID, 0, false, true, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {1, 2, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, + {2, 1, 1, SPECIAL_EDGEID, 0, false, false, false, true, TRAVEL_MODE_INACCESSIBLE,INVALID_LANE_STRINGID}, }; Graph graph(5, edges);