diff --git a/CHANGELOG.md b/CHANGELOG.md index d05891d88..5dccbf516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ - Properly handle destinations on `oneway=-1` roads - Guidance - Notifications are now exposed more prominently, announcing turns onto a ferry/pushing your bike more prominently + - Improved turn angle calculation, detecting offsets due to lanes / minor variations due to inaccuracies + - Corrected the bearings returned for intermediate steps - requires reprocessing + - Improved turn locations for collapsed turns - Trip Plugin - changed internal behaviour to prefer the smallest lexicographic result over the largest one - Bugfixes diff --git a/features/guidance/anticipate-lanes.feature b/features/guidance/anticipate-lanes.feature index d6af4766d..02045de5e 100644 --- a/features/guidance/anticipate-lanes.feature +++ b/features/guidance/anticipate-lanes.feature @@ -414,21 +414,21 @@ Feature: Turn Lane Guidance """ And the ways - | nodes | turn:lanes:forward | name | - | ab | through\|right\|right\|right | top | - | be | | top | - | bq | | off | - | ef | left\|through\|through\|through\|through\|right | main | - | fg | left\|left\|right\|right | main | - | fs | | off | - | ft | | off | - | gh | | top | - | hi | | top | - | cd | left\|left\|left\|through | bot | - | de | | bot | - | dr | | off | - | gj | | bot | - | jk | | bot | + | nodes | turn:lanes:forward | name | highway | oneway | + | ab | through\|right\|right\|right | top | primary | yes | + | be | | top | primary | yes | + | bq | | off | primary | yes | + | ef | left\|through\|through\|through\|through\|right | main | primary | yes | + | fg | left\|left\|right\|right | main | primary | yes | + | fs | | off | primary | yes | + | ft | | off | primary | yes | + | gh | | top | primary | yes | + | hi | | top | primary | yes | + | cd | left\|left\|left\|through | bot | primary | yes | + | de | | bot | primary | yes | + | dr | | off | primary | yes | + | gj | | bot | primary | yes | + | jk | | bot | primary | yes | When I route I should get | waypoints | route | turns | lanes | diff --git a/features/guidance/collapse-detail.feature b/features/guidance/collapse-detail.feature index 49728a109..49417c7ea 100644 --- a/features/guidance/collapse-detail.feature +++ b/features/guidance/collapse-detail.feature @@ -19,8 +19,8 @@ Feature: Collapse | nodes | highway | name | oneway | | abc | primary | road | yes | | defg | primary | road | yes | - | fb | primary_link | | | - | be | primary_link | | | + | fb | primary_link | | yes | + | be | primary_link | | yes | When I route I should get | waypoints | route | turns | diff --git a/features/guidance/collapse.feature b/features/guidance/collapse.feature index 043240a0e..eb8367f29 100644 --- a/features/guidance/collapse.feature +++ b/features/guidance/collapse.feature @@ -888,18 +888,18 @@ Feature: Collapse And the ways | nodes | highway | route | name | oneway | - | bf | primary | | road | yes | + | abf | primary | | road | yes | | hcd | primary | | road | yes | | bc | primary | | | yes | | di | service | | serv | yes | | ed | | ferry | ferry | | - | gab | | ferry | ferry | | + | ga | | ferry | ferry | | | kg | primary | | on | yes | | ej | primary | | off | yes | When I route I should get - | waypoints | route | turns | - | k,j | on,ferry,,ferry,off,off | depart,notification straight,continue uturn,turn straight,notification straight,arrive | + | waypoints | route | turns | + | k,j | on,ferry,road,road,ferry,off,off | depart,notification straight,notification straight,continue uturn,turn straight,notification straight,arrive | # http://www.openstreetmap.org/#map=19/37.78090/-122.41251 Scenario: U-Turn onto unnamed-road @@ -922,5 +922,84 @@ Feature: Collapse | ef | secondary | | down | yes | When I route I should get - | waypoints | route | turns | - | a,1 | up,turn,down, | depart,turn right,turn right,arrive | + | waypoints | route | turns | + | a,1 | up,turn,, | depart,turn right,turn sharp right,arrive | + + #http://www.openstreetmap.org/#map=19/52.48778/13.30024 + Scenario: Hohenzollerdammbrücke + Given the node map + """ + q s + p o + .. . . + . . . . + j - i - - - h - - - g - f + > k < > l < + a - b - - - c - - - d - e + . . . . + .. .. + m n + t r + """ + + And the ways + | nodes | highway | name | oneway | + | ab | secondary | hohe | yes | + | bc | secondary | hohebruecke | yes | + | cd | secondary | hohebruecke | yes | + | bk | secondary | hohebruecke | yes | + | kh | secondary | hohebruecke | yes | + | ki | secondary | hohebruecke | yes | + | ck | secondary | hohebruecke | yes | + | de | secondary | hohe | yes | + | fg | secondary | hohe | yes | + | gh | secondary | hohebruecke | yes | + | hi | secondary | hohebruecke | yes | + | gl | secondary | hohebruecke | yes | + | lc | secondary | hohebruecke | yes | + | hl | secondary | hohebruecke | yes | + | ld | secondary | hohebruecke | yes | + | ij | secondary | hohe | yes | + | bm | motorway_link | a100 | yes | + | cm | motorway_link | a100 | yes | + | nc | motorway_link | a100 | yes | + | nd | motorway_link | a100 | yes | + | go | motorway_link | a100 | yes | + | ho | motorway_link | a100 | yes | + | ph | motorway_link | a100 | yes | + | pi | motorway_link | a100 | yes | + | qp | motorway_link | a100 | yes | + | mt | motorway_link | a100 | yes | + | rn | motorway_link | a100 | yes | + | os | motorway_link | a100 | yes | + + And the relations + | type | way:from | way:to | node:via | restriction | + | restriction | ck | kh | k | no_right_turn | + | restriction | bk | ki | k | no_left_turn | + | restriction | hl | lc | l | no_right_turn | + | restriction | gl | ld | l | no_left_turn | + | restriction | bc | cm | c | no_right_turn | + | restriction | bc | ck | c | no_left_turn | + | restriction | nc | cm | c | no_left_turn | + | restriction | nc | cd | c | no_right_turn | + | restriction | lc | ck | c | no_left_turn | + | restriction | lc | cd | c | no_right_turn | + | restriction | gh | ho | h | no_right_turn | + | restriction | gh | hl | h | no_left_turn | + | restriction | kh | hi | h | no_left_turn | + | restriction | kh | hl | h | no_right_turn | + | restriction | ph | ho | h | no_left_turn | + | restriction | ph | hi | h | no_right_turn | + + When I route I should get + | waypoints | route | turns | + | a,e | hohe,hohe | depart,arrive | + | a,s | hohe,a100,a100 | depart,on ramp left,arrive | + | a,t | hohe,a100,a100 | depart,on ramp right,arrive | + | a,j | | | + | f,j | hohe,hohe | depart,arrive | + | a,t | hohe,a100,a100 | depart,on ramp right,arrive | + | f,e | | | + | q,j | a100,hohe,hohe | depart,turn right,arrive | + | q,e | a100,a100,hohe | depart,continue left,arrive | diff --git a/features/guidance/continue.feature b/features/guidance/continue.feature index bd6ce8081..235707631 100644 --- a/features/guidance/continue.feature +++ b/features/guidance/continue.feature @@ -134,4 +134,5 @@ Feature: Continue Instructions | a,c | abcdefb,abcdefb,abcdefb | depart,continue right,arrive | | a,f | abcdefb,abcdefb,abcdefb | depart,continue left,arrive | | a,d | abcdefb,abcdefb,abcdefb | depart,continue right,arrive | - | a,e | abcdefb,abcdefb,abcdefb | depart,continue left,arrive | + # continuing right here, since the turn to the left is more expensive + | a,e | abcdefb,abcdefb,abcdefb | depart,continue right,arrive | diff --git a/features/guidance/dedicated-turn-roads.feature b/features/guidance/dedicated-turn-roads.feature index 50969b331..afbf7cf60 100644 --- a/features/guidance/dedicated-turn-roads.feature +++ b/features/guidance/dedicated-turn-roads.feature @@ -43,6 +43,10 @@ Feature: Slipways and Dedicated Turn Lanes + + + + f @@ -59,6 +63,31 @@ Feature: Slipways and Dedicated Turn Lanes | waypoints | route | turns | | a,g | first,,second,second | depart,off ramp slight right,turn straight,arrive | + Scenario: Turn Instead of Ramp + Given the node map + """ + e + a b c d + h + + + + f + + + g + """ + + And the ways + | nodes | highway | name | + | abcd | motorway | first | + | bhf | motorway_link | | + | efg | primary | second | + + When I route I should get + | waypoints | route | turns | + | a,g | first,,second,second | depart,off ramp slight right,turn straight,arrive | + Scenario: Inner city expressway with on road Given the node map """ @@ -208,11 +237,11 @@ Feature: Slipways and Dedicated Turn Lanes | jcghf | primary | Brauerstrasse | yes | When I route I should get - | waypoints | route | turns | - | a,i | Ebertstrasse,Ebertstrasse | depart,arrive | - | a,l | Ebertstrasse,Ebertstrasse | depart,arrive | - | a,f | Ebertstrasse,Brauerstrasse,Brauerstrasse | depart,turn right,arrive | - | a,1 | Ebertstrasse,, | depart,turn slight right,arrive | + | waypoints | route | turns | + | a,i | Ebertstrasse,Ebertstrasse | depart,arrive | + | a,l | Ebertstrasse,Ebertstrasse | depart,arrive | + | a,f | Ebertstrasse,Brauerstrasse,Brauerstrasse | depart,turn right,arrive | + | a,1 | Ebertstrasse,, | depart,turn right,arrive | #2839 Scenario: Self-Loop diff --git a/features/guidance/roundabout-turn.feature b/features/guidance/roundabout-turn.feature index e846daab1..13c1e44bd 100644 --- a/features/guidance/roundabout-turn.feature +++ b/features/guidance/roundabout-turn.feature @@ -435,9 +435,9 @@ Feature: Basic Roundabout When I route I should get | waypoints | route | turns | bearing | - | a,d | ab,cd,cd | depart,roundabout turn left exit-3,arrive | 0->180,180->224,90->0 | - | a,f | ab,ef,ef | depart,roundabout turn straight exit-2,arrive | 0->180,180->224,180->0 | - | a,h | ab,gh,gh | depart,roundabout turn right exit-1,arrive | 0->180,180->224,270->0 | + | a,d | ab,cd,cd | depart,roundabout turn left exit-3,arrive | 0->180,180->225,90->0 | + | a,f | ab,ef,ef | depart,roundabout turn straight exit-2,arrive | 0->180,180->225,180->0 | + | a,h | ab,gh,gh | depart,roundabout turn right exit-1,arrive | 0->180,180->225,270->0 | Scenario: Enter and Exit - Bearings Given the node map diff --git a/features/guidance/roundabout.feature b/features/guidance/roundabout.feature index e7f73ae4c..11594417f 100644 --- a/features/guidance/roundabout.feature +++ b/features/guidance/roundabout.feature @@ -377,8 +377,8 @@ Feature: Basic Roundabout | h | give_way | When I route I should get - | waypoints | route | turns | + | waypoints | route | turns | # since we cannot handle these invalid roundabout tags yet, we cannout output roundabout taggings. This will hopefully change some day #| w,x | ll,egg,egg,tr,tr | depart,roundabout-exit-1,roundabout-exit-2,arrive | - | w,x | ll,egg,egg,tr,tr | depart,turn right,continue left,turn slight left,arrive | + | w,x | ll,egg,egg,tr,tr | depart,turn right,continue left,turn straight,arrive | diff --git a/features/guidance/turn-angles.feature b/features/guidance/turn-angles.feature new file mode 100644 index 000000000..d2fd342f2 --- /dev/null +++ b/features/guidance/turn-angles.feature @@ -0,0 +1,1151 @@ +@routing @guidance @turn-angles +Feature: Simple Turns + + Background: + Given the profile "car" + Given a grid size of 1 meters + + Scenario: Offset Turn + Given the node map + """ + a - - - - - - - - b - - - - - - - - - c + | + | + | + | + | + | + d + ' + ' + ' + ' + ' + e + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + f + """ + + And the ways + | nodes | highway | name | lanes | + | abc | primary | road | 4 | + | bdef | primary | turn | 2 | + + When I route I should get + | waypoints | route | turns | + | a,c | road,road | depart,arrive | + | a,e | road,turn,turn | depart,turn slight right,arrive | + | e,a | turn,road,road | depart,turn slight left,arrive | + | e,c | turn,road,road | depart,turn sharp right,arrive | + + Scenario: Road Taking a Turn after Intersection + Given the node map + """ + a - - - - - - - - b - - - - - - - - - c + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + d + ' + ' + ' + ' + ' + e + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + ' + f + """ + + And the ways + | nodes | highway | name | lanes | + | abc | primary | road | 4 | + | bdef | primary | turn | 2 | + + When I route I should get + | waypoints | route | turns | + | a,c | road,road | depart,arrive | + | a,e | road,turn,turn | depart,turn right,arrive | + | e,a | turn,road,road | depart,turn left,arrive | + | e,c | turn,road,road | depart,turn right,arrive | + + Scenario: U-Turn Lane + Given the node map + """ + j + | + | + | + | + | + | + | + i - - - - - - - - - - - - - - - - - g - h + | + | + | + | + | + | + f + | + / + / + d - - - - - - - - e + / + c + / + | + | + | + | + | + a - k - - - - - - - - - - - - - - - - - b + """ + + And the ways + | nodes | highway | name | lanes | oneway | + | akb | primary | road | 4 | yes | + | hgi | primary | road | 4 | yes | + | akcdefg | primary_link | | 1 | yes | + | gj | tertiary | turn | 1 | | + + When I route I should get + | waypoints | route | turns | + | a,b | road,road | depart,arrive | + | a,i | road,road,road | depart,continue uturn,arrive | + | a,j | road,turn,turn | depart,turn left,arrive | + + #http://www.openstreetmap.org/#map=19/52.50871/13.26127 + Scenario: Curved Turn + Given the node map + """ + + f - - - - - - - - - - - e - - - - - - - d + l + k + \ + \ + j + \ + \ + i + \ + \ + h + | + | + | + | + a - - - - - - - - - - - - - - - - - b - c + | + | + | + | + | + | + | + | + g + """ + + And the ways + | nodes | highway | name | lanes | oneway | + | abc | primary | road | 5 | no | + | gb | secondary | turn | 3 | yes | + | bhijkle | unclassified | turn | 2 | yes | + | de | residential | road | | yes | + | ef | residential | road | 2 | yes | + + When I route I should get + | waypoints | route | turns | + | a,c | road,road | depart,arrive | + | c,a | road,road | depart,arrive | + | g,a | turn,road,road | depart,turn left,arrive | + | g,c | turn,road,road | depart,turn right,arrive | + | g,f | turn,road | depart,arrive | + | c,f | road,road,road | depart,continue right,arrive | + | a,f | road,road,road | depart,continue uturn,arrive | + + # http://www.openstreetmap.org/#map=19/52.48753/13.52838 + Scenario: Traffic Circle + Given the node map + """ + . l . + m k + . + . + j + + n + . + + . + i - - - p + + o + + . h + + a - b + + g + c + . + + d f + . . + e + """ + + And the ways + | nodes | highway | name | lanes | oneway | junction | + | ab | residential | road | 1 | yes | | + | ip | residential | road | 1 | yes | | + | bcdefghijklmnob | residential | road | 1 | yes | roundabout | + + When I route I should get + | waypoints | route | turns | intersections | + | a,p | road,road,road | depart,roundabout turn straight exit-1,arrive | true:90;true:135 false:270 false:345,true:90 false:180 true:345;true:270 | + + Scenario: Splitting Road with many lanes + Given the node map + """ + f - - - - - - - - - - - - - - - - - - - - e + ' + ' + ' + ' + ' + a - - - - - b + ' + ' + ' + ' + ' + c - - - - - - - - - - - - - - - - - - - - d + """ + + And the ways + | nodes | highway | name | lanes | oneway | + | ab | primary | road | 4 | no | + | bcd | primary | road | 2 | yes | + | efb | primary | road | 2 | yes | + + When I route I should get + | waypoints | route | turns | + | a,d | road,road | depart,arrive | + | e,a | road,road | depart,arrive | + + @todo + # currently the intersections don't match up do to the `merging` process. + # The intermediate intersection is technically no-turn at all, since the road continues. + Scenario: Splitting Road with many lanes + Given the node map + """ + f - - - - - - - - - - - - - - - - - - - - e + ' + ' + ' + ' + ' + a - - - - - b + ' + ' + ' + ' + ' + c - - - - - - - - - - - - - - - - - - - - d + """ + + And the ways + | nodes | highway | name | lanes | oneway | + | ab | primary | road | 4 | no | + | bcd | primary | road | 2 | yes | + | efb | primary | road | 2 | yes | + + When I route I should get + | waypoints | route | turns | intersections ; | + | a,d | road,road | depart,arrive | true:90;true:270 | + | e,a | road,road | depart,arrive | true:270;rue:90 | + + + #http://www.openstreetmap.org/#map=19/52.54759/13.43929 + Scenario: Curved Turn At Cross + Given the node map + """ + h + | + | + | + | + | . b - - - - - - - - - - - - - - - - - - a + | . + | . + | c + | + | + | + d + | + | + | + e | + . | + . | + g - - - - - - - - - - - - - f | + | + | + | + i + """ + + And the ways + | nodes | lanes | name | + | abcd | 3 | road | + | defg | 2 | road | + | hdi | 2 | cross | + + When I route I should get + | waypoints | route | turns | + | a,g | road,road | depart,arrive | + | i,a | cross,road,road | depart,turn right,arrive | + | i,g | cross,road,road | depart,turn left,arrive | + | h,g | cross,road,road | depart,turn right,arrive | + | h,a | cross,road,road | depart,turn left,arrive | + + #http://www.openstreetmap.org/#map=19/52.56243/13.32666 + Scenario: Modelled Curve on Segregated Road + Given the node map + """ + a f + | . e - - - - g + | h ' | + | . ' | + | i | + | . | + | | + | . | + | | + | j | + | | + | ' | + |. | + | | + m b - - - - - - - - - - - - - - - - - - - k - - - - l + | | + c d + """ + + And the ways + | nodes | name | oneway | lanes | + | abc | road | yes | 3 | + | dkef | road | yes | 3 | + | mbkl | turn | yes | 3 | + | gehijb | turn | yes | 3 | + + When I route I should get + | waypoints | route | turns | + | a,c | road,road | depart,arrive | + | a,l | road,turn,turn | depart,turn left,arrive | + | a,f | road,road,road | depart,continue uturn,arrive | + | d,f | road,road | depart,arrive | + | d,l | road,turn,turn | depart,turn right,arrive | + | d,c | road,road,road | depart,continue uturn,arrive | + | g,l | turn,turn,turn | depart,continue uturn,arrive | + | g,c | turn,road,road | depart,turn left,arrive | + + #http://www.openstreetmap.org/#map=19/52.50878/13.26085 + Scenario: Curved Turn + Given the node map + """ + f - - - - - - - - - e - - - - - - - - - d + k + . + j + + . + + i + + . + + h + | + | + | + | + | + | + | + | + a - - - - - - - - - - - - - - - - - b - c + | + | + | + | + | + | + | + | + g + """ + + And the ways + | nodes | highway | name | lanes | oneway | + | abc | primary | road | 5 | no | + | gb | secondary | turn | 3 | yes | + | bhijke | unclassified | turn | 2 | yes | + | de | residential | road | | yes | + | ef | residential | road | 2 | yes | + + When I route I should get + | waypoints | route | turns | + | g,f | turn,road,road | depart,turn left,arrive | + | c,f | road,road,road | depart,continue right,arrive | + + #http://www.openstreetmap.org/search?query=52.479264%2013.295617#map=19/52.47926/13.29562 + Scenario: Splitting Roads with curved split + Given the node map + """ + f - - - - - - - - - - - - - - - - - e + + . + + . + + g + . + a - - - - - b + . + c + + . + + . + + h - - - - - - - - - - - - - - - - - d + """ + + And the ways + | nodes | highway | name | lanes | oneway | + | ab | primary | road | 4 | no | + | bchd | primary | road | 2 | yes | + | efgb | primary | road | 2 | yes | + + When I route I should get + | waypoints | route | turns | + | a,d | road,road | depart,arrive | + | e,a | road,road | depart,arrive | + + #http://www.openstreetmap.org/#map=19/52.51710/13.35462 + Scenario: Merge next to modelled turn + Given the node map + """ + f - - - - - - - - - - - - - - - e + . + + . + + . + + a - - - - - - - - b + .\ + .\ + .\ + g \ + \ + . \ + c- - - - - - - - - - - - - - - d + h + | + | + | + | + | + | + | + | + | + | + i + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | ab | spree | no | 6 | tertiary | + | bcd | spree | yes | 3 | tertiary | + | efb | spree | yes | 3 | tertiary | + | bghi | | no | | service | + + When I route I should get + | waypoints | route | turns | + | a,d | spree,spree | depart,arrive | + | e,a | spree,spree | depart,arrive | + | a,i | spree,, | depart,turn right,arrive | + | e,i | spree,, | depart,turn left,arrive | + + Scenario: Merge next to modelled turn + Given the node map + """ + f - - - - - - - - - - - - - - - e + . + + . + + . + + a - - - - - - - - b + .\ + .\ + .\ + g \ + \ + . \ + c- - - - - - - - - - - - - - - d + h + | + | + | + | + | + | + | + | + | + | + i + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | ab | spree | no | 6 | tertiary | + | bcd | spree | yes | 3 | tertiary | + | efb | spree | yes | 3 | tertiary | + | bghi | | no | | living_street | + + When I route I should get + | waypoints | route | turns | + | a,d | spree,spree | depart,arrive | + | e,a | spree,spree | depart,arrive | + | a,i | spree,, | depart,turn right,arrive | + | e,i | spree,, | depart,turn left,arrive | + + # http://www.openstreetmap.org/#map=18/52.52147/13.41779 + Scenario: Parking Isle + Given the node map + """ + c - - - - - - - - - - - - - - b - - - - - - - - - - - - - - a + . + . + . + . + . + . + . + 1 . + . + . + . + . + j - - - - - - - - - - - - - - - - - - - - - - i + . + . + . + . + d + . + . + . + . + e + . + . + . + k + 2 + . + . + + g - - - - - - - - - - f - - - - - - - - - - - - - - - - - - h + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | gfh | allee | yes | 3 | primary | + | bjdekf | | yes | | service | + | ij | | no | | parking aisle | + | abc | allee | yes | 3 | primary | + + When I route I should get + | waypoints | route | turns | + | a,c | allee,allee | depart,arrive | + | a,h | allee,allee,allee | depart,continue uturn,arrive | + | 1,h | ,allee,allee | depart,turn left,arrive | + | 2,h | ,allee,allee | depart,turn left,arrive | + + #http://www.openstreetmap.org/#map=18/52.56251/13.32650 + Scenario: Curved Turn on Separated Directions + Given the node map + """ + e d + f - - - - - - - - - - - - - - - c - - - - - - - - - - - j + | l ' | + | ' | + | ' | + | ' | + | ' | + | n | + | | + | ' | + | | + | ' | + | | + | | + | | + |' | + | | + | | + | | + | | + g | + h - - - - - - - - - - - - - - - b - - - - - - - - - - - o + | | + | | + | | + | | + | | + | | + i a + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | jc | Kapweg | yes | 3 | primary | + | clng | Kapweg | yes | | primary_link | + | hbo | Kapweg | yes | 2 | primary | + | efg | Kurt | yes | 4 | secondary | + | gh | Kurt | yes | 2 | primary | + | hi | Kurt | yes | 3 | primary | + | ab | Kurt | yes | 4 | primary | + | cd | Kurt | yes | 3 | secondary | + | bc | Kurt | yes | 2 | primary | + + When I route I should get + | waypoints | route | turns | + | j,i | Kapweg,Kurt,Kurt | depart,turn left,arrive | + | j,o | Kapweg,Kapweg,Kapweg | depart,continue uturn,arrive | + | a,i | Kurt,Kurt,Kurt | depart,continue uturn,arrive | + + #http://www.openstreetmap.org/#map=19/52.53731/13.36033 + Scenario: Splitting Road to Left + Given the node map + """ + k + + . + + . + + j + + . + + . + + . + + a - - - - - - - b - - - - - - c - - - - - - - d - - - - - - e + . + ' + f . + . + i + . . + + . . + + . . + + . . + + . . + + g h + """ + + And the nodes + | node | highway | + | i | traffic_signals | + + And the ways + | nodes | name | oneway | lanes | highway | + | abc | Perle | no | 4 | secondary | + | cde | Perle | no | 6 | secondary | + | cfg | Heide | yes | 2 | secondary | + | hic | Heide | yes | 3 | secondary | + | cjk | Friede | no | | tertiary | + + When I route I should get + | waypoints | route | turns | + | a,g | Perle,Heide,Heide | depart,turn right,arrive | + | a,k | Perle,Friede,Friede | depart,turn left,arrive | + | a,e | Perle,Perle | depart,arrive | + | e,k | Perle,Friede,Friede | depart,turn right,arrive | + | e,g | Perle,Heide,Heide | depart,turn left,arrive | + | h,k | Heide,Friede,Friede | depart,new name slight left,arrive | + | h,e | Heide,Perle,Perle | depart,turn right,arrive | + | h,a | Heide,Perle,Perle | depart,turn left,arrive | + + #http://www.openstreetmap.org/#map=19/52.53293/13.32956 + Scenario: Curved Exit from Curved Road + Given the node map + """ + g + . + . + f + . + . + . + . + . + e + . + + d + ' + c \ + a - - - - - - b ' + h . + i + ' . . . j + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | abcd | Siemens | no | 5 | secondary | + | defg | Erna | no | 3 | secondary | + | dhij | Siemens | no | | residential | + + When I route I should get + | waypoints | route | turns | + | a,j | Siemens,Siemens,Siemens | depart,continue slight right,arrive | + | a,g | Siemens,Erna,Erna | depart,new name slight left,arrive | + | g,j | Erna,Siemens,Siemens | depart,turn sharp left,arrive | + | g,a | Erna,Siemens,Siemens | depart,new name slight right,arrive | + + #http://www.openstreetmap.org/#map=19/52.51303/13.32170 + Scenario: Ernst Reuter Platz + Given the node map + """ + j g + . . + . . + . . + i h + .' e - - k - - d '. + ' . + f c + . + . + . + b + . + . + . + . + a + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | abcdk | ernst | yes | 4 | primary | + | ek | ernst | yes | 5 | primary | + | kef | ernst | yes | 4 | primary | + | ghd | march | yes | 3 | secondary | + | eij | otto | yes | 2 | secondary | + + When I route I should get + | waypoints | route | turns | + | a,j | ernst,otto,otto | depart,turn slight right,arrive | + | a,f | ernst,ernst | depart,arrive | + + #http://www.openstreetmap.org/#map=19/52.52409/13.36829 + Scenario: Tiny Curve at Turn + Given the node map + """ + h + | + | + | + | + | + | + f - - - - - - - - i - - g + e | + . d | + a - - - - - - - - - - b - c ' j + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | abcdef | ella | no | | residential | + | fig | ella | no | | residential | + | hf | ilse | no | | residential | + | ij | wash | no | | residential | + + When I route I should get + | waypoints | route | turns | + | a,g | ella,ella | depart,arrive | + | g,a | ella,ella | depart,arrive | + | g,h | ella,ilse,ilse | depart,turn right,arrive | + | a,h | ella,ilse,ilse | depart,turn left,arrive | + | h,g | ilse,ella,ella | depart,turn left,arrive | + | h,a | ilse,ella,ella | depart,turn right,arrive | + + #http://www.openstreetmap.org/#map=18/52.53738/13.36027 + Scenario: Merging at Turn - Don't report u-turn + Given the node map + """ + g + . + . + . + . + f + h . + . . + . j + . . + c + . . . + . . . + . . . + . . . + . . . + . . . + . . b + . . . + . . . + . . . + . d . + . . . + . . . + i . . + e a + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | abc | Heide | yes | 3 | secondary | + | cde | Heide | yes | 2 | secondary | + | cjf | Perle | yes | 6 | secondary | + | cjf | Perle | no | 6 | secondary | + | fg | Fenn | no | 6 | secondary | + | ch | Friede | no | | tertiary | + | ic | Perle | no | 4 | secondary | + + And the nodes + | node | highway | + | j | traffic_signals | + | b | traffic_signals | + + When I route I should get + | waypoints | route | turns | + | a,e | Heide,Heide,Heide | depart,continue uturn,arrive | + | a,g | Heide,Fenn,Fenn | depart,turn right,arrive | + | a,h | Heide,Friede,Friede | depart,turn slight left,arrive | + | i,e | Perle,Heide,Heide | depart,turn right,arrive | + | i,h | Perle,Friede,Friede | depart,turn left,arrive | + + #http://www.openstreetmap.org/#map=19/52.48630/13.36017 + Scenario: Don't Break U-turns + Given the node map + """ + . . . b - - - - - - - - - - a + c ' + . ' / + . ' i + d ' / + / + j + | + | + | + | + | + | + e - - - - - - - k + | . + f . + l ' - g - - - - - - - - - - - h + """ + + And the ways + | nodes | name | oneway | lanes | highway | + | ab | julius | yes | 2 | secondary | + | gh | julius | yes | 2 | secondary | + | bcd | kolonne | yes | 2 | secondary | + | ekg | kolonne | yes | 2 | secondary | + | cijkf | feurig | yes | | residential | + | fl | feurig | no | | residential | + + When I route I should get + | waypoints | route | turns | + | b,g | kolonne,kolonne,kolonne | depart,continue uturn,arrive | + + #http://www.openstreetmap.org/#map=19/52.51633/13.42077 + Scenario: Service Road at the end with slight offset + Given the node map + """ + d - - - - - - - - - - - - - - e + | + c - - - - - - - - - - - - - - b + | + | + | + | + | + | + | + | + | + a + """ + + And the ways + | nodes | name | highway | + | ab | holz | residential | + | bc | | service | + | bde | | service | + + When I route I should get + | waypoints | route | turns | + | e,c | , | depart,arrive | + | e,a | ,holz,holz | depart,turn left,arrive | + | c,e | , | depart,arrive | + | c,a | ,holz,holz | depart,turn right,arrive | + | a,c | holz,, | depart,turn left,arrive | + | a,e | holz,, | depart,turn right,arrive | + + #http://www.openstreetmap.org/#map=19/52.45037/13.51923 + Scenario: Special Turn Road + Given the node map + """ + h - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - g - - - - f + | + | + | + | + | + d - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - e + . + . + . + . + . + a - - - b - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - c + """ + + And the ways + | nodes | name | ref | lanes | highway | oneway | + | abc | mich | b9 | 3 | primary | yes | + | fgh | mich | b9 | 3 | primary | yes | + | bdeg | mich | | | primary_link | yes | + + When I route I should get + | waypoints | route | ref | turns | + | a,h | mich,mich,mich | b9,b9,b9 | depart,continue uturn,arrive | + + #http://www.openstreetmap.org/#map=19/52.49449/13.18116 + Scenario: Curved Parking Lot Road + Given the node map + """ + m + | + | + | + | + f - - - - - - - - - - - - - - e - - - - - - - - - - - - - - d + l + k + \ + j + i + / + h + g + a - - - - - - - - - - - - - - b - - - - - - - - - - - - - - c + """ + + And the ways + | nodes | name | oneway | highway | + | abc | gato | yes | residential | + | def | gato | yes | residential | + | bghijkle | | yes | service | + | em | hain | no | service | + + When I route I should get + | waypoints | route | turns | + | a,f | gato,gato,gato | depart,continue uturn,arrive | + | d,m | gato,hain,hain | depart,turn right,arrive | + | a,m | gato,hain,hain | depart,turn left,arrive | + | d,f | gato,gato | depart,arrive | + + #http://www.openstreetmap.org/#map=19/52.60831/13.42990 + Scenario: Double Curve Turn + Given the node map + """ + + _ . d + f - - - - - - - - - - - - - - - - - - - -_- e ' + . + g + . + . + . + . + h + . _ c + . . + . . + i . + . . + . . + a - - - - - - - - - - - - - - b + . + . + . + j + . + . + k + """ + + And the ways + | nodes | name | oneway | highway | lanes | + | ab | rose | yes | secondary | 2 | + | ef | rose | yes | secondary | 2 | + | de | trift | yes | secondary | 2 | + | bc | trift | yes | secondary | 2 | + | eghibj | muhle | yes | tertiary | 1 | + + And the nodes + | node | highway | + | h | traffic_signals | + | j | traffic_signals | + + When I route I should get + | waypoints | route | turns | + | a,c | rose,trift,trift | depart,new name slight left,arrive | + | a,k | rose,muhle,muhle | depart,turn slight right,arrive | + | d,f | trift,rose,rose | depart,new name straight,arrive | + | d,k | trift,muhle,muhle | depart,turn sharp left,arrive | + | d,c | trift,trift,trift | depart,continue uturn,arrive | + + #http://www.openstreetmap.org/#map=19/52.50740/13.44824 + Scenario: Turning Loop at the end of the road + Given the node map + """ + l - - k _ + . j + m ' + . i + . . + . . + . h + . . + n . + . . + . g + o . + . . + p f + . . + . . + . . e + a - - - - b - - - - c - - - - - - - - - - - - - d ' + """ + + And the ways + | nodes | name | highway | lanes | + | abcdefghijklmnopc | circled | residential | 1 | + + When I route I should get + | waypoints | bearings | route | turns | + | b,a | 90,2 180,180 | circled,circled | depart,arrive | + + Scenario: Curved Parking Lot Road + Given the node map + """ + m + | + | + | + | + f - - - - - - - - - - - - - - e - - - - - - - - - - - - - - d + l + k + + j + i + + h + g + a - - - - - - - - - - - - - - b - - - - - - - - - - - - - - c + """ + + And the ways + | nodes | name | oneway | highway | + | abc | gato | yes | residential | + | def | gato | yes | residential | + | bghijkle | | yes | service | + | em | hain | no | service | + + When I route I should get + | waypoints | route | turns | + | a,m | gato,hain,hain | depart,turn left,arrive | diff --git a/features/guidance/turn-lanes.feature b/features/guidance/turn-lanes.feature index 7a839ad9c..3596746ca 100644 --- a/features/guidance/turn-lanes.feature +++ b/features/guidance/turn-lanes.feature @@ -901,10 +901,10 @@ Feature: Turn Lane Guidance Given the node map """ f e - - - c - a b + | | + | | + | c + a - b ' | g d """ @@ -1091,3 +1091,26 @@ Feature: Turn Lane Guidance | waypoints | route | turns | lanes | | a,f | ,ksd,ksd | depart,turn left,arrive | ,left:true none:true right:false, | | a,i | ,ksd,ksd | depart,turn right,arrive | ,left:false none:true right:true, | + + Scenario: Reverse Not Allowed + Given the node map + """ + n o + f - - e\- - g-j-m + d | | + a - 1 b/- - c-i-l + h k + """ + + And the ways + | nodes | name | highway | oneway | turn:lanes:forward | + | abc | road | secondary | yes | left\|through\|right | + | cil | road | secondary | yes | | + | mjgef | road | secondary | yes | | + | bde | road | secondary_link | yes | | + | ngch | turn | secondary | yes | | + | kijo | turn | secondary | yes | | + + When I route I should get + | waypoints | bearings | route | turns | + | 1,a | 90,2 180,180 | | | diff --git a/features/guidance/turn.feature b/features/guidance/turn.feature index e2e94604c..6250a47e5 100644 --- a/features/guidance/turn.feature +++ b/features/guidance/turn.feature @@ -827,16 +827,16 @@ Feature: Simple Turns And the ways | nodes | highway | oneway | - | abc | primary | no | - | db | primary | no | + | abc | primary | yes | + | bd | primary | yes | | eb | primary | yes | - | fb | primary | no | - | bg | primary | no | + | bf | primary | yes | + | bg | primary | yes | When I route I should get | waypoints | route | turns | - | a,d | abc,db,db | depart,turn sharp right,arrive | - | a,f | abc,fb,fb | depart,turn right,arrive | + | a,d | abc,bd,bd | depart,turn sharp right,arrive | + | a,f | abc,bf,bf | depart,turn right,arrive | Scenario: Right Turn Assignment Three Conflicting Turns with invalid - 3 Given the node map @@ -907,11 +907,10 @@ Feature: Simple Turns Scenario: Turn Lane on Splitting up Road Given the node map """ - - g f - - h e c d - a b + g - - - f + \ + . h - - e - - c - - d + a - - b _______/ i """ diff --git a/features/support/route.js b/features/support/route.js index 32401b731..be4cf97ac 100644 --- a/features/support/route.js +++ b/features/support/route.js @@ -121,8 +121,7 @@ module.exports = function () { return this.requestPath('match', params, callback); }; - this.extractInstructionList = (instructions, keyFinder, postfix) => { - postfix = postfix || null; + this.extractInstructionList = (instructions, keyFinder) => { if (instructions) { return instructions.legs.reduce((m, v) => m.concat(v.steps), []) .map(keyFinder) @@ -152,8 +151,16 @@ module.exports = function () { return this.extractInstructionList(instructions, s => s.destinations || ''); }; + this.reverseBearing = (bearing) => { + if (bearing >= 180) + return bearing - 180.; + return bearing + 180; + }; + this.bearingList = (instructions) => { - return this.extractInstructionList(instructions, s => s.maneuver.bearing_before + '->' + s.maneuver.bearing_after); + return this.extractInstructionList(instructions, s => ('in' in s.intersections[0] ? this.reverseBearing(s.intersections[0].bearings[s.intersections[0].in]) : 0) + + '->' + + ('out' in s.intersections[0] ? s.intersections[0].bearings[s.intersections[0].out] : 0)); }; this.annotationList = (instructions) => { diff --git a/include/engine/datafacade/datafacade_base.hpp b/include/engine/datafacade/datafacade_base.hpp index 79a1892f4..331786612 100644 --- a/include/engine/datafacade/datafacade_base.hpp +++ b/include/engine/datafacade/datafacade_base.hpp @@ -13,6 +13,8 @@ #include "util/exception.hpp" #include "util/guidance/bearing_class.hpp" #include "util/guidance/entry_class.hpp" +#include "util/guidance/turn_bearing.hpp" +#include "util/guidance/turn_lanes.hpp" #include "util/integer_range.hpp" #include "util/string_util.hpp" #include "util/typedefs.hpp" @@ -173,6 +175,9 @@ class BaseDataFacade virtual BearingClassID GetBearingClassID(const NodeID id) const = 0; + virtual util::guidance::TurnBearing PreTurnBearing(const EdgeID eid) const = 0; + virtual util::guidance::TurnBearing PostTurnBearing(const EdgeID eid) const = 0; + virtual util::guidance::BearingClass GetBearingClass(const BearingClassID bearing_class_id) const = 0; diff --git a/include/engine/datafacade/internal_datafacade.hpp b/include/engine/datafacade/internal_datafacade.hpp index 9d3b12f9e..c442754f9 100644 --- a/include/engine/datafacade/internal_datafacade.hpp +++ b/include/engine/datafacade/internal_datafacade.hpp @@ -18,6 +18,7 @@ #include "storage/io.hpp" #include "engine/geospatial_query.hpp" #include "util/graph_loader.hpp" +#include "util/guidance/turn_bearing.hpp" #include "util/guidance/turn_lanes.hpp" #include "util/io.hpp" #include "util/packed_vector.hpp" @@ -104,6 +105,9 @@ class InternalDataFacade final : public BaseDataFacade util::ShM::vector m_bearing_class_id_table; // entry class IDs by edge based egde util::ShM::vector m_entry_class_id_list; + // bearings pre/post turn + util::ShM::vector m_pre_turn_bearing; + util::ShM::vector m_post_turn_bearing; // the look-up table for entry classes. An entry class lists the possibility of entry for all // available turns. For every turn, there is an associated entry class. util::ShM::vector m_entry_class_table; @@ -205,6 +209,8 @@ class InternalDataFacade final : public BaseDataFacade m_lane_data_id.resize(number_of_edges); m_travel_mode_list.resize(number_of_edges); m_entry_class_id_list.resize(number_of_edges); + m_pre_turn_bearing.resize(number_of_edges); + m_post_turn_bearing.resize(number_of_edges); extractor::OriginalEdgeData current_edge_data; for (unsigned i = 0; i < number_of_edges; ++i) @@ -217,6 +223,8 @@ class InternalDataFacade final : public BaseDataFacade m_lane_data_id[i] = current_edge_data.lane_data_id; m_travel_mode_list[i] = current_edge_data.travel_mode; m_entry_class_id_list[i] = current_edge_data.entry_classid; + m_pre_turn_bearing[i] = current_edge_data.pre_turn_bearing; + m_post_turn_bearing[i] = current_edge_data.post_turn_bearing; } } @@ -921,6 +929,15 @@ class InternalDataFacade final : public BaseDataFacade return m_entry_class_id_list.at(eid); } + util::guidance::TurnBearing PreTurnBearing(const EdgeID eid) const override final + { + return m_pre_turn_bearing.at(eid); + } + util::guidance::TurnBearing PostTurnBearing(const EdgeID eid) const override final + { + return m_post_turn_bearing.at(eid); + } + util::guidance::EntryClass GetEntryClass(const EntryClassID entry_class_id) const override final { return m_entry_class_table.at(entry_class_id); diff --git a/include/engine/datafacade/shared_datafacade.hpp b/include/engine/datafacade/shared_datafacade.hpp index f0d1e844d..d67a99764 100644 --- a/include/engine/datafacade/shared_datafacade.hpp +++ b/include/engine/datafacade/shared_datafacade.hpp @@ -18,6 +18,7 @@ #include "engine/geospatial_query.hpp" #include "util/packed_vector.hpp" +#include "util/guidance/turn_bearing.hpp" #include "util/range_table.hpp" #include "util/rectangle.hpp" #include "util/simple_logger.hpp" @@ -85,6 +86,8 @@ class SharedDataFacade final : public BaseDataFacade util::ShM::vector m_lane_data_id; util::ShM::vector m_turn_instruction_list; util::ShM::vector m_travel_mode_list; + util::ShM::vector m_pre_turn_bearing; + util::ShM::vector m_post_turn_bearing; util::ShM::vector m_names_char_list; util::ShM::vector m_name_begin_indices; util::ShM::vector m_geometry_indices; @@ -104,11 +107,11 @@ class SharedDataFacade final : public BaseDataFacade boost::filesystem::path file_index_path; std::shared_ptr> m_name_table; - // bearing classes by node based node util::ShM::vector m_bearing_class_id_table; // entry class IDs util::ShM::vector m_entry_class_id_list; + // the look-up table for entry classes. An entry class lists the possibility of entry for all // available turns. Such a class id is stored with every edge. util::ShM::vector m_entry_class_table; @@ -182,7 +185,7 @@ class SharedDataFacade final : public BaseDataFacade void LoadNodeAndEdgeInformation() { - auto coordinate_list_ptr = data_layout->GetBlockPtr( + const auto coordinate_list_ptr = data_layout->GetBlockPtr( shared_memory, storage::SharedDataLayout::COORDINATE_LIST); m_coordinate_list.reset( coordinate_list_ptr, @@ -193,7 +196,7 @@ class SharedDataFacade final : public BaseDataFacade BOOST_ASSERT(GetCoordinateOfNode(i).IsValid()); } - auto osmnodeid_list_ptr = data_layout->GetBlockPtr( + const auto osmnodeid_list_ptr = data_layout->GetBlockPtr( shared_memory, storage::SharedDataLayout::OSM_NODE_ID_LIST); m_osmnodeid_list.reset( osmnodeid_list_ptr, @@ -202,26 +205,27 @@ class SharedDataFacade final : public BaseDataFacade m_osmnodeid_list.set_number_of_entries( data_layout->num_entries[storage::SharedDataLayout::COORDINATE_LIST]); - auto travel_mode_list_ptr = data_layout->GetBlockPtr( + const auto travel_mode_list_ptr = data_layout->GetBlockPtr( shared_memory, storage::SharedDataLayout::TRAVEL_MODE); util::ShM::vector travel_mode_list( travel_mode_list_ptr, data_layout->num_entries[storage::SharedDataLayout::TRAVEL_MODE]); m_travel_mode_list = std::move(travel_mode_list); - auto lane_data_id_ptr = data_layout->GetBlockPtr( + const auto lane_data_id_ptr = data_layout->GetBlockPtr( shared_memory, storage::SharedDataLayout::LANE_DATA_ID); util::ShM::vector lane_data_id( lane_data_id_ptr, data_layout->num_entries[storage::SharedDataLayout::LANE_DATA_ID]); m_lane_data_id = std::move(lane_data_id); - auto lane_tupel_id_pair_ptr = data_layout->GetBlockPtr( - shared_memory, storage::SharedDataLayout::TURN_LANE_DATA); + const auto lane_tupel_id_pair_ptr = + data_layout->GetBlockPtr( + shared_memory, storage::SharedDataLayout::TURN_LANE_DATA); util::ShM::vector lane_tupel_id_pair( lane_tupel_id_pair_ptr, data_layout->num_entries[storage::SharedDataLayout::TURN_LANE_DATA]); m_lane_tupel_id_pairs = std::move(lane_tupel_id_pair); - auto turn_instruction_list_ptr = + const auto turn_instruction_list_ptr = data_layout->GetBlockPtr( shared_memory, storage::SharedDataLayout::TURN_INSTRUCTION); util::ShM::vector turn_instruction_list( @@ -229,18 +233,32 @@ class SharedDataFacade final : public BaseDataFacade data_layout->num_entries[storage::SharedDataLayout::TURN_INSTRUCTION]); m_turn_instruction_list = std::move(turn_instruction_list); - auto name_id_list_ptr = data_layout->GetBlockPtr( + const auto name_id_list_ptr = data_layout->GetBlockPtr( shared_memory, storage::SharedDataLayout::NAME_ID_LIST); util::ShM::vector name_id_list( name_id_list_ptr, data_layout->num_entries[storage::SharedDataLayout::NAME_ID_LIST]); m_name_ID_list = std::move(name_id_list); - auto entry_class_id_list_ptr = data_layout->GetBlockPtr( + const auto entry_class_id_list_ptr = data_layout->GetBlockPtr( shared_memory, storage::SharedDataLayout::ENTRY_CLASSID); typename util::ShM::vector entry_class_id_list( entry_class_id_list_ptr, data_layout->num_entries[storage::SharedDataLayout::ENTRY_CLASSID]); m_entry_class_id_list = std::move(entry_class_id_list); + + const auto pre_turn_bearing_ptr = data_layout->GetBlockPtr( + shared_memory, storage::SharedDataLayout::PRE_TURN_BEARING); + typename util::ShM::vector pre_turn_bearing( + pre_turn_bearing_ptr, + data_layout->num_entries[storage::SharedDataLayout::PRE_TURN_BEARING]); + m_pre_turn_bearing = std::move(pre_turn_bearing); + + const auto post_turn_bearing_ptr = data_layout->GetBlockPtr( + shared_memory, storage::SharedDataLayout::POST_TURN_BEARING); + typename util::ShM::vector post_turn_bearing( + post_turn_bearing_ptr, + data_layout->num_entries[storage::SharedDataLayout::POST_TURN_BEARING]); + m_post_turn_bearing = std::move(post_turn_bearing); } void LoadViaNodeList() @@ -931,6 +949,15 @@ class SharedDataFacade final : public BaseDataFacade return m_entry_class_id_list.at(eid); } + util::guidance::TurnBearing PreTurnBearing(const EdgeID eid) const override final + { + return m_pre_turn_bearing.at(eid); + } + util::guidance::TurnBearing PostTurnBearing(const EdgeID eid) const override final + { + return m_post_turn_bearing.at(eid); + } + util::guidance::EntryClass GetEntryClass(const EntryClassID entry_class_id) const override final { return m_entry_class_table.at(entry_class_id); diff --git a/include/engine/geospatial_query.hpp b/include/engine/geospatial_query.hpp index c00a2a11e..9470522ff 100644 --- a/include/engine/geospatial_query.hpp +++ b/include/engine/geospatial_query.hpp @@ -372,8 +372,10 @@ template class GeospatialQuery int forward_offset = 0, forward_weight = 0; int reverse_offset = 0, reverse_weight = 0; - const std::vector forward_weight_vector = datafacade.GetUncompressedForwardWeights(data.packed_geometry_id); - const std::vector reverse_weight_vector = datafacade.GetUncompressedReverseWeights(data.packed_geometry_id); + const std::vector forward_weight_vector = + datafacade.GetUncompressedForwardWeights(data.packed_geometry_id); + const std::vector reverse_weight_vector = + datafacade.GetUncompressedReverseWeights(data.packed_geometry_id); for (std::size_t i = 0; i < data.fwd_segment_position; i++) { @@ -383,8 +385,7 @@ template class GeospatialQuery BOOST_ASSERT(data.fwd_segment_position < reverse_weight_vector.size()); - for (std::size_t i = 0; - i < reverse_weight_vector.size() - data.fwd_segment_position - 1; + for (std::size_t i = 0; i < reverse_weight_vector.size() - data.fwd_segment_position - 1; i++) { reverse_offset += reverse_weight_vector[i]; @@ -469,16 +470,18 @@ template class GeospatialQuery bool forward_edge_valid = false; bool reverse_edge_valid = false; - const std::vector forward_weight_vector = datafacade.GetUncompressedForwardWeights(segment.data.packed_geometry_id); + const std::vector forward_weight_vector = + datafacade.GetUncompressedForwardWeights(segment.data.packed_geometry_id); if (forward_weight_vector[segment.data.fwd_segment_position] != INVALID_EDGE_WEIGHT) { forward_edge_valid = segment.data.forward_segment_id.enabled; } - const std::vector reverse_weight_vector = datafacade.GetUncompressedReverseWeights(segment.data.packed_geometry_id); - if (reverse_weight_vector[reverse_weight_vector.size() - - segment.data.fwd_segment_position - 1] != INVALID_EDGE_WEIGHT) + const std::vector reverse_weight_vector = + datafacade.GetUncompressedReverseWeights(segment.data.packed_geometry_id); + if (reverse_weight_vector[reverse_weight_vector.size() - segment.data.fwd_segment_position - + 1] != INVALID_EDGE_WEIGHT) { reverse_edge_valid = segment.data.reverse_segment_id.enabled; } diff --git a/include/engine/guidance/assemble_geometry.hpp b/include/engine/guidance/assemble_geometry.hpp index 09372c9b4..629b00607 100644 --- a/include/engine/guidance/assemble_geometry.hpp +++ b/include/engine/guidance/assemble_geometry.hpp @@ -47,8 +47,8 @@ inline LegGeometry assembleGeometry(const datafacade::BaseDataFacade &facade, // TODO: check if this was traversed in reverse? const std::vector source_geometry = facade.GetUncompressedForwardGeometry(source_node.packed_geometry_id); - geometry.osm_node_ids.push_back(facade.GetOSMNodeIDOfNode( - source_geometry[source_node.fwd_segment_position])); + geometry.osm_node_ids.push_back( + facade.GetOSMNodeIDOfNode(source_geometry[source_node.fwd_segment_position])); auto cumulative_distance = 0.; auto current_distance = 0.; diff --git a/include/engine/guidance/assemble_steps.hpp b/include/engine/guidance/assemble_steps.hpp index da503beab..1a034c4f6 100644 --- a/include/engine/guidance/assemble_steps.hpp +++ b/include/engine/guidance/assemble_steps.hpp @@ -33,8 +33,6 @@ namespace detail { std::pair getDepartBearings(const LegGeometry &leg_geometry); std::pair getArriveBearings(const LegGeometry &leg_geometry); -std::pair getIntermediateBearings(const LegGeometry &leg_geometry, - const std::size_t segment_index); } // ns detail inline std::vector assembleSteps(const datafacade::BaseDataFacade &facade, @@ -132,12 +130,14 @@ inline std::vector assembleSteps(const datafacade::BaseDataFacade &fa step_name_id = target_node.name_id; } - bearings = detail::getIntermediateBearings(leg_geometry, segment_index); + // extract bearings + bearings = std::make_pair( + path_point.pre_turn_bearing.Get(), path_point.post_turn_bearing.Get()); const auto entry_class = facade.GetEntryClass(path_point.entry_classid); const auto bearing_class = facade.GetBearingClass(facade.GetBearingClassID(path_point.turn_via_node)); - intersection.in = bearing_class.findMatchingBearing( - util::bearing::reverseBearing(bearings.first)); + auto bearing_data = bearing_class.getAvailableBearings(); + intersection.in = bearing_class.findMatchingBearing(bearings.first); intersection.out = bearing_class.findMatchingBearing(bearings.second); intersection.location = facade.GetCoordinateOfNode(path_point.turn_via_node); intersection.bearings.clear(); @@ -155,8 +155,10 @@ inline std::vector assembleSteps(const datafacade::BaseDataFacade &fa { intersection.entry.push_back(entry_class.allowsEntry(idx)); } + std::int16_t bearing_in_driving_direction = + util::bearing::reverseBearing(std::round(bearings.first)); maneuver = {intersection.location, - bearings.first, + bearing_in_driving_direction, bearings.second, path_point.turn_instruction, WaypointType::None, diff --git a/include/engine/internal_route_result.hpp b/include/engine/internal_route_result.hpp index 7fa47fd58..939de14e3 100644 --- a/include/engine/internal_route_result.hpp +++ b/include/engine/internal_route_result.hpp @@ -5,6 +5,7 @@ #include "extractor/travel_mode.hpp" #include "engine/phantom_node.hpp" #include "osrm/coordinate.hpp" +#include "util/guidance/turn_bearing.hpp" #include "util/guidance/turn_lanes.hpp" #include "util/typedefs.hpp" @@ -36,6 +37,11 @@ struct PathData // Source of the speed value on this road segment DatasourceID datasource_id; + + // bearing (as seen from the intersection) pre-turn + util::guidance::TurnBearing pre_turn_bearing; + // bearing (as seen from the intersection) post-turn + util::guidance::TurnBearing post_turn_bearing; }; struct InternalRouteResult diff --git a/include/engine/phantom_node.hpp b/include/engine/phantom_node.hpp index ca0a9698d..3dd0cf63c 100644 --- a/include/engine/phantom_node.hpp +++ b/include/engine/phantom_node.hpp @@ -64,10 +64,10 @@ struct PhantomNode : forward_segment_id(forward_segment_id), reverse_segment_id(reverse_segment_id), name_id(name_id), forward_weight(forward_weight), reverse_weight(reverse_weight), forward_offset(forward_offset), reverse_offset(reverse_offset), - packed_geometry_id(packed_geometry_id_), - component{component_id, is_tiny_component}, location(std::move(location)), - input_location(std::move(input_location)), fwd_segment_position(fwd_segment_position), - forward_travel_mode(forward_travel_mode), backward_travel_mode(backward_travel_mode) + packed_geometry_id(packed_geometry_id_), component{component_id, is_tiny_component}, + location(std::move(location)), input_location(std::move(input_location)), + fwd_segment_position(fwd_segment_position), forward_travel_mode(forward_travel_mode), + backward_travel_mode(backward_travel_mode) { } @@ -76,9 +76,8 @@ struct PhantomNode reverse_segment_id{SPECIAL_SEGMENTID, false}, name_id(std::numeric_limits::max()), forward_weight(INVALID_EDGE_WEIGHT), reverse_weight(INVALID_EDGE_WEIGHT), forward_offset(0), reverse_offset(0), - packed_geometry_id(SPECIAL_GEOMETRYID), - component{INVALID_COMPONENTID, false}, fwd_segment_position(0), - forward_travel_mode(TRAVEL_MODE_INACCESSIBLE), + packed_geometry_id(SPECIAL_GEOMETRYID), component{INVALID_COMPONENTID, false}, + fwd_segment_position(0), forward_travel_mode(TRAVEL_MODE_INACCESSIBLE), backward_travel_mode(TRAVEL_MODE_INACCESSIBLE) { } diff --git a/include/engine/routing_algorithms/routing_base.hpp b/include/engine/routing_algorithms/routing_base.hpp index 853f35567..35dffbcf6 100644 --- a/include/engine/routing_algorithms/routing_base.hpp +++ b/include/engine/routing_algorithms/routing_base.hpp @@ -6,6 +6,7 @@ #include "engine/internal_route_result.hpp" #include "engine/search_engine_data.hpp" #include "util/coordinate_calculation.hpp" +#include "util/guidance/turn_bearing.hpp" #include "util/typedefs.hpp" #include @@ -250,81 +251,83 @@ template class BasicRoutingInterface { id_vector = facade.GetUncompressedForwardGeometry(geometry_index.id); weight_vector = facade.GetUncompressedForwardWeights(geometry_index.id); - datasource_vector = - facade.GetUncompressedForwardDatasources(geometry_index.id); + datasource_vector = facade.GetUncompressedForwardDatasources(geometry_index.id); } else { id_vector = facade.GetUncompressedReverseGeometry(geometry_index.id); weight_vector = facade.GetUncompressedReverseWeights(geometry_index.id); - datasource_vector = - facade.GetUncompressedReverseDatasources(geometry_index.id); + datasource_vector = facade.GetUncompressedReverseDatasources(geometry_index.id); } BOOST_ASSERT(id_vector.size() > 0); BOOST_ASSERT(weight_vector.size() > 0); BOOST_ASSERT(datasource_vector.size() > 0); - const auto total_weight = - std::accumulate(weight_vector.begin(), weight_vector.end(), 0); + const auto total_weight = + std::accumulate(weight_vector.begin(), weight_vector.end(), 0); - BOOST_ASSERT(weight_vector.size() == id_vector.size() - 1); - const bool is_first_segment = unpacked_path.empty(); + BOOST_ASSERT(weight_vector.size() == id_vector.size() - 1); + const bool is_first_segment = unpacked_path.empty(); - const std::size_t start_index = - (is_first_segment - ? ((start_traversed_in_reverse) - ? weight_vector.size() - - phantom_node_pair.source_phantom.fwd_segment_position - 1 - : phantom_node_pair.source_phantom.fwd_segment_position) - : 0); - const std::size_t end_index = weight_vector.size(); + const std::size_t start_index = + (is_first_segment + ? ((start_traversed_in_reverse) + ? weight_vector.size() - + phantom_node_pair.source_phantom.fwd_segment_position - 1 + : phantom_node_pair.source_phantom.fwd_segment_position) + : 0); + const std::size_t end_index = weight_vector.size(); - BOOST_ASSERT(start_index >= 0); - BOOST_ASSERT(start_index < end_index); - for (std::size_t segment_idx = start_index; segment_idx < end_index; ++segment_idx) - { - unpacked_path.push_back( - PathData{id_vector[segment_idx + 1], - name_index, - weight_vector[segment_idx], - extractor::guidance::TurnInstruction::NO_TURN(), - {{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID}, - travel_mode, - INVALID_ENTRY_CLASSID, - datasource_vector[segment_idx]}); - } - BOOST_ASSERT(unpacked_path.size() > 0); - if (facade.hasLaneData(edge_data.id)) - unpacked_path.back().lane_data = facade.GetLaneData(edge_data.id); + BOOST_ASSERT(start_index >= 0); + BOOST_ASSERT(start_index < end_index); + for (std::size_t segment_idx = start_index; segment_idx < end_index; ++segment_idx) + { + unpacked_path.push_back( + PathData{id_vector[segment_idx + 1], + name_index, + weight_vector[segment_idx], + extractor::guidance::TurnInstruction::NO_TURN(), + {{0, INVALID_LANEID}, INVALID_LANE_DESCRIPTIONID}, + travel_mode, + INVALID_ENTRY_CLASSID, + datasource_vector[segment_idx], + util::guidance::TurnBearing(0), + util::guidance::TurnBearing(0)}); + } + BOOST_ASSERT(unpacked_path.size() > 0); + if (facade.hasLaneData(edge_data.id)) + unpacked_path.back().lane_data = facade.GetLaneData(edge_data.id); - unpacked_path.back().entry_classid = facade.GetEntryClassID(edge_data.id); - unpacked_path.back().turn_instruction = turn_instruction; - unpacked_path.back().duration_until_turn += (edge_data.distance - total_weight); - }); + unpacked_path.back().entry_classid = facade.GetEntryClassID(edge_data.id); + unpacked_path.back().turn_instruction = turn_instruction; + unpacked_path.back().duration_until_turn += (edge_data.distance - total_weight); + unpacked_path.back().pre_turn_bearing = facade.PreTurnBearing(edge_data.id); + unpacked_path.back().post_turn_bearing = facade.PostTurnBearing(edge_data.id); + }); - std::size_t start_index = 0, end_index = 0; - std::vector id_vector; - std::vector weight_vector; - std::vector datasource_vector; - const bool is_local_path = (phantom_node_pair.source_phantom.packed_geometry_id == - phantom_node_pair.target_phantom.packed_geometry_id) && - unpacked_path.empty(); + std::size_t start_index = 0, end_index = 0; + std::vector id_vector; + std::vector weight_vector; + std::vector datasource_vector; + const bool is_local_path = (phantom_node_pair.source_phantom.packed_geometry_id == + phantom_node_pair.target_phantom.packed_geometry_id) && + unpacked_path.empty(); - if (target_traversed_in_reverse) - { - id_vector = facade.GetUncompressedReverseGeometry( - phantom_node_pair.target_phantom.packed_geometry_id); + if (target_traversed_in_reverse) + { + id_vector = facade.GetUncompressedReverseGeometry( + phantom_node_pair.target_phantom.packed_geometry_id); - weight_vector = facade.GetUncompressedReverseWeights( - phantom_node_pair.target_phantom.packed_geometry_id); + weight_vector = facade.GetUncompressedReverseWeights( + phantom_node_pair.target_phantom.packed_geometry_id); - datasource_vector = facade.GetUncompressedReverseDatasources( - phantom_node_pair.target_phantom.packed_geometry_id); + datasource_vector = facade.GetUncompressedReverseDatasources( + phantom_node_pair.target_phantom.packed_geometry_id); if (is_local_path) { - start_index = - weight_vector.size() - phantom_node_pair.source_phantom.fwd_segment_position - 1; + start_index = weight_vector.size() - + phantom_node_pair.source_phantom.fwd_segment_position - 1; } end_index = weight_vector.size() - phantom_node_pair.target_phantom.fwd_segment_position - 1; @@ -354,7 +357,8 @@ template class BasicRoutingInterface // t: fwd_segment 3 // -> (U, v), (v, w), (w, x) // note that (x, t) is _not_ included but needs to be added later. - for (std::size_t segment_idx = start_index; segment_idx != end_index; (start_index < end_index ? ++segment_idx : --segment_idx)) + for (std::size_t segment_idx = start_index; segment_idx != end_index; + (start_index < end_index ? ++segment_idx : --segment_idx)) { BOOST_ASSERT(segment_idx < id_vector.size() - 1); BOOST_ASSERT(phantom_node_pair.target_phantom.forward_travel_mode > 0); @@ -367,7 +371,9 @@ template class BasicRoutingInterface target_traversed_in_reverse ? phantom_node_pair.target_phantom.backward_travel_mode : phantom_node_pair.target_phantom.forward_travel_mode, INVALID_ENTRY_CLASSID, - datasource_vector[segment_idx]}); + datasource_vector[segment_idx], + util::guidance::TurnBearing(0), + util::guidance::TurnBearing(0)}); } if (unpacked_path.size() > 0) diff --git a/include/extractor/edge_based_node.hpp b/include/extractor/edge_based_node.hpp index 5ad1e1992..bfd635b06 100644 --- a/include/extractor/edge_based_node.hpp +++ b/include/extractor/edge_based_node.hpp @@ -22,8 +22,7 @@ struct EdgeBasedNode EdgeBasedNode() : forward_segment_id{SPECIAL_SEGMENTID, false}, reverse_segment_id{SPECIAL_SEGMENTID, false}, u(SPECIAL_NODEID), v(SPECIAL_NODEID), - name_id(0), packed_geometry_id(SPECIAL_GEOMETRYID), - component{INVALID_COMPONENTID, false}, + name_id(0), packed_geometry_id(SPECIAL_GEOMETRYID), component{INVALID_COMPONENTID, false}, fwd_segment_position(std::numeric_limits::max()), forward_travel_mode(TRAVEL_MODE_INACCESSIBLE), backward_travel_mode(TRAVEL_MODE_INACCESSIBLE) diff --git a/include/extractor/guidance/constants.hpp b/include/extractor/guidance/constants.hpp index 520f44722..890b8dfad 100644 --- a/include/extractor/guidance/constants.hpp +++ b/include/extractor/guidance/constants.hpp @@ -34,7 +34,7 @@ const int constexpr MAX_SLIPROAD_THRESHOLD = 250; // Road priorities give an idea of how obvious a turn is. If two priorities differ greatly (e.g. // service road over a primary road, the better priority can be seen as obvious due to its road // category). -const double constexpr PRIORITY_DISTINCTION_FACTOR = 2.0; +const double constexpr PRIORITY_DISTINCTION_FACTOR = 1.75; } // namespace guidance } // namespace extractor diff --git a/include/extractor/guidance/coordinate_extractor.hpp b/include/extractor/guidance/coordinate_extractor.hpp new file mode 100644 index 000000000..348aac436 --- /dev/null +++ b/include/extractor/guidance/coordinate_extractor.hpp @@ -0,0 +1,203 @@ +#ifndef OSRM_EXTRACTOR_COORDINATE_EXTRACTOR_HPP_ +#define OSRM_EXTRACTOR_COORDINATE_EXTRACTOR_HPP_ + +#include + +#include "extractor/query_node.hpp" +#include "util/coordinate.hpp" +#include "util/coordinate_calculation.hpp" + +#include "extractor/compressed_edge_container.hpp" +#include "util/node_based_graph.hpp" + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +class CoordinateExtractor +{ + public: + CoordinateExtractor(const util::NodeBasedDynamicGraph &node_based_graph, + const extractor::CompressedEdgeContainer &compressed_geometries, + const std::vector &node_coordinates); + + /* Find a interpolated coordinate a long the compressed geometries. The desired coordinate + * should be in a certain distance. This method is dedicated to find representative coordinates + * at turns. + */ + util::Coordinate GetCoordinateAlongRoad(const NodeID intersection_node, + const EdgeID turn_edge, + const bool traversed_in_reverse, + const NodeID to_node, + const std::uint8_t number_of_in_lanes) const; + + // instead of finding only a single coordinate, we can also list all coordinates along a road. + std::vector GetCoordinatesAlongRoad(const NodeID intersection_node, + const EdgeID turn_edge, + const bool traversed_in_reverse, + const NodeID to_node) const; + + /* When extracting the coordinates, we first extract all coordinates. We don't care about most + * of them, though. + * + * Our very first step trims the coordinates to a saller set, close to the intersection.. The + * idea here is to filter all coordinates at the end of the road and consider only the formi + * close to the intersection: + * + * a -------------- v ----------. + * . + * . + * . + * b + * + * For calculating the turn angle for the intersection at `a`, we do not care about the turn + * between `v` and `b`. This calculation trims the coordinates to the ones immediately at the + * intersection. + */ + std::vector TrimCoordinatesToLength(std::vector coordinates, + const double desired_length) const; + + /* when looking at a set of coordinates, this function allows trimming the vector to a smaller, + * only containing coordinates up to a given distance along the path. The last coordinate might + * be interpolated + */ + std::vector + TrimCoordinatesByLengthFront(std::vector coordinates, + const double desired_length) const; + + /* + * to correct for the initial offset, we move the lookahead coordinate close + * to the original road. We do so by subtracting the difference between the + * turn coordinate and the offset coordinate from the lookahead coordinge: + * + * a ------ b ------ c + * | + * d + * \ + * \ + * e + * + * is converted to: + * + * a ------ b ------ c + * \ + * \ + * e + * + * for fixpoint `b`, vector_base `d` and vector_head `e` + */ + util::Coordinate GetCorrectedCoordinate(const util::Coordinate fixpoint, + const util::Coordinate vector_base, + const util::Coordinate vector_head) const; + + /* generate a uniform vector of coordinates in same range distances + * + * Turns: + * x ------------ x -- x - x + * + * Into: + * x -- x -- x -- x -- x - x + */ + std::vector + SampleCoordinates(const std::vector &coordinates, + const double length, + const double rate) const; + + private: + const util::NodeBasedDynamicGraph &node_based_graph; + const extractor::CompressedEdgeContainer &compressed_geometries; + const std::vector &node_coordinates; + + double ComputeInterpolationFactor(const double desired_distance, + const double distance_to_first, + const double distance_to_second) const; + + std::pair + RegressionLine(const std::vector &coordinates) const; + + /* In an ideal world, the road would only have two coordinates if it goes mainly straigt. Since + * OSM is operating on noisy data, we have some variations going straight. + * + * b d + * a ---------------------------------------------- e + * c + * + * The road from a-e offers a lot of variation, even though it is mostly straight. Here we + * calculate the distances of all nodes in between to the straight line between a and e. If the + * distances inbetween are small, we assume a straight road. To calculate these distances, we + * don't use the coordinates of the road itself but our just calculated regression vector + */ + double GetMaxDeviation(std::vector::const_iterator range_begin, + const std::vector::const_iterator &range_end, + const util::Coordinate straight_begin, + const util::Coordinate straight_end) const; + + /* + * the curve is still best described as looking at the very first vector for the turn angle. + * Consider: + * + * | + * a - 1 + * | o + * | 2 + * | o + * | 3 + * | o + * | 4 + * + * The turn itself from a-1 would be considered as a 90 degree turn, even though the road is + * taking a turn later. + * In this situaiton we return the very first coordinate, describing the road just at the + * turn. + * As an added benefit, we get a straight turn at a curved road: + * + * o b o + * o o + * o o + * o o + * o o + * a c + * + * The turn from a-b to b-c is straight. With every vector we go further down the road, the + * turn + * angle would get stronger. Therefore we consider the very first coordinate as our best + * choice + */ + bool IsCurve(const std::vector &coordinates, + const std::vector &segment_distances, + const double segment_length, + const double considered_lane_width, + const util::NodeBasedEdgeData &edge_data) const; + + /* + * If the very first coordinate is within lane offsets and the rest offers a near straight line, + * we use an offset coordinate. + * + * ---------------------------------------- + * + * ---------------------------------------- + * a - + * ---------------------------------------- + * \ + * ---------------------------------------- + * \ + * b --------------------c + * + * Will be considered a very slight turn, instead of the near 90 degree turn we see right here. + */ + bool IsDirectOffset(const std::vector &coordinates, + const std::size_t straight_index, + const double straight_distance, + const double segment_length, + const std::vector &segment_distances, + const std::uint8_t considered_lanes) const; +}; + +} // namespace guidance +} // namespace extractor +} // namespace osrm + +#endif // OSRM_EXTRACTOR_COORDINATE_EXTRACTOR_HPP_ diff --git a/include/extractor/guidance/intersection.hpp b/include/extractor/guidance/intersection.hpp index 940d26fe0..0cc5c4712 100644 --- a/include/extractor/guidance/intersection.hpp +++ b/include/extractor/guidance/intersection.hpp @@ -21,6 +21,7 @@ struct TurnOperation final { EdgeID eid; double angle; + double bearing; TurnInstruction instruction; LaneDataID lane_data_id; }; diff --git a/include/extractor/guidance/intersection_generator.hpp b/include/extractor/guidance/intersection_generator.hpp index a2b5b613d..1ef2d7c9a 100644 --- a/include/extractor/guidance/intersection_generator.hpp +++ b/include/extractor/guidance/intersection_generator.hpp @@ -2,6 +2,7 @@ #define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_GENERATOR_HPP_ #include "extractor/compressed_edge_container.hpp" +#include "extractor/guidance/coordinate_extractor.hpp" #include "extractor/guidance/intersection.hpp" #include "extractor/query_node.hpp" #include "extractor/restriction_map.hpp" @@ -51,7 +52,7 @@ class IntersectionGenerator const RestrictionMap &restriction_map; const std::unordered_set &barrier_nodes; const std::vector &node_info_list; - const CompressedEdgeContainer &compressed_edge_container; + const CoordinateExtractor coordinate_extractor; // Check for restrictions/barriers and generate a list of valid and invalid turns present at // the diff --git a/include/extractor/guidance/road_classification.hpp b/include/extractor/guidance/road_classification.hpp index 2b7434850..498b153e2 100644 --- a/include/extractor/guidance/road_classification.hpp +++ b/include/extractor/guidance/road_classification.hpp @@ -59,21 +59,24 @@ class RoadClassification // (difference <=1), we can see the road as a fork. Else one of the road classes is seen as // obvious choice RoadPriorityClass::Enum road_priority_class : 5; + // the number of lanes in the road + std::uint8_t number_of_lanes; public: // default construction RoadClassification() : motorway_class(0), link_class(0), may_be_ignored(1), - road_priority_class(RoadPriorityClass::CONNECTIVITY) + road_priority_class(RoadPriorityClass::CONNECTIVITY), number_of_lanes(0) { } RoadClassification(bool motorway_class, bool link_class, bool may_be_ignored, - RoadPriorityClass::Enum road_priority_class) + RoadPriorityClass::Enum road_priority_class, + std::uint8_t number_of_lanes) : motorway_class(motorway_class), link_class(link_class), may_be_ignored(may_be_ignored), - road_priority_class(road_priority_class) + road_priority_class(road_priority_class), number_of_lanes(number_of_lanes) { } @@ -88,6 +91,9 @@ class RoadClassification bool IsLowPriorityRoadClass() const { return (0 != may_be_ignored); } void SetLowPriorityFlag(const bool new_value) { may_be_ignored = new_value; } + std::uint8_t GetNumberOfLanes() const { return number_of_lanes; } + void SetNumberOfLanes(const std::uint8_t new_value) { number_of_lanes = new_value; } + std::uint32_t GetPriority() const { return static_cast(road_priority_class); } RoadPriorityClass::Enum GetClass() const { return road_priority_class; } @@ -112,8 +118,8 @@ class RoadClassification #pragma pack(pop) static_assert( - sizeof(RoadClassification) == 1, - "Road Classification should fit a byte. Increasing this has a severe impact on memory."); + sizeof(RoadClassification) == 2, + "Road Classification should fit two bytes. Increasing this has a severe impact on memory."); inline bool canBeSeenAsFork(const RoadClassification first, const RoadClassification second) { diff --git a/include/extractor/guidance/roundabout_handler.hpp b/include/extractor/guidance/roundabout_handler.hpp index 5fd33dbc1..5164c0cb9 100644 --- a/include/extractor/guidance/roundabout_handler.hpp +++ b/include/extractor/guidance/roundabout_handler.hpp @@ -2,6 +2,7 @@ #define OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_ #include "extractor/compressed_edge_container.hpp" +#include "extractor/guidance/coordinate_extractor.hpp" #include "extractor/guidance/intersection.hpp" #include "extractor/guidance/intersection_generator.hpp" #include "extractor/guidance/intersection_handler.hpp" @@ -87,6 +88,8 @@ class RoundaboutHandler : public IntersectionHandler const CompressedEdgeContainer &compressed_edge_container; const ProfileProperties &profile_properties; + + const CoordinateExtractor coordinate_extractor; }; } // namespace guidance diff --git a/include/extractor/guidance/toolkit.hpp b/include/extractor/guidance/toolkit.hpp index 6ee1c52a8..b21e7159e 100644 --- a/include/extractor/guidance/toolkit.hpp +++ b/include/extractor/guidance/toolkit.hpp @@ -7,12 +7,15 @@ #include "util/coordinate_calculation.hpp" #include "util/guidance/toolkit.hpp" #include "util/guidance/turn_lanes.hpp" +#include "util/node_based_graph.hpp" #include "util/typedefs.hpp" #include "extractor/compressed_edge_container.hpp" #include "extractor/query_node.hpp" +#include "extractor/guidance/constants.hpp" #include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/road_classification.hpp" #include "extractor/guidance/turn_instruction.hpp" #include "engine/guidance/route_step.hpp" @@ -24,6 +27,7 @@ #include #include #include +#include #include #include @@ -44,113 +48,6 @@ using util::guidance::angularDeviation; using util::guidance::entersRoundabout; using util::guidance::leavesRoundabout; -namespace detail -{ -const constexpr double DESIRED_SEGMENT_LENGTH = 10.0; - -template -util::Coordinate -getCoordinateFromCompressedRange(util::Coordinate current_coordinate, - const IteratorType compressed_geometry_begin, - const IteratorType compressed_geometry_end, - const util::Coordinate final_coordinate, - const std::vector &query_nodes) -{ - const auto extractCoordinateFromNode = - [](const extractor::QueryNode &node) -> util::Coordinate { - return {node.lon, node.lat}; - }; - double distance_to_current_coordinate = 0; - double distance_to_next_coordinate = 0; - - // get the length that is missing from the current segment to reach DESIRED_SEGMENT_LENGTH - const auto getFactor = [](const double first_distance, const double second_distance) { - BOOST_ASSERT(first_distance < detail::DESIRED_SEGMENT_LENGTH); - double segment_length = second_distance - first_distance; - BOOST_ASSERT(segment_length > 0); - BOOST_ASSERT(second_distance >= detail::DESIRED_SEGMENT_LENGTH); - double missing_distance = detail::DESIRED_SEGMENT_LENGTH - first_distance; - return std::max(0., std::min(missing_distance / segment_length, 1.0)); - }; - - for (auto compressed_geometry_itr = compressed_geometry_begin; - compressed_geometry_itr != compressed_geometry_end; - ++compressed_geometry_itr) - { - const auto next_coordinate = - extractCoordinateFromNode(query_nodes[compressed_geometry_itr->node_id]); - distance_to_next_coordinate = - distance_to_current_coordinate + - util::coordinate_calculation::haversineDistance(current_coordinate, next_coordinate); - - // reached point where coordinates switch between - if (distance_to_next_coordinate >= detail::DESIRED_SEGMENT_LENGTH) - return util::coordinate_calculation::interpolateLinear( - getFactor(distance_to_current_coordinate, distance_to_next_coordinate), - current_coordinate, - next_coordinate); - - // prepare for next iteration - current_coordinate = next_coordinate; - distance_to_current_coordinate = distance_to_next_coordinate; - } - - distance_to_next_coordinate = - distance_to_current_coordinate + - util::coordinate_calculation::haversineDistance(current_coordinate, final_coordinate); - - // reached point where coordinates switch between - if (distance_to_current_coordinate < detail::DESIRED_SEGMENT_LENGTH && - distance_to_next_coordinate >= detail::DESIRED_SEGMENT_LENGTH) - return util::coordinate_calculation::interpolateLinear( - getFactor(distance_to_current_coordinate, distance_to_next_coordinate), - current_coordinate, - final_coordinate); - else - return final_coordinate; -} -} // namespace detail - -// Finds a (potentially interpolated) coordinate that is DESIRED_SEGMENT_LENGTH away -// from the start of an edge -inline util::Coordinate -getRepresentativeCoordinate(const NodeID from_node, - const NodeID to_node, - const EdgeID via_edge_id, - const bool traverse_in_reverse, - const extractor::CompressedEdgeContainer &compressed_geometries, - const std::vector &query_nodes) -{ - const auto extractCoordinateFromNode = - [](const extractor::QueryNode &node) -> util::Coordinate { - return {node.lon, node.lat}; - }; - - // Uncompressed roads are simple, return the coordinate at the end - if (!compressed_geometries.HasZippedEntryForForwardID(via_edge_id) && !compressed_geometries.HasZippedEntryForReverseID(via_edge_id)) - { - return extractCoordinateFromNode(traverse_in_reverse ? query_nodes[from_node] - : query_nodes[to_node]); - } - else - { - const auto &geometry = compressed_geometries.GetBucketReference(via_edge_id); - - const auto base_node_id = (traverse_in_reverse) ? to_node : from_node; - const auto base_coordinate = extractCoordinateFromNode(query_nodes[base_node_id]); - - const auto final_node = (traverse_in_reverse) ? from_node : to_node; - const auto final_coordinate = extractCoordinateFromNode(query_nodes[final_node]); - - if (traverse_in_reverse) - return detail::getCoordinateFromCompressedRange( - base_coordinate, geometry.rbegin(), geometry.rend(), final_coordinate, query_nodes); - else - return detail::getCoordinateFromCompressedRange( - base_coordinate, geometry.begin(), geometry.end(), final_coordinate, query_nodes); - } -} - // To simplify handling of Left/Right hand turns, we can mirror turns and write an intersection // handler only for one side. The mirror function turns a left-hand turn in a equivalent right-hand // turn and vice versa. @@ -313,6 +210,78 @@ auto inline lanesToTheRight(const engine::guidance::RouteStep &step) return boost::make_iterator_range(description.end() - num_lanes_right, description.end()); } +inline std::uint8_t getLaneCountAtIntersection(const NodeID intersection_node, + const util::NodeBasedDynamicGraph &node_based_graph) +{ + std::uint8_t lanes = 0; + for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(intersection_node)) + lanes = std::max( + lanes, node_based_graph.GetEdgeData(onto_edge).road_classification.GetNumberOfLanes()); + return lanes; +} + +inline bool obviousByRoadClass(const RoadClassification in_classification, + const RoadClassification obvious_candidate, + const RoadClassification compare_candidate) +{ + const bool has_high_priority = PRIORITY_DISTINCTION_FACTOR * obvious_candidate.GetPriority() < + compare_candidate.GetPriority(); + + const bool continues_on_same_class = in_classification == obvious_candidate; + return (has_high_priority && continues_on_same_class) || + (!obvious_candidate.IsLowPriorityRoadClass() && + !in_classification.IsLowPriorityRoadClass() && + compare_candidate.IsLowPriorityRoadClass()); +} + +/* We use the sum of least squares to calculate a linear regression through our +* coordinates. +* This regression gives a good idea of how the road can be perceived and corrects for +* initial and final corrections +*/ +inline std::pair +leastSquareRegression(const std::vector &coordinates) +{ + BOOST_ASSERT(coordinates.size() >= 2); + double sum_lon = 0, sum_lat = 0, sum_lon_lat = 0, sum_lon_lon = 0; + double min_lon = static_cast(toFloating(coordinates.front().lon)); + double max_lon = static_cast(toFloating(coordinates.front().lon)); + for (const auto coord : coordinates) + { + min_lon = std::min(min_lon, static_cast(toFloating(coord.lon))); + max_lon = std::max(max_lon, static_cast(toFloating(coord.lon))); + sum_lon += static_cast(toFloating(coord.lon)); + sum_lon_lon += + static_cast(toFloating(coord.lon)) * static_cast(toFloating(coord.lon)); + sum_lat += static_cast(toFloating(coord.lat)); + sum_lon_lat += + static_cast(toFloating(coord.lon)) * static_cast(toFloating(coord.lat)); + } + + const auto dividend = coordinates.size() * sum_lon_lat - sum_lon * sum_lat; + const auto divisor = coordinates.size() * sum_lon_lon - sum_lon * sum_lon; + if (std::abs(divisor) < std::numeric_limits::epsilon()) + return std::make_pair(coordinates.front(), coordinates.back()); + + // slope of the regression line + const auto slope = dividend / divisor; + const auto intercept = (sum_lat - slope * sum_lon) / coordinates.size(); + + const auto GetLatAtLon = [intercept, + slope](const util::FloatLongitude longitude) -> util::FloatLatitude { + return {intercept + slope * static_cast((longitude))}; + }; + + const util::Coordinate regression_first = { + toFixed(util::FloatLongitude{min_lon - 1}), + toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{min_lon - 1})))}; + const util::Coordinate regression_end = { + toFixed(util::FloatLongitude{max_lon + 1}), + toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{max_lon + 1})))}; + + return {regression_first, regression_end}; +} + } // namespace guidance } // namespace extractor } // namespace osrm diff --git a/include/extractor/guidance/turn_classification.hpp b/include/extractor/guidance/turn_classification.hpp index 6c8b46754..4489dfab9 100644 --- a/include/extractor/guidance/turn_classification.hpp +++ b/include/extractor/guidance/turn_classification.hpp @@ -25,11 +25,7 @@ namespace guidance { std::pair -classifyIntersection(NodeID nid, - const Intersection &intersection, - const util::NodeBasedDynamicGraph &node_based_graph, - const extractor::CompressedEdgeContainer &compressed_geometries, - const std::vector &query_nodes); +classifyIntersection(Intersection intersection); } // namespace guidance } // namespace extractor diff --git a/include/extractor/guidance/turn_instruction.hpp b/include/extractor/guidance/turn_instruction.hpp index a32516013..e64296070 100644 --- a/include/extractor/guidance/turn_instruction.hpp +++ b/include/extractor/guidance/turn_instruction.hpp @@ -6,7 +6,6 @@ #include #include "extractor/guidance/roundabout_type.hpp" -#include "util/guidance/turn_lanes.hpp" #include "util/typedefs.hpp" namespace osrm @@ -68,10 +67,8 @@ const constexpr Enum Sliproad = const constexpr Enum MaxTurnType = 27; // Special value for static asserts } -// turn angle in 1.40625 degree -> 128 == 180 degree struct TurnInstruction { - using LaneTuple = util::guidance::LaneTuple; TurnInstruction(const TurnType::Enum type = TurnType::Invalid, const DirectionModifier::Enum direction_modifier = DirectionModifier::UTurn) : type(type), direction_modifier(direction_modifier) diff --git a/include/extractor/original_edge_data.hpp b/include/extractor/original_edge_data.hpp index 9a2778aaa..a8303a3bd 100644 --- a/include/extractor/original_edge_data.hpp +++ b/include/extractor/original_edge_data.hpp @@ -3,6 +3,7 @@ #include "extractor/guidance/turn_instruction.hpp" #include "extractor/travel_mode.hpp" +#include "util/guidance/turn_bearing.hpp" #include "util/typedefs.hpp" #include @@ -20,9 +21,12 @@ struct OriginalEdgeData LaneDataID lane_data_id, guidance::TurnInstruction turn_instruction, EntryClassID entry_classid, - TravelMode travel_mode) + TravelMode travel_mode, + util::guidance::TurnBearing pre_turn_bearing, + util::guidance::TurnBearing post_turn_bearing) : via_geometry(via_geometry), name_id(name_id), entry_classid(entry_classid), - lane_data_id(lane_data_id), turn_instruction(turn_instruction), travel_mode(travel_mode) + lane_data_id(lane_data_id), turn_instruction(turn_instruction), travel_mode(travel_mode), + pre_turn_bearing(pre_turn_bearing), post_turn_bearing(post_turn_bearing) { } @@ -30,16 +34,18 @@ struct OriginalEdgeData : via_geometry{std::numeric_limits::max() >> 1, false}, name_id(std::numeric_limits::max()), entry_classid(INVALID_ENTRY_CLASSID), lane_data_id(INVALID_LANE_DATAID), turn_instruction(guidance::TurnInstruction::INVALID()), - travel_mode(TRAVEL_MODE_INACCESSIBLE) + travel_mode(TRAVEL_MODE_INACCESSIBLE), pre_turn_bearing(0.0), post_turn_bearing(0.0) { } GeometryID via_geometry; - unsigned name_id; + NameID name_id; EntryClassID entry_classid; LaneDataID lane_data_id; guidance::TurnInstruction turn_instruction; TravelMode travel_mode; + util::guidance::TurnBearing pre_turn_bearing; + util::guidance::TurnBearing post_turn_bearing; }; static_assert(sizeof(OriginalEdgeData) == 16, diff --git a/include/storage/shared_datatype.hpp b/include/storage/shared_datatype.hpp index daedc4eb8..1e01f5713 100644 --- a/include/storage/shared_datatype.hpp +++ b/include/storage/shared_datatype.hpp @@ -46,6 +46,8 @@ const constexpr char *block_id_to_name[] = {"NAME_OFFSETS", "BEARING_VALUES", "ENTRY_CLASS", "LANE_DATA_ID", + "PRE_TURN_BEARING", + "POST_TURN_BEARING", "TURN_LANE_DATA", "LANE_DESCRIPTION_OFFSETS", "LANE_DESCRIPTION_MASKS"}; @@ -84,6 +86,8 @@ struct SharedDataLayout BEARING_VALUES, ENTRY_CLASS, LANE_DATA_ID, + PRE_TURN_BEARING, + POST_TURN_BEARING, TURN_LANE_DATA, LANE_DESCRIPTION_OFFSETS, LANE_DESCRIPTION_MASKS, diff --git a/include/util/coordinate_calculation.hpp b/include/util/coordinate_calculation.hpp index 61fa7bf16..8015f2262 100644 --- a/include/util/coordinate_calculation.hpp +++ b/include/util/coordinate_calculation.hpp @@ -30,6 +30,13 @@ double haversineDistance(const Coordinate first_coordinate, const Coordinate sec double greatCircleDistance(const Coordinate first_coordinate, const Coordinate second_coordinate); +// Find the closest distance and location between coordinate and the line connecting source and +// target: +// coordinate +// | +// | +// source -------- x -------- target. +// returns x as well as the distance between source and x as ratio ([0,1]) inline std::pair projectPointOnSegment(const FloatCoordinate &source, const FloatCoordinate &target, const FloatCoordinate &coordinate) @@ -99,6 +106,16 @@ double circleRadius(const Coordinate first_coordinate, // returns to Coordinate interpolateLinear(double factor, const Coordinate from, const Coordinate to); +// compute the signed area of a triangle +double signedArea(const Coordinate first_coordinate, + const Coordinate second_coordinate, + const Coordinate third_coordinate); + +// check if a set of three coordinates is given in CCW order +bool isCCW(const Coordinate first_coordinate, + const Coordinate second_coordinate, + const Coordinate third_coordinate); + } // ns coordinate_calculation } // ns util } // ns osrm diff --git a/include/util/debug.hpp b/include/util/debug.hpp index 28cfde220..aa5b2ec4f 100644 --- a/include/util/debug.hpp +++ b/include/util/debug.hpp @@ -10,12 +10,15 @@ #include #include +#include +#include #include namespace osrm { namespace util { + namespace guidance { inline void print(const engine::guidance::RouteStep &step) @@ -30,7 +33,9 @@ inline void print(const engine::guidance::RouteStep &step) for (const auto &intersection : step.intersections) { std::cout << "(Lanes: " << static_cast(intersection.lanes.lanes_in_turn) << " " - << static_cast(intersection.lanes.first_lane_from_the_right) << " bearings:"; + << static_cast(intersection.lanes.first_lane_from_the_right) << " [" + << intersection.in << "," << intersection.out << "]" + << " bearings:"; for (auto bearing : intersection.bearings) std::cout << " " << bearing; std::cout << ", entry: "; diff --git a/include/util/guidance/toolkit.hpp b/include/util/guidance/toolkit.hpp index b7ca55c96..67cc203e6 100644 --- a/include/util/guidance/toolkit.hpp +++ b/include/util/guidance/toolkit.hpp @@ -32,6 +32,12 @@ inline double angularDeviation(const double angle, const double from) return std::min(360 - deviation, deviation); } +inline bool hasRampType(const extractor::guidance::TurnInstruction instruction) +{ + return instruction.type == extractor::guidance::TurnType::OffRamp || + instruction.type == extractor::guidance::TurnType::OnRamp; +} + inline extractor::guidance::DirectionModifier::Enum getTurnDirection(const double angle) { // An angle of zero is a u-turn diff --git a/include/util/guidance/turn_bearing.hpp b/include/util/guidance/turn_bearing.hpp new file mode 100644 index 000000000..87b4f2a3b --- /dev/null +++ b/include/util/guidance/turn_bearing.hpp @@ -0,0 +1,30 @@ +#ifndef OSRM_INCLUDE_UTIL_TURN_BEARING_HPP_ +#define OSRM_INCLUDE_UTIL_TURN_BEARING_HPP_ + +#include + +namespace osrm +{ +namespace util +{ +namespace guidance +{ + +#pragma pack(push, 1) +class TurnBearing +{ + public: + TurnBearing(const double value = 0); + + double Get() const; + + private: + std::uint8_t bearing; +}; +#pragma pack(pop) + +} // namespace guidance +} // namespace util +} // namespace osrm + +#endif /* OSRM_INCLUDE_UTIL_TURN_BEARING_HPP_ */ diff --git a/include/util/shared_memory_vector_wrapper.hpp b/include/util/shared_memory_vector_wrapper.hpp index f4a85dbbc..2e31f0b8b 100644 --- a/include/util/shared_memory_vector_wrapper.hpp +++ b/include/util/shared_memory_vector_wrapper.hpp @@ -46,7 +46,8 @@ template class ShMemIterator : public std::iterator class ShMemReverseIterator : public std::iterator +template +class ShMemReverseIterator : public std::iterator { DataT *p; @@ -99,7 +100,10 @@ template class SharedMemoryWrapper ShMemIterator end() const { return ShMemIterator(m_ptr + m_size); } - ShMemReverseIterator rbegin() const { return ShMemReverseIterator(m_ptr + m_size - 1); } + ShMemReverseIterator rbegin() const + { + return ShMemReverseIterator(m_ptr + m_size - 1); + } ShMemReverseIterator rend() const { return ShMemReverseIterator(m_ptr - 1); } diff --git a/include/util/typedefs.hpp b/include/util/typedefs.hpp index b8cb0b4fa..ce5dbd3c5 100644 --- a/include/util/typedefs.hpp +++ b/include/util/typedefs.hpp @@ -112,7 +112,6 @@ struct GeometryID std::uint32_t forward : 1; }; - static_assert(sizeof(SegmentID) == 4, "SegmentID needs to be 4 bytes big"); #endif /* TYPEDEFS_H */ diff --git a/profiles/bicycle.lua b/profiles/bicycle.lua index 9e68365e8..c49a11381 100644 --- a/profiles/bicycle.lua +++ b/profiles/bicycle.lua @@ -395,7 +395,7 @@ function way_function (way, result) end -- set the road classification based on guidance globals configuration - set_classification(highway,result) + set_classification(highway,result,way) -- maxspeed limit( result, maxspeed, maxspeed_forward, maxspeed_backward ) diff --git a/profiles/car.lua b/profiles/car.lua index b8a56636d..161199aba 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -425,7 +425,7 @@ function way_function (way, result) end -- set the road classification based on guidance globals configuration - set_classification(highway,result) + set_classification(highway,result,way) -- parse the remaining tags local name = way:get_value_by_key("name") diff --git a/profiles/lib/guidance.lua b/profiles/lib/guidance.lua index 882567d70..d867fd56e 100644 --- a/profiles/lib/guidance.lua +++ b/profiles/lib/guidance.lua @@ -42,7 +42,7 @@ road_types = { ["motorway"] = true, link_types = { ["motorway_link"] = true, ["trunk_link"] = true, ["primary_link"] = true, ["secondary_link"] = true, ["tertiary_link"] = true } -function Guidance.set_classification (highway, result) +function Guidance.set_classification (highway, result, input_way) if motorway_types[highway] then result.road_classification.motorway_class = true; end @@ -59,6 +59,33 @@ function Guidance.set_classification (highway, result) else result.road_classification.may_be_ignored = true; end + + local lane_count = input_way:get_value_by_key("lanes") + if lane_count and lane_count ~= "" then + local lc = tonumber(lane_count) + if lane_count ~= nil then + result.road_classification.num_lanes = lc + end + else + local total_count = 0 + local forward_count = input_way:get_value_by_key("lanes:forward") + if forward_count and forward_count ~= "" then + local fc = tonumber(forward_count) + if fc ~= nil then + total_count = fc + end + end + local backward_count = input_way:get_value_by_key("lanes:backward") + if backward_count and backward_count ~= "" then + local bc = tonumber(backward_count) + if bc ~= nil then + total_count = total_count + bc + end + end + if total_count ~= 0 then + result.road_classification.num_lanes = total_count + end + end end -- returns forward,backward psv lane count @@ -69,19 +96,19 @@ local function get_psv_counts(way) local fw = 0; local bw = 0; - if( psv and psv ~= "" ) then + if psv and psv ~= "" then fw = tonumber(psv) if( fw == nil ) then fw = 0 end - end - if( psv_forward and psv_forward ~= "" ) then + 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 + end + if psv_backward and psv_backward ~= "" then bw = tonumber(psv_backward); if( bw == nil ) then bw = 0 diff --git a/src/contractor/contractor.cpp b/src/contractor/contractor.cpp index efa114b81..b5bd850ad 100644 --- a/src/contractor/contractor.cpp +++ b/src/contractor/contractor.cpp @@ -598,10 +598,10 @@ EdgeID Contractor::LoadEdgeExpandedGraph( const auto current_segment = &(m_geometry_list[forward_begin + leaf_object.fwd_segment_position]); - u = &(internal_to_external_node_map - [m_geometry_list[forward_begin + - leaf_object.fwd_segment_position] - .node_id]); + u = &( + internal_to_external_node_map[m_geometry_list[forward_begin + + leaf_object.fwd_segment_position] + .node_id]); v = &(internal_to_external_node_map [m_geometry_list[forward_begin + leaf_object.fwd_segment_position + 1] .node_id]); @@ -619,8 +619,8 @@ EdgeID Contractor::LoadEdgeExpandedGraph( current_segment->forward_weight, log_edge_updates_factor); - m_geometry_list[forward_begin + 1 + leaf_object.fwd_segment_position].forward_weight = - new_segment_weight; + m_geometry_list[forward_begin + 1 + leaf_object.fwd_segment_position] + .forward_weight = new_segment_weight; m_geometry_datasource[forward_begin + 1 + leaf_object.fwd_segment_position] = forward_speed_iter->speed_source.source; @@ -642,8 +642,8 @@ EdgeID Contractor::LoadEdgeExpandedGraph( segment_speed_filenames, current_segment->reverse_weight, log_edge_updates_factor); - m_geometry_list[forward_begin + leaf_object.fwd_segment_position].reverse_weight = - new_segment_weight; + m_geometry_list[forward_begin + leaf_object.fwd_segment_position] + .reverse_weight = new_segment_weight; m_geometry_datasource[forward_begin + leaf_object.fwd_segment_position] = reverse_speed_iter->speed_source.source; diff --git a/src/engine/guidance/assemble_steps.cpp b/src/engine/guidance/assemble_steps.cpp index dc75fc93c..308b68a77 100644 --- a/src/engine/guidance/assemble_steps.cpp +++ b/src/engine/guidance/assemble_steps.cpp @@ -13,22 +13,6 @@ namespace guidance { namespace detail { -std::pair getIntermediateBearings(const LegGeometry &leg_geometry, - const std::size_t segment_index) -{ - auto turn_index = leg_geometry.BackIndex(segment_index); - BOOST_ASSERT(turn_index > 0); - BOOST_ASSERT(turn_index + 1 < leg_geometry.locations.size()); - - // TODO chose a bigger look-a-head to smooth complex geometry - const auto pre_turn_coordinate = leg_geometry.locations[turn_index - 1]; - const auto turn_coordinate = leg_geometry.locations[turn_index]; - const auto post_turn_coordinate = leg_geometry.locations[turn_index + 1]; - - return std::make_pair( - std::round(util::coordinate_calculation::bearing(pre_turn_coordinate, turn_coordinate)), - std::round(util::coordinate_calculation::bearing(turn_coordinate, post_turn_coordinate))); -} std::pair getDepartBearings(const LegGeometry &leg_geometry) { diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index 60bf7feb5..c3ff15da3 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -1,6 +1,6 @@ +#include "engine/guidance/post_processing.hpp" #include "extractor/guidance/toolkit.hpp" #include "extractor/guidance/turn_instruction.hpp" -#include "engine/guidance/post_processing.hpp" #include "engine/guidance/assemble_steps.hpp" #include "engine/guidance/lane_processing.hpp" @@ -42,8 +42,10 @@ const constexpr double MAX_COLLAPSE_DISTANCE = 30; // check if at least one of the turns is actually a maneuver inline bool hasManeuver(const RouteStep &first, const RouteStep &second) { - return first.maneuver.instruction.type != TurnType::Suppressed || - second.maneuver.instruction.type != TurnType::Suppressed; + return (first.maneuver.instruction.type != TurnType::Suppressed || + second.maneuver.instruction.type != TurnType::Suppressed) && + (first.maneuver.instruction.type != TurnType::NoTurn && + second.maneuver.instruction.type != TurnType::NoTurn); } // forward all signage/name data from one step to another. @@ -67,6 +69,7 @@ inline bool choiceless(const RouteStep &step, const RouteStep &previous) 1 >= std::count(step.intersections.front().entry.begin(), step.intersections.front().entry.end(), true); + return is_without_choice; } @@ -330,8 +333,9 @@ void closeOffRoundabout(const bool on_roundabout, entry_intersection.bearings[entry_intersection.in]), exit_bearing); + auto bearings = propagation_step.intersections.front().bearings; propagation_step.maneuver.instruction.direction_modifier = - ::osrm::util::guidance::getTurnDirection(angle); + util::guidance::getTurnDirection(angle); } forwardStepSignage(propagation_step, destination_copy); @@ -347,6 +351,142 @@ void closeOffRoundabout(const bool on_roundabout, } } +bool bearingsAreReversed(const double bearing_in, const double bearing_out) +{ + // Nearly perfectly reversed angles have a difference close to 180 degrees (straight) + const double left_turn_angle = [&]() { + if (0 <= bearing_out && bearing_out <= bearing_in) + return bearing_in - bearing_out; + return bearing_in + 360 - bearing_out; + }(); + return angularDeviation(left_turn_angle, 180) <= 35; +} + +bool isLinkroad(const RouteStep &step) +{ + const constexpr double MAX_LINK_ROAD_LENGTH = 60.0; + return step.distance <= MAX_LINK_ROAD_LENGTH && step.name_id == EMPTY_NAMEID; +} + +bool isUTurn(const RouteStep &in_step, const RouteStep &out_step, const RouteStep &pre_in_step) +{ + const bool is_possible_candidate = in_step.distance <= MAX_COLLAPSE_DISTANCE || + choiceless(out_step, in_step) || + (isLinkroad(in_step) && out_step.name_id != EMPTY_NAMEID && + pre_in_step.name_id == out_step.name_id); + const bool takes_u_turn = bearingsAreReversed( + util::bearing::reverseBearing( + in_step.intersections.front().bearings[in_step.intersections.front().in]), + out_step.intersections.front().bearings[out_step.intersections.front().out]); + + return is_possible_candidate && takes_u_turn && compatible(in_step, out_step); +} + +double findTotalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_step) +{ + const auto exit_intersection = exit_step.intersections.front(); + const auto exit_step_exit_bearing = exit_intersection.bearings[exit_intersection.out]; + const auto exit_step_entry_bearing = + util::bearing::reverseBearing(exit_intersection.bearings[exit_intersection.in]); + + const auto entry_intersection = entry_step.intersections.front(); + const auto entry_step_entry_bearing = + util::bearing::reverseBearing(entry_intersection.bearings[entry_intersection.in]); + const auto entry_step_exit_bearing = entry_intersection.bearings[entry_intersection.out]; + + const auto exit_angle = turn_angle(exit_step_entry_bearing, exit_step_exit_bearing); + const auto entry_angle = turn_angle(entry_step_entry_bearing, entry_step_exit_bearing); + + const double total_angle = turn_angle(entry_step_entry_bearing, exit_step_exit_bearing); + // We allow for minor deviations from a straight line + if (((entry_step.distance < MAX_COLLAPSE_DISTANCE && exit_step.intersections.size() == 1) || + (entry_angle <= 185 && exit_angle <= 185) || (entry_angle >= 175 && exit_angle >= 175)) && + angularDeviation(total_angle, 180) > 20) + { + // both angles are in the same direction, the total turn gets increased + // + // a ---- b + // \ + // c + // | + // d + // + // Will be considered just like + // a -----b + // | + // c + // | + // d + return total_angle; + } + else + { + // to prevent ignoring angles like + // a -- b + // | + // c -- d + // We don't combine both turn angles here but keep the very first turn angle. + // We choose the first one, since we consider the first maneuver in a merge range the + // important one + return entry_angle; + } +} + +std::size_t getPreviousIndex(std::size_t index, const std::vector &steps) +{ + BOOST_ASSERT(index > 0); + BOOST_ASSERT(index < steps.size()); + --index; + while (index > 0 && steps[index].maneuver.instruction.type == TurnType::NoTurn) + --index; + + return index; +}; + +void collapseUTurn(std::vector &steps, + const std::size_t two_back_index, + const std::size_t one_back_index, + const std::size_t step_index) +{ + BOOST_ASSERT(two_back_index < steps.size()); + BOOST_ASSERT(step_index < steps.size()); + BOOST_ASSERT(one_back_index < steps.size()); + const auto ¤t_step = steps[step_index]; + + // the simple case is a u-turn that changes directly into the in-name again + const bool direct_u_turn = !isNoticeableNameChange(steps[two_back_index], current_step); + + // however, we might also deal with a dual-collapse scenario in which we have to + // additionall collapse a name-change as welll + const auto next_step_index = step_index + 1; + const bool continues_with_name_change = + (next_step_index < steps.size()) && + ((steps[next_step_index].maneuver.instruction.type == TurnType::UseLane && + steps[next_step_index].maneuver.instruction.direction_modifier == + DirectionModifier::Straight) || + isCollapsableInstruction(steps[next_step_index].maneuver.instruction)); + const bool u_turn_with_name_change = + continues_with_name_change && steps[next_step_index].name_id != EMPTY_NAMEID && + !isNoticeableNameChange(steps[two_back_index], steps[next_step_index]); + + if (direct_u_turn || u_turn_with_name_change) + { + steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); + invalidateStep(steps[step_index]); + if (u_turn_with_name_change) + { + steps[one_back_index] = + elongate(std::move(steps[one_back_index]), steps[next_step_index]); + invalidateStep(steps[next_step_index]); // will be skipped due to the + // continue statement at the + // beginning of this function + } + forwardStepSignage(steps[one_back_index], steps[two_back_index]); + steps[one_back_index].maneuver.instruction.type = TurnType::Continue; + steps[one_back_index].maneuver.instruction.direction_modifier = DirectionModifier::UTurn; + } +} + void collapseTurnAt(std::vector &steps, const std::size_t two_back_index, const std::size_t one_back_index, @@ -357,45 +497,81 @@ void collapseTurnAt(std::vector &steps, const auto ¤t_step = steps[step_index]; const auto &one_back_step = steps[one_back_index]; - // FIXME: 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) - 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; - }; - + // This function assumes driving on the right hand side of the streat BOOST_ASSERT(!one_back_step.intersections.empty() && !current_step.intersections.empty()); if (!hasManeuver(one_back_step, current_step)) return; - // Very Short New Name - if (((collapsable(one_back_step, current_step) || - (isCollapsableInstruction(one_back_step.maneuver.instruction) && - choiceless(current_step, one_back_step))) && - !(one_back_step.maneuver.instruction.type == TurnType::Merge))) - // the check against merge is a workaround for motorways + // A maneuver is preceded by a name change if the instruction just before can be collapsed + // normally or the instruction itself is collapsable and does not actually present a choice + const auto maneuverPrecededByNameChange = [](const RouteStep &turning_point, + const RouteStep &possible_name_change_location, + const RouteStep &preceeding_step) { + // the check against merge is a workaround for motorways + if (possible_name_change_location.maneuver.instruction.type == TurnType::Merge || + !compatible(possible_name_change_location, preceeding_step)) + return false; + + return collapsable(possible_name_change_location, turning_point) || + (isCollapsableInstruction(possible_name_change_location.maneuver.instruction) && + choiceless(possible_name_change_location, preceeding_step)); + }; + + // check if the actual turn we wan't to announce is delayed. This situation describes a turn + // that is expressed by two turns, + const auto isDelayedTurn = [](const RouteStep &openining_turn, + const RouteStep &finishing_turn) { + // only possible if both are compatible + if (!compatible(openining_turn, finishing_turn)) + return false; + else + { + const auto is_short_and_collapsable = + openining_turn.distance <= MAX_COLLAPSE_DISTANCE && + isCollapsableInstruction(finishing_turn.maneuver.instruction); + + const auto without_choice = choiceless(finishing_turn, openining_turn); + + const auto is_not_too_long_and_choiceless = + openining_turn.distance <= 2 * MAX_COLLAPSE_DISTANCE && without_choice; + + // for ramps we allow longer stretches, since they are often on some major brides/large + // roads. A combined distance of of 4 intersections would be to long for a normal + // collapse. In case of a ramp though, we also account for situations that have the ramp + // tagged late + const auto is_delayed_turn_onto_a_ramp = + openining_turn.distance <= 4 * MAX_COLLAPSE_DISTANCE && without_choice && + util::guidance::hasRampType(finishing_turn.maneuver.instruction); + return !util::guidance::hasRampType(openining_turn.maneuver.instruction) && + (is_short_and_collapsable || is_not_too_long_and_choiceless || + isLinkroad(openining_turn) || is_delayed_turn_onto_a_ramp); + } + }; + + // Handle possible u-turns + if (isUTurn(one_back_step, current_step, steps[two_back_index])) + collapseUTurn(steps, two_back_index, one_back_index, step_index); + // Very Short New Name that will be suppressed. Turn location remains at current_step + else if (maneuverPrecededByNameChange(current_step, one_back_step, steps[two_back_index])) { BOOST_ASSERT(two_back_index < steps.size()); - if (compatible(one_back_step, steps[two_back_index])) + BOOST_ASSERT(!one_back_step.intersections.empty()); + if (TurnType::Merge == current_step.maneuver.instruction.type) + { + steps[step_index].maneuver.instruction.direction_modifier = + util::guidance::mirrorDirectionModifier( + steps[step_index].maneuver.instruction.direction_modifier); + steps[step_index].maneuver.instruction.type = TurnType::Turn; + } + else { - BOOST_ASSERT(!one_back_step.intersections.empty()); if (TurnType::Continue == current_step.maneuver.instruction.type || (TurnType::Suppressed == current_step.maneuver.instruction.type && current_step.maneuver.instruction.direction_modifier != DirectionModifier::Straight)) steps[step_index].maneuver.instruction.type = TurnType::Turn; - else if (TurnType::Merge == current_step.maneuver.instruction.type) - { - steps[step_index].maneuver.instruction.direction_modifier = - util::guidance::mirrorDirectionModifier( - steps[step_index].maneuver.instruction.direction_modifier); - steps[step_index].maneuver.instruction.type = TurnType::Turn; - } + else if (TurnType::NewName == current_step.maneuver.instruction.type && current_step.maneuver.instruction.direction_modifier != DirectionModifier::Straight && @@ -407,111 +583,100 @@ void collapseTurnAt(std::vector &steps, 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 - invalidateStep(steps[one_back_index]); + const auto total_angle = findTotalTurnAngle(steps[one_back_index], current_step); + steps[step_index].maneuver.instruction.direction_modifier = + getTurnDirection(total_angle); } + + steps[two_back_index] = 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 + invalidateStep(steps[one_back_index]); } - // very short segment after turn - else if (one_back_step.distance <= MAX_COLLAPSE_DISTANCE && - isCollapsableInstruction(current_step.maneuver.instruction)) + // very short segment after turn, turn location remains at one_back_step + else if (isDelayedTurn(one_back_step, current_step)) { + steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); // TODO check for lanes (https://github.com/Project-OSRM/osrm-backend/issues/2553) - if (compatible(one_back_step, current_step)) + if (TurnType::Continue == one_back_step.maneuver.instruction.type && + isNoticeableNameChange(steps[two_back_index], current_step)) { - steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); - - if ((TurnType::Continue == one_back_step.maneuver.instruction.type || - TurnType::Suppressed == one_back_step.maneuver.instruction.type) && - isNoticeableNameChange(steps[two_back_index], current_step)) - { - + if (current_step.maneuver.instruction.type == TurnType::OnRamp || + current_step.maneuver.instruction.type == TurnType::OffRamp) + steps[one_back_index].maneuver.instruction.type = + current_step.maneuver.instruction.type; + else steps[one_back_index].maneuver.instruction.type = TurnType::Turn; - } - else if (TurnType::Turn == one_back_step.maneuver.instruction.type && - !isNoticeableNameChange(steps[two_back_index], current_step)) - { - steps[one_back_index].maneuver.instruction.type = TurnType::Continue; + } + else if (TurnType::Turn == one_back_step.maneuver.instruction.type && + !isNoticeableNameChange(steps[two_back_index], current_step)) + { + steps[one_back_index].maneuver.instruction.type = TurnType::Continue; - const auto getBearing = [](bool in, const RouteStep &step) { - const auto index = - in ? step.intersections.front().in : step.intersections.front().out; - return step.intersections.front().bearings[index]; - }; + const auto getBearing = [](bool in, const RouteStep &step) { + const auto index = + in ? step.intersections.front().in : step.intersections.front().out; + return step.intersections.front().bearings[index]; + }; - // If we Merge onto the same street, we end up with a u-turn in some cases - if (bearingsAreReversed( - util::bearing::reverseBearing(getBearing(true, one_back_step)), - getBearing(false, current_step))) - { - steps[one_back_index].maneuver.instruction.type = TurnType::Continue; - steps[one_back_index].maneuver.instruction.direction_modifier = - DirectionModifier::UTurn; - } - } - else if (TurnType::Merge == one_back_step.maneuver.instruction.type && - current_step.maneuver.instruction.type != - TurnType::Suppressed) // This suppressed is a check for highways. We might - // need a highway-suppressed to get the turn onto a - // highway... + // If we Merge onto the same street, we end up with a u-turn in some cases + if (bearingsAreReversed(util::bearing::reverseBearing(getBearing(true, one_back_step)), + getBearing(false, current_step))) { steps[one_back_index].maneuver.instruction.direction_modifier = - util::guidance::mirrorDirectionModifier( - steps[one_back_index].maneuver.instruction.direction_modifier); + DirectionModifier::UTurn; } forwardStepSignage(steps[one_back_index], current_step); - invalidateStep(steps[step_index]); } - } - // Potential U-Turn - else if ((one_back_step.distance <= MAX_COLLAPSE_DISTANCE || - choiceless(current_step, one_back_step)) && - bearingsAreReversed(util::bearing::reverseBearing( - one_back_step.intersections.front() - .bearings[one_back_step.intersections.front().in]), - current_step.intersections.front() - .bearings[current_step.intersections.front().out]) && - compatible(one_back_step, current_step)) - { - BOOST_ASSERT(two_back_index < steps.size()); - // the simple case is a u-turn that changes directly into the in-name again - const bool direct_u_turn = !isNoticeableNameChange(steps[two_back_index], current_step); + else if (TurnType::NewName == one_back_step.maneuver.instruction.type || + (TurnType::NewName == current_step.maneuver.instruction.type && + steps[one_back_index].maneuver.instruction.type == TurnType::Suppressed)) + steps[one_back_index].maneuver.instruction.type = TurnType::Turn; - // however, we might also deal with a dual-collapse scenario in which we have to - // additionall collapse a name-change as welll - const auto next_step_index = step_index + 1; - const bool continues_with_name_change = - (next_step_index < steps.size()) && - ((steps[next_step_index].maneuver.instruction.type == TurnType::UseLane && - steps[next_step_index].maneuver.instruction.direction_modifier == - DirectionModifier::Straight) || - isCollapsableInstruction(steps[next_step_index].maneuver.instruction)); - const bool u_turn_with_name_change = - continues_with_name_change && steps[next_step_index].name_id != EMPTY_NAMEID && - !isNoticeableNameChange(steps[two_back_index], steps[next_step_index]); - - if (direct_u_turn || u_turn_with_name_change) + if (TurnType::Merge == one_back_step.maneuver.instruction.type && + current_step.maneuver.instruction.type != + TurnType::Suppressed) // This suppressed is a check for highways. We might + // need a highway-suppressed to get the turn onto a + // highway... { - steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); - invalidateStep(steps[step_index]); - if (u_turn_with_name_change && - compatible(steps[one_back_index], steps[next_step_index])) - { - steps[one_back_index] = - elongate(std::move(steps[one_back_index]), steps[next_step_index]); - invalidateStep(steps[next_step_index]); // will be skipped due to the - // continue statement at the - // beginning of this function - - forwardStepSignage(steps[one_back_index], steps[two_back_index]); - } - if (direct_u_turn) - forwardStepSignage(steps[one_back_index], steps[two_back_index]); - steps[one_back_index].maneuver.instruction.type = TurnType::Continue; steps[one_back_index].maneuver.instruction.direction_modifier = - DirectionModifier::UTurn; + util::guidance::mirrorDirectionModifier( + steps[one_back_index].maneuver.instruction.direction_modifier); } + // on non merge-types, we check for a combined turn angle + else if (TurnType::Merge != one_back_step.maneuver.instruction.type) + { + const auto combined_angle = findTotalTurnAngle(one_back_step, current_step); + steps[one_back_index].maneuver.instruction.direction_modifier = + getTurnDirection(combined_angle); + } + + steps[one_back_index].name = current_step.name; + steps[one_back_index].name_id = current_step.name_id; + invalidateStep(steps[step_index]); + } + else if (TurnType::Suppressed == current_step.maneuver.instruction.type && + !isNoticeableNameChange(one_back_step, current_step)) + { + steps[one_back_index] = elongate(std::move(steps[one_back_index]), current_step); + const auto angle = findTotalTurnAngle(one_back_step, current_step); + steps[one_back_index].maneuver.instruction.direction_modifier = + util::guidance::getTurnDirection(angle); + + invalidateStep(steps[step_index]); + } + else if (TurnType::Turn == one_back_step.maneuver.instruction.type && + TurnType::OnRamp == current_step.maneuver.instruction.type) + { + // turning onto a ramp makes the first turn into a ramp + steps[one_back_index] = elongate(std::move(steps[one_back_index]), current_step); + steps[one_back_index].maneuver.instruction.type = TurnType::OnRamp; + const auto angle = findTotalTurnAngle(one_back_step, current_step); + steps[one_back_index].maneuver.instruction.direction_modifier = + util::guidance::getTurnDirection(angle); + + forwardStepSignage(steps[one_back_index], current_step); + invalidateStep(steps[step_index]); } } @@ -559,10 +724,33 @@ bool isStaggeredIntersection(const RouteStep &previous, const RouteStep ¤t } // namespace +// A check whether two instructions can be treated as one. This is only the case for very short +// maneuvers that can, in some form, be seen as one. Lookahead of one step. +bool collapsable(const RouteStep &step, const RouteStep &next) +{ + const auto is_short_step = step.distance < MAX_COLLAPSE_DISTANCE; + const auto instruction_can_be_collapsed = isCollapsableInstruction(step.maneuver.instruction); + + const auto is_use_lane = step.maneuver.instruction.type == TurnType::UseLane; + const auto lanes_dont_change = + step.intersections.front().lanes == next.intersections.front().lanes; + + if (is_short_step && instruction_can_be_collapsed) + return true; + + // Prevent collapsing away important lane change steps + if (is_short_step && is_use_lane && lanes_dont_change) + return true; + + return false; +} + // elongate a step by another. the data is added either at the front, or the back OSRM_ATTR_WARN_UNUSED RouteStep elongate(RouteStep step, const RouteStep &by_step) { + BOOST_ASSERT(step.mode == by_step.mode); + step.duration += by_step.duration; step.distance += by_step.distance; BOOST_ASSERT(step.mode == by_step.mode); @@ -597,27 +785,6 @@ RouteStep elongate(RouteStep step, const RouteStep &by_step) // Post processing can invalidate some instructions. For example StayOnRoundabout // is turned into exit counts. These instructions are removed by the following function -// A check whether two instructions can be treated as one. This is only the case for very short -// maneuvers that can, in some form, be seen as one. Lookahead of one step. -bool collapsable(const RouteStep &step, const RouteStep &next) -{ - const auto is_short_step = step.distance < MAX_COLLAPSE_DISTANCE; - const auto instruction_can_be_collapsed = isCollapsableInstruction(step.maneuver.instruction); - - const auto is_use_lane = step.maneuver.instruction.type == TurnType::UseLane; - const auto lanes_dont_change = - step.intersections.front().lanes == next.intersections.front().lanes; - - if (is_short_step && instruction_can_be_collapsed) - return true; - - // Prevent collapsing away important lane change steps - if (is_short_step && is_use_lane && lanes_dont_change) - return true; - - return false; -} - std::vector removeNoTurnInstructions(std::vector steps) { // finally clean up the post-processed instructions. @@ -733,17 +900,6 @@ std::vector collapseTurns(std::vector steps) if (steps.size() <= 2) return steps; - // Get the previous non-invalid instruction - const auto getPreviousIndex = [&steps](std::size_t index) { - BOOST_ASSERT(index > 0); - BOOST_ASSERT(index < steps.size()); - --index; - while (index > 0 && steps[index].maneuver.instruction.type == TurnType::NoTurn) - --index; - - return index; - }; - const auto getPreviousNameIndex = [&steps](std::size_t index) { BOOST_ASSERT(index > 0); BOOST_ASSERT(index < steps.size()); @@ -774,9 +930,7 @@ std::vector collapseTurns(std::vector steps) { const auto ¤t_step = steps[step_index]; const auto next_step_index = step_index + 1; - if (current_step.maneuver.instruction.type == TurnType::NoTurn) - continue; - const auto one_back_index = getPreviousIndex(step_index); + const auto one_back_index = getPreviousIndex(step_index, steps); BOOST_ASSERT(one_back_index < steps.size()); const auto &one_back_step = steps[one_back_index]; @@ -808,13 +962,24 @@ std::vector collapseTurns(std::vector steps) else { // Handle possible u-turns between highways that look like slip-roads + if (steps[getPreviousIndex(one_back_index, steps)].name_id == + steps[step_index].name_id && + steps[step_index].name_id != EMPTY_NAMEID) + { + steps[one_back_index].maneuver.instruction.type = TurnType::Continue; + } + else + { + steps[one_back_index].maneuver.instruction.type = TurnType::Turn; + } if (compatible(one_back_step, current_step)) { // Turn Types in the response depend on whether we find the same road name // (sliproad indcating a u-turn) or if we are turning onto a different road, in // which case we use a turn. - if (!isNoticeableNameChange(steps[getPreviousIndex(one_back_index)], - steps[step_index])) + if (!isNoticeableNameChange(steps[getPreviousIndex(one_back_index, steps)], + current_step) && + current_step.name_id != EMPTY_NAMEID) steps[one_back_index].maneuver.instruction.type = TurnType::Continue; else steps[one_back_index].maneuver.instruction.type = TurnType::Turn; @@ -830,16 +995,9 @@ std::vector collapseTurns(std::vector steps) steps[one_back_index].intersections.front().lane_description = current_step.intersections.front().lane_description; - const auto exit_intersection = steps[step_index].intersections.front(); - const auto exit_bearing = exit_intersection.bearings[exit_intersection.out]; - - const auto entry_intersection = steps[one_back_index].intersections.front(); - const auto entry_bearing = entry_intersection.bearings[entry_intersection.in]; - - const double angle = - turn_angle(util::bearing::reverseBearing(entry_bearing), exit_bearing); + const auto angle = findTotalTurnAngle(one_back_step, current_step); steps[one_back_index].maneuver.instruction.direction_modifier = - ::osrm::util::guidance::getTurnDirection(angle); + util::guidance::getTurnDirection(angle); invalidateStep(steps[step_index]); } else @@ -875,7 +1033,7 @@ std::vector collapseTurns(std::vector steps) isCollapsableInstruction(one_back_step.maneuver.instruction)) || isStaggeredIntersection(one_back_step, current_step))) { - const auto two_back_index = getPreviousIndex(one_back_index); + const auto two_back_index = getPreviousIndex(one_back_index, steps); BOOST_ASSERT(two_back_index < steps.size()); // valid, since one_back is collapsable or a turn and therefore not depart: if (!isNoticeableNameChange(steps[two_back_index], current_step)) @@ -930,27 +1088,44 @@ std::vector collapseTurns(std::vector steps) { // check for one of the multiple collapse scenarios and, if possible, collapse the // turn - const auto two_back_index = getPreviousIndex(one_back_index); + const auto two_back_index = getPreviousIndex(one_back_index, steps); BOOST_ASSERT(two_back_index < steps.size()); collapseTurnAt(steps, two_back_index, one_back_index, step_index); } } - else if (one_back_index > 0 && (one_back_step.distance <= MAX_COLLAPSE_DISTANCE || - choiceless(current_step, one_back_step))) + else if (one_back_index > 0 && + (one_back_step.distance <= MAX_COLLAPSE_DISTANCE || + choiceless(current_step, one_back_step) || isLinkroad(one_back_step))) { // check for one of the multiple collapse scenarios and, if possible, collapse the turn - const auto two_back_index = getPreviousIndex(one_back_index); + const auto two_back_index = getPreviousIndex(one_back_index, steps); BOOST_ASSERT(two_back_index < steps.size()); // all turns that are handled lower down are also compatible collapseTurnAt(steps, two_back_index, one_back_index, step_index); } + + if (steps[step_index].maneuver.instruction.type == TurnType::Turn) + { + const auto u_turn_one_back_index = getPreviousIndex(step_index, steps); + if (u_turn_one_back_index > 0) + { + const auto u_turn_two_back_index = getPreviousIndex(u_turn_one_back_index, steps); + if (isUTurn(steps[u_turn_one_back_index], + steps[step_index], + steps[u_turn_two_back_index])) + { + collapseUTurn(steps, u_turn_two_back_index, u_turn_one_back_index, step_index); + } + } + } } // handle final sliproad if (steps.size() >= 3 && - steps[getPreviousIndex(steps.size() - 1)].maneuver.instruction.type == TurnType::Sliproad) + steps[getPreviousIndex(steps.size() - 1, steps)].maneuver.instruction.type == + TurnType::Sliproad) { - steps[getPreviousIndex(steps.size() - 1)].maneuver.instruction.type = TurnType::Turn; + steps[getPreviousIndex(steps.size() - 1, steps)].maneuver.instruction.type = TurnType::Turn; } BOOST_ASSERT(steps.front().intersections.size() >= 1); @@ -1260,6 +1435,7 @@ std::vector buildIntersections(std::vector steps) steps[last_valid_instruction] = elongate(std::move(steps[last_valid_instruction]), step); step.maneuver.instruction = TurnInstruction::NO_TURN(); + invalidateStep(steps[step_index]); } else if (!isSilent(instruction)) { @@ -1296,16 +1472,6 @@ std::vector collapseUseLane(std::vector steps) return (mask & tag) != extractor::guidance::TurnLaneType::empty; }; - const auto getPreviousIndex = [&steps](std::size_t index) { - BOOST_ASSERT(index > 0); - BOOST_ASSERT(index < steps.size()); - --index; - while (index > 0 && steps[index].maneuver.instruction.type == TurnType::NoTurn) - --index; - - return index; - }; - const auto canCollapseUseLane = [containsTag](const RouteStep &step) { // the lane description is given left to right, lanes are counted from the right. // Therefore we access the lane description using the reverse iterator @@ -1330,9 +1496,8 @@ std::vector collapseUseLane(std::vector steps) const auto &step = steps[step_index]; if (step.maneuver.instruction.type == TurnType::UseLane && canCollapseUseLane(step)) { - const auto previous = getPreviousIndex(step_index); + const auto previous = getPreviousIndex(step_index, steps); steps[previous] = elongate(std::move(steps[previous]), steps[step_index]); - // elongate(steps[step_index-1], steps[step_index]); invalidateStep(steps[step_index]); } } diff --git a/src/engine/plugins/tile.cpp b/src/engine/plugins/tile.cpp index 8a892bb2d..b10faeca3 100644 --- a/src/engine/plugins/tile.cpp +++ b/src/engine/plugins/tile.cpp @@ -467,7 +467,8 @@ Status TilePlugin::HandleRequest(const std::shared_ptrGetUncompressedForwardGeometry(source_ebn.second.packed_geometry_id); + first_geometry = + facade->GetUncompressedForwardGeometry(source_ebn.second.packed_geometry_id); // We earlier saved the source and target intersection nodes for every road section. // We can use the target node to find all road sections that lead away from @@ -534,7 +535,8 @@ Status TilePlugin::HandleRequest(const std::shared_ptrGetUncompressedForwardWeights(source_ebn.second.packed_geometry_id); + forward_weight_vector = + facade->GetUncompressedForwardWeights(source_ebn.second.packed_geometry_id); const auto sum_node_weight = std::accumulate( forward_weight_vector.begin(), forward_weight_vector.end(), EdgeWeight{0}); diff --git a/src/extractor/compressed_edge_container.cpp b/src/extractor/compressed_edge_container.cpp index 0fd7f30ce..95304db2f 100644 --- a/src/extractor/compressed_edge_container.cpp +++ b/src/extractor/compressed_edge_container.cpp @@ -164,7 +164,8 @@ void CompressedEdgeContainer::CompressEdge(const EdgeID edge_id_1, BOOST_ASSERT(edge_bucket_id1 == GetPositionForID(edge_id_1)); BOOST_ASSERT(edge_bucket_id1 < m_compressed_oneway_geometries.size()); - std::vector &edge_bucket_list1 = m_compressed_oneway_geometries[edge_bucket_id1]; + std::vector &edge_bucket_list1 = + m_compressed_oneway_geometries[edge_bucket_id1]; // note we don't save the start coordinate: it is implicitly given by edge 1 // weight1 is the distance to the (currently) last coordinate in the bucket @@ -235,7 +236,8 @@ void CompressedEdgeContainer::AddUncompressedEdge(const EdgeID edge_id, BOOST_ASSERT(edge_bucket_id == GetPositionForID(edge_id)); BOOST_ASSERT(edge_bucket_id < m_compressed_oneway_geometries.size()); - std::vector &edge_bucket_list = m_compressed_oneway_geometries[edge_bucket_id]; + std::vector &edge_bucket_list = + m_compressed_oneway_geometries[edge_bucket_id]; // note we don't save the start coordinate: it is implicitly given by edge_id // weight is the distance to the (currently) last coordinate in the bucket @@ -264,7 +266,8 @@ unsigned CompressedEdgeContainer::ZipEdges(const EdgeID f_edge_id, const EdgeID std::vector zipped_edge_bucket; const auto &first_node = reverse_bucket.back(); - zipped_edge_bucket.emplace_back(CompressedEdge{first_node.node_id, INVALID_EDGE_WEIGHT, first_node.weight}); + zipped_edge_bucket.emplace_back( + CompressedEdge{first_node.node_id, INVALID_EDGE_WEIGHT, first_node.weight}); for (std::size_t i = 0; i < forward_bucket.size() - 1; ++i) { @@ -273,11 +276,13 @@ unsigned CompressedEdgeContainer::ZipEdges(const EdgeID f_edge_id, const EdgeID BOOST_ASSERT(fwd_node.node_id == rev_node.node_id); - zipped_edge_bucket.emplace_back(CompressedEdge{fwd_node.node_id, fwd_node.weight, rev_node.weight}); + zipped_edge_bucket.emplace_back( + CompressedEdge{fwd_node.node_id, fwd_node.weight, rev_node.weight}); } const auto &last_node = forward_bucket.back(); - zipped_edge_bucket.emplace_back(CompressedEdge{last_node.node_id, last_node.weight, INVALID_EDGE_WEIGHT}); + zipped_edge_bucket.emplace_back( + CompressedEdge{last_node.node_id, last_node.weight, INVALID_EDGE_WEIGHT}); m_compressed_geometries.emplace_back(std::move(zipped_edge_bucket)); return zipped_geometry_id; diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index 2596ebf52..534bfa594 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -1,8 +1,10 @@ #include "extractor/edge_based_graph_factory.hpp" #include "extractor/edge_based_edge.hpp" +#include "util/bearing.hpp" #include "util/coordinate.hpp" #include "util/coordinate_calculation.hpp" #include "util/exception.hpp" +#include "util/guidance/turn_bearing.hpp" #include "util/integer_range.hpp" #include "util/percent.hpp" #include "util/simple_logger.hpp" @@ -377,18 +379,14 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( 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, - intersection, - *m_node_based_graph, - m_compressed_edge_container, - m_node_info_list); + const auto turn_classification = classifyIntersection(intersection); const auto entry_class_id = [&](const util::guidance::EntryClass entry_class) { if (0 == entry_class_hash.count(entry_class)) @@ -436,6 +434,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( const int32_t turn_penalty = scripting_environment.GetTurnPenalty(180. - turn.angle); + const auto turn_instruction = turn.instruction; if (turn_instruction.direction_modifier == guidance::DirectionModifier::UTurn) @@ -443,27 +442,43 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( distance += profile_properties.u_turn_penalty; } - distance += turn_penalty; + // don't add turn penalty if it is not an actual turn. This heuristic is necessary + // since OSRM cannot handle looping roads/parallel roads + if (turn_instruction.type != guidance::TurnType::NoTurn) + distance += turn_penalty; - const bool is_encoded_forwards = m_compressed_edge_container.HasZippedEntryForForwardID(edge_from_u); - const bool is_encoded_backwards = m_compressed_edge_container.HasZippedEntryForReverseID(edge_from_u); + const bool is_encoded_forwards = + m_compressed_edge_container.HasZippedEntryForForwardID(edge_from_u); + const bool is_encoded_backwards = + m_compressed_edge_container.HasZippedEntryForReverseID(edge_from_u); BOOST_ASSERT(is_encoded_forwards || is_encoded_backwards); - if (is_encoded_forwards) { + if (is_encoded_forwards) + { original_edge_data_vector.emplace_back( - GeometryID{m_compressed_edge_container.GetZippedPositionForForwardID(edge_from_u), true}, + GeometryID{ + m_compressed_edge_container.GetZippedPositionForForwardID(edge_from_u), + true}, edge_data1.name_id, turn.lane_data_id, turn_instruction, entry_class_id, - edge_data1.travel_mode); - } else if (is_encoded_backwards) { + edge_data1.travel_mode, + util::guidance::TurnBearing(intersection[0].turn.bearing), + util::guidance::TurnBearing(turn.bearing)); + } + else if (is_encoded_backwards) + { original_edge_data_vector.emplace_back( - GeometryID{m_compressed_edge_container.GetZippedPositionForReverseID(edge_from_u), false}, + GeometryID{ + m_compressed_edge_container.GetZippedPositionForReverseID(edge_from_u), + false}, edge_data1.name_id, turn.lane_data_id, turn_instruction, entry_class_id, - edge_data1.travel_mode); + edge_data1.travel_mode, + util::guidance::TurnBearing(intersection[0].turn.bearing), + util::guidance::TurnBearing(turn.bearing)); } ++original_edges_counter; diff --git a/src/extractor/guidance/coordinate_extractor.cpp b/src/extractor/guidance/coordinate_extractor.cpp new file mode 100644 index 000000000..e4b63fea1 --- /dev/null +++ b/src/extractor/guidance/coordinate_extractor.cpp @@ -0,0 +1,816 @@ +#include "extractor/guidance/coordinate_extractor.hpp" +#include "extractor/guidance/constants.hpp" +#include "extractor/guidance/toolkit.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace osrm +{ +namespace extractor +{ +namespace guidance +{ + +namespace +{ +// to use the corrected coordinate, we require it to be at least a bit further down the +// road than the offset coordinate. We postulate a minimum Distance of 2 Meters +const constexpr double DESIRED_COORDINATE_DIFFERENCE = 2.0; +// the default distance we lookahead on a road. This distance prevents small mapping +// errors to impact the turn angles. +const constexpr double LOOKAHEAD_DISTANCE_WITHOUT_LANES = 10.0; +// The standard with of a interstate highway is 3.7 meters. Local roads have +// smaller widths, ranging from 2.5 to 3.25 meters. As a compromise, we use +// the 3.25 here for our angle calculations +const constexpr double ASSUMED_LANE_WIDTH = 3.25; +const constexpr double FAR_LOOKAHEAD_DISTANCE = 30.0; + +// The count of lanes assumed when no lanes are present. Since most roads will have lanes for both +// directions or a lane count specified, we use 2. Overestimating only makes our calculations safer, +// so we are fine for 1-lane ways. larger than 2 lanes should usually be specified in the data. +const constexpr std::uint16_t ASSUMED_LANE_COUNT = 2; +} + +CoordinateExtractor::CoordinateExtractor( + const util::NodeBasedDynamicGraph &node_based_graph, + const extractor::CompressedEdgeContainer &compressed_geometries, + const std::vector &node_coordinates) + : node_based_graph(node_based_graph), compressed_geometries(compressed_geometries), + node_coordinates(node_coordinates) +{ +} + +util::Coordinate +CoordinateExtractor::GetCoordinateAlongRoad(const NodeID intersection_node, + const EdgeID turn_edge, + const bool traversed_in_reverse, + const NodeID to_node, + const std::uint8_t intersection_lanes) const +{ + const auto considered_lanes = + (intersection_lanes == 0) ? ASSUMED_LANE_COUNT : intersection_lanes; + + // we first extract all coordinates from the road + auto coordinates = + GetCoordinatesAlongRoad(intersection_node, turn_edge, traversed_in_reverse, to_node); + + /* if we are looking at a straight line, we don't care where exactly the coordinate + * is. Simply return the final coordinate. Turn angles/turn vectors are the same no matter which + * coordinate we look at. + */ + if (coordinates.size() <= 2) + return coordinates.back(); + + // fallback, mostly necessary for dead ends + if (intersection_node == to_node) + return TrimCoordinatesToLength(coordinates, 5).back(); + + const auto lookahead_distance = + FAR_LOOKAHEAD_DISTANCE + considered_lanes * ASSUMED_LANE_WIDTH * 0.5; + + // reduce coordinates to the ones we care about + coordinates = TrimCoordinatesToLength(std::move(coordinates), lookahead_distance); + + // If this reduction leaves us with only two coordinates, the turns/angles are represented in a + // valid way. Only curved roads and other difficult scenarios will require multiple coordinates. + if (coordinates.size() == 2) + return coordinates.back(); + + const auto &turn_edge_data = node_based_graph.GetEdgeData(turn_edge); + const util::Coordinate turn_coordinate = + node_coordinates[traversed_in_reverse ? to_node : intersection_node]; + + // Low priority roads are usually modelled very strangely. The roads are so small, though, that + // our basic heuristic looking at the road should be fine. + if (turn_edge_data.road_classification.IsLowPriorityRoadClass()) + { + // Look ahead a tiny bit. Low priority road classes can be modelled fairly distinct in the + // very first part of the road + coordinates = TrimCoordinatesToLength(std::move(coordinates), 10); + if (coordinates.size() > 2 && + util::coordinate_calculation::haversineDistance(turn_coordinate, coordinates[1]) < + ASSUMED_LANE_WIDTH) + return GetCorrectedCoordinate(turn_coordinate, coordinates[1], coordinates.back()); + else + return coordinates.back(); + } + + /* + * The coordinates along the road are in different distances from the source. If only very few + * coordinates are close to the intersection, It might just be we simply looked to far down the + * road. We can decide to weight coordinates differently based on their distance from the + * intersection. + * In addition, changes very close to an intersection indicate graphical representation of the + * intersection over perceived turn angles. + * + * a - + * \ + * -------------------- b + * + * Here the initial angle close to a might simply be due to OSM-Ways being located in the middle + * of the actual roads. If a road splits in two, the ways for the separate direction can be + * modeled very far apart with a steep angle at the split, even though the roads actually don't + * take a turn. The distance between the coordinates can be an indicator for these small changes + */ + const auto segment_distances = [&coordinates]() { + std::vector segment_distances; + segment_distances.reserve(coordinates.size()); + // sentinel + auto last_coordinate = coordinates.front(); + boost::range::transform(coordinates, + std::back_inserter(segment_distances), + [&last_coordinate](const util::Coordinate current_coordinate) { + const auto distance = + util::coordinate_calculation::haversineDistance( + last_coordinate, current_coordinate); + last_coordinate = current_coordinate; + return distance; + }); + return segment_distances; + }(); + + /* if the very first coordinate along the road is reasonably far away from the road, we assume + * the coordinate to correctly represent the turn. This could probably be improved using + * information on the very first turn angle (requires knowledge about previous road) and the + * respective lane widths. + */ + const bool first_coordinate_is_far_away = [&segment_distances, considered_lanes]() { + const auto required_distance = + considered_lanes * 0.5 * ASSUMED_LANE_WIDTH + LOOKAHEAD_DISTANCE_WITHOUT_LANES; + return segment_distances[1] > required_distance; + }(); + + if (first_coordinate_is_far_away) + { + return coordinates[1]; + } + + const double max_deviation_from_straight = GetMaxDeviation( + coordinates.begin(), coordinates.end(), coordinates.front(), coordinates.back()); + + // if the deviation from a straight line is small, we can savely use the coordinate. We use half + // a lane as heuristic to determine if the road is straight enough. + if (max_deviation_from_straight < 0.5 * ASSUMED_LANE_WIDTH) + { + return coordinates.back(); + } + + /* + * if a road turns barely in the beginning, it is similar to the first coordinate being + * sufficiently far ahead. + * possible negative: + * http://www.openstreetmap.org/search?query=52.514503%2013.32252#map=19/52.51450/13.32252 + */ + const auto straight_distance_and_index = [&]() { + auto straight_distance = segment_distances[1]; + + std::size_t index; + for (index = 2; index < coordinates.size(); ++index) + { + // check the deviation from a straight line + if (GetMaxDeviation(coordinates.begin(), + coordinates.begin() + index, + coordinates.front(), + *(coordinates.begin() + index)) < 0.25 * ASSUMED_LANE_WIDTH) + straight_distance += segment_distances[index]; + else + break; + } + return std::make_pair(index - 1, straight_distance); + }(); + const auto straight_distance = straight_distance_and_index.second; + const auto straight_index = straight_distance_and_index.first; + + const bool starts_of_without_turn = [&]() { + return straight_distance >= + considered_lanes * 0.5 * ASSUMED_LANE_WIDTH + LOOKAHEAD_DISTANCE_WITHOUT_LANES; + }(); + if (starts_of_without_turn) + { + // skip over repeated coordinates + return TrimCoordinatesToLength(std::move(coordinates), 5).back(); + } + + // compute the regression vector based on the sum of least squares + const auto regression_line = RegressionLine(coordinates); + + /* + * If we can find a line that represents the full set of coordinates within a certain range in + * relation to ASSUMED_LANE_WIDTH, we use the regression line to express the turn angle. + * This yields a transformation similar to: + * + * c d d + * b -> c + * b + * a a + */ + const double max_deviation_from_regression = GetMaxDeviation( + coordinates.begin(), coordinates.end(), regression_line.first, regression_line.second); + + if (max_deviation_from_regression < 0.35 * ASSUMED_LANE_WIDTH) + { + // We use the locations on the regression line to offset the regression line onto the + // intersection. + const auto coord_between_front = + util::coordinate_calculation::projectPointOnSegment( + regression_line.first, regression_line.second, coordinates.front()) + .second; + const auto coord_between_back = + util::coordinate_calculation::projectPointOnSegment( + regression_line.first, regression_line.second, coordinates.back()) + .second; + return GetCorrectedCoordinate(turn_coordinate, coord_between_front, coord_between_back); + } + + const auto total_distance = + std::accumulate(segment_distances.begin(), segment_distances.end(), 0.); + + if (IsDirectOffset(coordinates, + straight_index, + straight_distance, + total_distance, + segment_distances, + considered_lanes)) + { + // could be too agressive? Depend on lanes to check how far we want to go out? + // compare + // http://www.openstreetmap.org/search?query=52.411243%2013.363575#map=19/52.41124/13.36357 + const auto offset_index = std::max(1, straight_index); + return GetCorrectedCoordinate( + turn_coordinate, coordinates[offset_index], coordinates[offset_index + 1]); + } + + if (IsCurve(coordinates, + segment_distances, + total_distance, + considered_lanes * 0.5 * ASSUMED_LANE_WIDTH, + turn_edge_data)) + { + /* + * In curves we now have to distinguish between larger curves and tiny curves modelling the + * actual turn in the beginnig. + * + * We distinguish between turns that simply model the initial way of getting onto the + * destination lanes and the ones that performa a larger turn. + */ + const double offset = 0.5 * considered_lanes * ASSUMED_LANE_WIDTH; + coordinates = TrimCoordinatesToLength(std::move(coordinates), offset); + const auto vector_head = coordinates.back(); + coordinates = TrimCoordinatesToLength(std::move(coordinates), offset); + BOOST_ASSERT(coordinates.size() >= 2); + return GetCorrectedCoordinate(turn_coordinate, coordinates.back(), vector_head); + } + + { + // skip over the first coordinates, in specific the assumed lane count. We add a small + // safety factor, to not overshoot on the regression + const auto trimmed_coordinates = TrimCoordinatesByLengthFront( + coordinates, 0.8 * (considered_lanes * ASSUMED_LANE_WIDTH)); + if (trimmed_coordinates.size() >= 2) + { + // get the regression line + const auto regression_line_trimmed = RegressionLine(trimmed_coordinates); + + // and compute the maximum deviation from it + const auto max_deviation_from_trimmed_regression = + GetMaxDeviation(trimmed_coordinates.begin(), + trimmed_coordinates.end(), + regression_line_trimmed.first, + regression_line_trimmed.second); + + if (max_deviation_from_trimmed_regression < 0.5 * ASSUMED_LANE_WIDTH) + return GetCorrectedCoordinate( + turn_coordinate, regression_line_trimmed.first, regression_line_trimmed.second); + } + } + + // We use the locations on the regression line to offset the regression line onto the + // intersection. + return TrimCoordinatesToLength(coordinates, LOOKAHEAD_DISTANCE_WITHOUT_LANES).back(); +} + +std::vector +CoordinateExtractor::GetCoordinatesAlongRoad(const NodeID intersection_node, + const EdgeID turn_edge, + const bool traversed_in_reverse, + const NodeID to_node) const +{ + if (!compressed_geometries.HasEntryForID(turn_edge)) + { + if (traversed_in_reverse) + return {{node_coordinates[to_node]}, {node_coordinates[intersection_node]}}; + else + return {{node_coordinates[intersection_node]}, {node_coordinates[to_node]}}; + } + else + { + // extracts the geometry in coordinates from the compressed edge container + std::vector result; + const auto &geometry = compressed_geometries.GetBucketReference(turn_edge); + result.reserve(geometry.size() + 2); + + // the compressed edges contain node ids, we transfer them to coordinates accessing the + // node_coordinates array + const auto compressedGeometryToCoordinate = + [this](const CompressedEdgeContainer::OnewayCompressedEdge &compressed_edge) + -> util::Coordinate { return node_coordinates[compressed_edge.node_id]; }; + + // add the coordinates to the result in either normal or reversed order, based on + // traversed_in_reverse + if (traversed_in_reverse) + { + std::transform(geometry.rbegin(), + geometry.rend(), + std::back_inserter(result), + compressedGeometryToCoordinate); + result.push_back(node_coordinates[intersection_node]); + } + else + { + result.push_back(node_coordinates[intersection_node]); + std::transform(geometry.begin(), + geometry.end(), + std::back_inserter(result), + compressedGeometryToCoordinate); + } + return result; + } +} + +double +CoordinateExtractor::GetMaxDeviation(std::vector::const_iterator range_begin, + const std::vector::const_iterator &range_end, + const util::Coordinate straight_begin, + const util::Coordinate straight_end) const +{ + // compute the deviation of a single coordinate from a straight line + auto get_single_deviation = [&](const util::Coordinate coordinate) { + // find the projected coordinate + auto coord_between = util::coordinate_calculation::projectPointOnSegment( + straight_begin, straight_end, coordinate) + .second; + // and calculate the distance between the intermediate coordinate and the coordinate + // on the osrm-way + return util::coordinate_calculation::haversineDistance(coord_between, coordinate); + }; + + // note: we don't accumulate here but rather compute the maximum. The functor passed here is not + // summing up anything. + return std::accumulate( + range_begin, range_end, 0.0, [&](const double current, const util::Coordinate coordinate) { + return std::max(current, get_single_deviation(coordinate)); + }); +}; + +bool CoordinateExtractor::IsCurve(const std::vector &coordinates, + const std::vector &segment_distances, + const double segment_length, + const double considered_lane_width, + const util::NodeBasedEdgeData &edge_data) const +{ + BOOST_ASSERT(coordinates.size() > 2); + + // by default, we treat roundabout as curves + if (edge_data.roundabout) + return true; + + // TODO we might have to fix this to better compensate for errors due to repeated coordinates + const bool takes_an_actual_turn = [&coordinates]() { + const auto begin_bearing = + util::coordinate_calculation::bearing(coordinates[0], coordinates[1]); + const auto end_bearing = util::coordinate_calculation::bearing( + coordinates[coordinates.size() - 2], coordinates[coordinates.size() - 1]); + + const auto total_angle = angularDeviation(begin_bearing, end_bearing); + return total_angle > 0.5 * NARROW_TURN_ANGLE; + }(); + + if (!takes_an_actual_turn) + return false; + + const auto get_deviation = [](const util::Coordinate line_start, + const util::Coordinate line_end, + const util::Coordinate point) { + // find the projected coordinate + auto coord_between = + util::coordinate_calculation::projectPointOnSegment(line_start, line_end, point).second; + // and calculate the distance between the intermediate coordinate and the coordinate + return util::coordinate_calculation::haversineDistance(coord_between, point); + }; + + // a curve needs to be on one side of the coordinate array + const bool all_same_side = [&]() { + if (coordinates.size() <= 3) + return true; + + const bool ccw = util::coordinate_calculation::isCCW( + coordinates.front(), coordinates.back(), coordinates[1]); + + return std::all_of( + coordinates.begin() + 2, coordinates.end() - 1, [&](const util::Coordinate coordinate) { + const bool compare_ccw = util::coordinate_calculation::isCCW( + coordinates.front(), coordinates.back(), coordinate); + return ccw == compare_ccw; + }); + }(); + + if (!all_same_side) + return false; + + // check if the deviation is a sequence that increases up to a maximum deviation and decreses + // after, following what we would expect from a modelled curve + bool has_up_down_deviation = false; + std::size_t maximum_deviation_index = 0; + double maximum_deviation = 0; + + std::tie(has_up_down_deviation, maximum_deviation_index, maximum_deviation) = + [&coordinates, get_deviation]() -> std::tuple { + const auto increasing = [&](const util::Coordinate lhs, const util::Coordinate rhs) { + return get_deviation(coordinates.front(), coordinates.back(), lhs) <= + get_deviation(coordinates.front(), coordinates.back(), rhs); + }; + + const auto decreasing = [&](const util::Coordinate lhs, const util::Coordinate rhs) { + return get_deviation(coordinates.front(), coordinates.back(), lhs) >= + get_deviation(coordinates.front(), coordinates.back(), rhs); + }; + + if (coordinates.size() < 3) + return std::make_tuple(true, 0, 0.); + + if (coordinates.size() == 3) + return std::make_tuple( + true, 1, get_deviation(coordinates.front(), coordinates.back(), coordinates[1])); + + const auto maximum_itr = + std::is_sorted_until(coordinates.begin() + 1, coordinates.end(), increasing); + + if (maximum_itr == coordinates.end()) + return std::make_tuple(true, coordinates.size() - 1, 0.); + else if (std::is_sorted(maximum_itr, coordinates.end(), decreasing)) + return std::make_tuple( + true, + std::distance(coordinates.begin(), maximum_itr), + get_deviation(coordinates.front(), coordinates.back(), *maximum_itr)); + else + return std::make_tuple(false, 0, 0.); + }(); + + // a curve has increasing deviation from its front/back vertices to a certain point and after it + // only decreases + if (!has_up_down_deviation) + return false; + + // if the maximum deviation is at a quarter of the total curve, we are probably looking at a + // normal turn + const auto distance_to_max_deviation = std::accumulate( + segment_distances.begin(), segment_distances.begin() + maximum_deviation_index, 0.); + + if ((distance_to_max_deviation <= 0.35 * segment_length || + maximum_deviation < std::max(0.3 * considered_lane_width, 0.5 * ASSUMED_LANE_WIDTH)) && + segment_length > 10) + return false; + + BOOST_ASSERT(coordinates.size() >= 3); + // Compute all turn angles along the road + const auto turn_angles = [coordinates]() { + std::vector turn_angles; + turn_angles.reserve(coordinates.size() - 2); + for (std::size_t index = 0; index + 2 < coordinates.size(); ++index) + { + turn_angles.push_back(util::coordinate_calculation::computeAngle( + coordinates[index], coordinates[index + 1], coordinates[index + 2])); + } + return turn_angles; + }(); + + const bool curve_is_valid = + [&turn_angles, &segment_distances, &segment_length, &considered_lane_width]() { + // internal state for our lamdae + bool last_was_straight = false; + // a turn angle represents two segments between three coordinates. We initialize the + // distance with the very first segment length (in-segment) of the first turn-angle + double straight_distance = std::max(0., segment_distances[1] - considered_lane_width); + auto distance_itr = segment_distances.begin() + 1; + + // every call to the lamda requires a call to the distances. They need to be aligned + BOOST_ASSERT(segment_distances.size() == turn_angles.size() + 2); + + const auto detect_invalid_curve = [&](const double previous_angle, + const double current_angle) { + const auto both_actually_turn = + (angularDeviation(previous_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) && + (angularDeviation(current_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE); + // they cannot be straight, since they differ at least by FUZZY_ANGLE_DIFFERENCE + const auto turn_direction_switches = + (previous_angle > STRAIGHT_ANGLE) == (current_angle < STRAIGHT_ANGLE); + + // a turn that switches direction mid-curve is not a valid curve + if (both_actually_turn && turn_direction_switches) + return true; + + const bool is_straight = angularDeviation(current_angle, STRAIGHT_ANGLE) < 5; + ++distance_itr; + if (is_straight) + { + // since the angle is straight, we augment it by the second part of the segment + straight_distance += *distance_itr; + if (last_was_straight && straight_distance > 0.3 * segment_length) + { + return true; + } + } // if a segment on its own is long enough, thats fair game as well + else if (straight_distance > 0.3 * segment_length) + return true; + else + { + // we reset the last distance, starting with the next in-segment again + straight_distance = *distance_itr; + } + last_was_straight = is_straight; + return false; + }; + + const auto end_of_straight_segment = + std::adjacent_find(turn_angles.begin(), turn_angles.end(), detect_invalid_curve); + + // No curve should have a very long straight segment + return end_of_straight_segment == turn_angles.end(); + }(); + + return (segment_length > 2 * considered_lane_width && curve_is_valid); +} + +bool CoordinateExtractor::IsDirectOffset(const std::vector &coordinates, + const std::size_t straight_index, + const double straight_distance, + const double segment_length, + const std::vector &segment_distances, + const std::uint8_t considered_lanes) const +{ + // check if a given length is with half a lane of the assumed lane offset + const auto IsCloseToLaneDistance = [considered_lanes](const double width) { + // a road usually is connected to the middle of the lanes. So the lane-offset has to + // consider half to road + const auto lane_offset = 0.5 * considered_lanes * ASSUMED_LANE_WIDTH; + return std::abs(width - lane_offset) < 0.5 * ASSUMED_LANE_WIDTH; + }; + + // Check whether the very first coordinate is simply an offset. This is the case if the initial + // vertex is close to the turn and the remaining coordinates are nearly straight. + const auto offset_index = std::max(1, straight_index); + + // we need at least a single coordinate + if (offset_index + 1 >= coordinates.size()) + return false; + + // the straight part has to be around the lane distance + if (!IsCloseToLaneDistance(segment_distances[offset_index])) + return false; + + // the segment itself cannot be short + if (segment_length < 0.8 * FAR_LOOKAHEAD_DISTANCE) + return false; + + // if the remaining segment is short, we don't consider it an offset + if ((segment_length - std::max(straight_distance, segment_distances[1])) > 0.1 * segment_length) + return false; + + // finally, we cannot be far off from a straight line for the remaining coordinates + return 0.5 * ASSUMED_LANE_WIDTH > GetMaxDeviation(coordinates.begin() + offset_index, + coordinates.end(), + coordinates[offset_index], + coordinates.back()); +} + +std::vector +CoordinateExtractor::TrimCoordinatesToLength(std::vector coordinates, + const double desired_length) const +{ + BOOST_ASSERT(coordinates.size() >= 2); + double distance_to_current_coordinate = 0; + + for (std::size_t coordinate_index = 1; coordinate_index < coordinates.size(); + ++coordinate_index) + { + const auto distance_to_next_coordinate = + distance_to_current_coordinate + + util::coordinate_calculation::haversineDistance(coordinates[coordinate_index - 1], + coordinates[coordinate_index]); + + // if we reached the number of coordinates, we can stop here + if (distance_to_next_coordinate >= desired_length) + { + coordinates.resize(coordinate_index + 1); + coordinates.back() = util::coordinate_calculation::interpolateLinear( + ComputeInterpolationFactor( + desired_length, distance_to_current_coordinate, distance_to_next_coordinate), + coordinates[coordinate_index - 1], + coordinates[coordinate_index]); + break; + } + + // remember the accumulated distance + distance_to_current_coordinate = distance_to_next_coordinate; + } + if (coordinates.size() > 2 && + util::coordinate_calculation::haversineDistance(coordinates[0], coordinates[1]) <= 1) + coordinates.erase(coordinates.begin() + 1); + + BOOST_ASSERT(coordinates.size()); + return coordinates; +} + +util::Coordinate +CoordinateExtractor::GetCorrectedCoordinate(const util::Coordinate fixpoint, + const util::Coordinate vector_base, + const util::Coordinate vector_head) const +{ + // if the coordinates are close together, we were not able to look far ahead, so + // we can use the end-coordinate + if (util::coordinate_calculation::haversineDistance(vector_base, vector_head) < + DESIRED_COORDINATE_DIFFERENCE) + return vector_head; + else + { + /* to correct for the initial offset, we move the lookahead coordinate close + * to the original road. We do so by subtracting the difference between the + * turn coordinate and the offset coordinate from the lookahead coordinge: + * + * a ------ b ------ c + * | + * d + * \ + * \ + * e + * + * is converted to: + * + * a ------ b ------ c + * \ + * \ + * e + * + * for turn node `b`, vector_base `d` and vector_head `e` + */ + const auto offset_percentage = 90; + const auto corrected_lon = + vector_head.lon - + util::FixedLongitude{offset_percentage * + static_cast(vector_base.lon - fixpoint.lon) / 100}; + const auto corrected_lat = + vector_head.lat - + util::FixedLatitude{offset_percentage * + static_cast(vector_base.lat - fixpoint.lat) / 100}; + + return util::Coordinate(corrected_lon, corrected_lat); + } +}; + +std::vector +CoordinateExtractor::SampleCoordinates(const std::vector &coordinates, + const double max_sample_length, + const double rate) const +{ + BOOST_ASSERT(rate > 0 && coordinates.size() >= 2); + + // the return value + std::vector sampled_coordinates; + sampled_coordinates.reserve(ceil(max_sample_length / rate) + 2); + + // the very first coordinate is always part of the sample + sampled_coordinates.push_back(coordinates.front()); + + double carry_length = 0., total_length = 0.; + // interpolate coordinates as long as we are not past the desired length + const auto add_samples_until_length_limit = [&](const util::Coordinate previous_coordinate, + const util::Coordinate current_coordinate) { + // pretend to have found an element and stop the sampling + if (total_length > max_sample_length) + return true; + + const auto distance_between = util::coordinate_calculation::haversineDistance( + previous_coordinate, current_coordinate); + + if (carry_length + distance_between >= rate) + { + // within the current segment, there is at least a single coordinate that we want to + // sample. We extract all coordinates that are on our sampling intervals and update our + // local sampling item to reflect the travelled distance + const auto base_sampling = rate - carry_length; + + // the number of samples in the interval is equal to the length of the interval (+ the + // already traversed part from the previous segment) divided by the sampling rate + BOOST_ASSERT(max_sample_length > total_length); + const std::size_t num_samples = std::floor( + (std::min(max_sample_length - total_length, distance_between) + carry_length) / + rate); + + for (std::size_t sample_value = 0; sample_value < num_samples; ++sample_value) + { + const auto interpolation_factor = ComputeInterpolationFactor( + base_sampling + sample_value * rate, 0, distance_between); + auto sampled_coordinate = util::coordinate_calculation::interpolateLinear( + interpolation_factor, previous_coordinate, current_coordinate); + sampled_coordinates.emplace_back(sampled_coordinate); + } + + // current length needs to reflect how much is missing to the next sample. Here we can + // ignore max sample range, because if we reached it, the loop is done anyhow + carry_length = (distance_between + carry_length) - (num_samples * rate); + } + else + { + // do the necessary bookkeeping and continue + carry_length += distance_between; + } + // the total length travelled is always updated by the full distance + total_length += distance_between; + + return false; + }; + + // misuse of adjacent_find. Loop over coordinates, until a total sample length is reached + std::adjacent_find(coordinates.begin(), coordinates.end(), add_samples_until_length_limit); + + return sampled_coordinates; +} + +double CoordinateExtractor::ComputeInterpolationFactor(const double desired_distance, + const double distance_to_first, + const double distance_to_second) const +{ + BOOST_ASSERT(distance_to_first < desired_distance); + double segment_length = distance_to_second - distance_to_first; + BOOST_ASSERT(segment_length > 0); + BOOST_ASSERT(distance_to_second >= desired_distance); + double missing_distance = desired_distance - distance_to_first; + return std::max(0., std::min(missing_distance / segment_length, 1.0)); +} + +std::vector +CoordinateExtractor::TrimCoordinatesByLengthFront(std::vector coordinates, + const double desired_length) const +{ + double distance_to_index = 0; + std::size_t index = 0; + for (std::size_t next_index = 1; next_index < coordinates.size(); ++next_index) + { + const double next_distance = + distance_to_index + util::coordinate_calculation::haversineDistance( + coordinates[index], coordinates[next_index]); + if (next_distance >= desired_length) + { + const auto factor = + ComputeInterpolationFactor(desired_length, distance_to_index, next_distance); + auto interpolated_coordinate = util::coordinate_calculation::interpolateLinear( + factor, coordinates[index], coordinates[next_index]); + if (index > 0) + coordinates.erase(coordinates.begin(), coordinates.begin() + index); + coordinates.front() = interpolated_coordinate; + return coordinates; + } + + distance_to_index = next_distance; + index = next_index; + } + + // the coordinates in total are too short in length for the desired length + // this part is only reached when we don't return from within the above loop + coordinates.clear(); + return coordinates; +} + +std::pair +CoordinateExtractor::RegressionLine(const std::vector &coordinates) const +{ + // create a sample of all coordinates to improve the quality of our regression vector + // (less dependent on modelling of the data in OSM) + const auto sampled_coordinates = SampleCoordinates(coordinates, FAR_LOOKAHEAD_DISTANCE, 1); + + // compute the regression vector based on the sum of least squares + const auto regression_line = leastSquareRegression(sampled_coordinates); + const auto coord_between_front = + util::coordinate_calculation::projectPointOnSegment( + regression_line.first, regression_line.second, coordinates.front()) + .second; + const auto coord_between_back = + util::coordinate_calculation::projectPointOnSegment( + regression_line.first, regression_line.second, coordinates.back()) + .second; + + return {coord_between_front, coord_between_back}; +} + +} // namespace guidance +} // namespace extractor +} // namespace osrm diff --git a/src/extractor/guidance/intersection.cpp b/src/extractor/guidance/intersection.cpp index 350c1b85c..2755bcbcd 100644 --- a/src/extractor/guidance/intersection.cpp +++ b/src/extractor/guidance/intersection.cpp @@ -21,6 +21,8 @@ std::string toString(const ConnectedRoad &road) result += std::to_string(road.entry_allowed); result += " angle: "; result += std::to_string(road.turn.angle); + result += " bearing: "; + result += std::to_string(road.turn.bearing); result += " instruction: "; result += std::to_string(static_cast(road.turn.instruction.type)) + " " + std::to_string(static_cast(road.turn.instruction.direction_modifier)) + diff --git a/src/extractor/guidance/intersection_generator.cpp b/src/extractor/guidance/intersection_generator.cpp index 525149eb7..56d9458d5 100644 --- a/src/extractor/guidance/intersection_generator.cpp +++ b/src/extractor/guidance/intersection_generator.cpp @@ -26,7 +26,7 @@ IntersectionGenerator::IntersectionGenerator( const CompressedEdgeContainer &compressed_edge_container) : node_based_graph(node_based_graph), restriction_map(restriction_map), barrier_nodes(barrier_nodes), node_info_list(node_info_list), - compressed_edge_container(compressed_edge_container) + coordinate_extractor(node_based_graph, compressed_edge_container, node_info_list) { } @@ -75,16 +75,21 @@ Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node, bool has_uturn_edge = false; bool uturn_could_be_valid = false; + const util::Coordinate turn_coordinate = node_info_list[turn_node]; + + const auto intersection_lanes = getLaneCountAtIntersection(turn_node, node_based_graph); + for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) { BOOST_ASSERT(onto_edge != SPECIAL_EDGEID); const NodeID to_node = node_based_graph.GetTarget(onto_edge); + const auto &onto_data = node_based_graph.GetEdgeData(onto_edge); bool turn_is_valid = // reverse edges are never valid turns because the resulting turn would look like this: // from_node --via_edge--> turn_node <--onto_edge-- to_node // however we need this for capture intersection shape for incoming one-ways - !node_based_graph.GetEdgeData(onto_edge).reversed && + !onto_data.reversed && // we are not turning over a barrier (!is_barrier_node || from_node == to_node) && // We are at an only_-restriction but not at the right turn. @@ -93,8 +98,17 @@ Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node, !restriction_map.CheckIfTurnIsRestricted(from_node, turn_node, to_node); auto angle = 0.; + double bearing = 0.; + + // The first coordinate (the origin) can depend on the number of lanes turning onto, + // just as the target coordinate can. Here we compute the corrected coordinate for the + // incoming edge. + const auto first_coordinate = coordinate_extractor.GetCoordinateAlongRoad( + from_node, via_eid, INVERT, turn_node, intersection_lanes); + if (from_node == to_node) { + bearing = util::coordinate_calculation::bearing(turn_coordinate, first_coordinate); uturn_could_be_valid = turn_is_valid; if (turn_is_valid && !is_barrier_node) { @@ -121,33 +135,48 @@ Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node, } else { - // unpack first node of second segment if packed - const auto first_coordinate = getRepresentativeCoordinate( - from_node, turn_node, via_eid, INVERT, compressed_edge_container, node_info_list); - const auto third_coordinate = getRepresentativeCoordinate( - turn_node, to_node, onto_edge, !INVERT, compressed_edge_container, node_info_list); + // the default distance we lookahead on a road. This distance prevents small mapping + // errors to impact the turn angles. + const auto third_coordinate = coordinate_extractor.GetCoordinateAlongRoad( + turn_node, onto_edge, !INVERT, to_node, intersection_lanes); + angle = util::coordinate_calculation::computeAngle( - first_coordinate, node_info_list[turn_node], third_coordinate); + first_coordinate, turn_coordinate, third_coordinate); + + bearing = util::coordinate_calculation::bearing(turn_coordinate, third_coordinate); + if (std::abs(angle) < std::numeric_limits::epsilon()) has_uturn_edge = true; } - intersection.push_back( ConnectedRoad(TurnOperation{onto_edge, angle, + bearing, {TurnType::Invalid, DirectionModifier::UTurn}, INVALID_LANE_DATAID}, turn_is_valid)); } - // We hit the case of a street leading into nothing-ness. Since the code here assumes that this + // We hit the case of a street leading into nothing-ness. Since the code here assumes + // that this // will never happen we add an artificial invalid uturn in this case. if (!has_uturn_edge) { - intersection.push_back( - {TurnOperation{ - via_eid, 0., {TurnType::Invalid, DirectionModifier::UTurn}, INVALID_LANE_DATAID}, - false}); + const auto first_coordinate = coordinate_extractor.GetCoordinateAlongRoad( + from_node, + via_eid, + INVERT, + turn_node, + node_based_graph.GetEdgeData(via_eid).road_classification.GetNumberOfLanes()); + const double bearing = + util::coordinate_calculation::bearing(turn_coordinate, first_coordinate); + + intersection.push_back({TurnOperation{via_eid, + 0., + bearing, + {TurnType::Invalid, DirectionModifier::UTurn}, + INVALID_LANE_DATAID}, + false}); } const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) { @@ -162,7 +191,8 @@ Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node, boost::count_if(intersection, [](const ConnectedRoad &road) { return road.entry_allowed; }); if (0 == valid_count && uturn_could_be_valid) { - // after intersections sorting by angles, find the u-turn with (from_node == to_node) + // after intersections sorting by angles, find the u-turn with (from_node == + // to_node) // that was inserted together with setting uturn_could_be_valid flag std::size_t self_u_turn = 0; while (self_u_turn < intersection.size() && @@ -175,7 +205,6 @@ Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node, BOOST_ASSERT(from_node == node_based_graph.GetTarget(intersection[self_u_turn].turn.eid)); intersection[self_u_turn].entry_allowed = true; } - return intersection; } @@ -219,15 +248,21 @@ bool IntersectionGenerator::CanMerge(const NodeID node_at_intersection, const auto angle_between = angularDeviation(intersection[first_index].turn.angle, intersection[second_index].turn.angle); - const auto coordinate_at_in_edge = - getRepresentativeCoordinate(node_at_intersection, - node_based_graph.GetTarget(intersection[0].turn.eid), - intersection[0].turn.eid, - false, - compressed_edge_container, - node_info_list); + const auto intersection_lanes = + getLaneCountAtIntersection(node_at_intersection, node_based_graph); + + const auto coordinate_at_in_edge = coordinate_extractor.GetCoordinateAlongRoad( + node_at_intersection, + intersection[0].turn.eid, + !INVERT, + node_based_graph.GetTarget(intersection[0].turn.eid), + intersection_lanes); + const auto coordinate_at_intersection = node_info_list[node_at_intersection]; + if (angle_between >= 120) + return false; + const auto isValidYArm = [this, intersection, coordinate_at_in_edge, @@ -253,20 +288,19 @@ bool IntersectionGenerator::CanMerge(const NodeID node_at_intersection, coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_target); const auto other_turn_angle = util::coordinate_calculation::computeAngle( coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_other_target); - const double distance_to_target = util::coordinate_calculation::haversineDistance( - coordinate_at_intersection, coordinate_at_target); - - const constexpr double MAX_COLLAPSE_DISTANCE = 30; - if (distance_to_target < MAX_COLLAPSE_DISTANCE) - return false; const bool becomes_narrower = angularDeviation(turn_angle, other_turn_angle) < NARROW_TURN_ANGLE && - angularDeviation(turn_angle, other_turn_angle) < + angularDeviation(turn_angle, other_turn_angle) <= angularDeviation(intersection[index].turn.angle, intersection[other_index].turn.angle); - return becomes_narrower; + const bool has_same_deviation = + std::abs(angularDeviation(intersection[index].turn.angle, STRAIGHT_ANGLE) - + angularDeviation(intersection[other_index].turn.angle, STRAIGHT_ANGLE)) < + MAXIMAL_ALLOWED_NO_TURN_DEVIATION; + + return becomes_narrower || has_same_deviation; }; const bool is_y_arm_first = isValidYArm(first_index, second_index); @@ -345,33 +379,37 @@ Intersection IntersectionGenerator::MergeSegregatedRoads(const NodeID intersecti return (index + intersection.size() - 1) % intersection.size(); }; - const auto merge = [](const ConnectedRoad &first, - const ConnectedRoad &second) -> ConnectedRoad { - if (!first.entry_allowed) - { - ConnectedRoad result = second; - result.turn.angle = (first.turn.angle + second.turn.angle) / 2; - if (first.turn.angle - second.turn.angle > 180) - result.turn.angle += 180; - if (result.turn.angle > 360) - result.turn.angle -= 360; + // we only merge small angles. If the difference between both is large, we are looking at a + // bearing leading north. Such a bearing cannot be handled via the basic average. In this + // case we actually need to shift the bearing by half the difference. + const auto aroundZero = [](const double first, const double second) { + return (std::max(first, second) - std::min(first, second)) >= 180; + }; - return result; - } + // find the angle between two other angles + const auto combineAngles = [aroundZero](const double first, const double second) { + if (!aroundZero(first, second)) + return .5 * (first + second); else { - BOOST_ASSERT(!second.entry_allowed); - ConnectedRoad result = first; - result.turn.angle = (first.turn.angle + second.turn.angle) / 2; - - if (first.turn.angle - second.turn.angle > 180) - result.turn.angle += 180; - if (result.turn.angle > 360) - result.turn.angle -= 360; - - return result; + const auto offset = angularDeviation(first, second); + auto new_angle = std::max(first, second) + .5 * offset; + if (new_angle > 360) + return new_angle - 360; + return new_angle; } }; + + const auto merge = [combineAngles](const ConnectedRoad &first, + const ConnectedRoad &second) -> ConnectedRoad { + ConnectedRoad result = first.entry_allowed ? first : second; + result.turn.angle = combineAngles(first.turn.angle, second.turn.angle); + result.turn.bearing = combineAngles(first.turn.bearing, second.turn.bearing); + BOOST_ASSERT(0 <= result.turn.angle && result.turn.angle <= 360.0); + BOOST_ASSERT(0 <= result.turn.bearing && result.turn.bearing <= 360.0); + return result; + }; + if (intersection.size() <= 1) return intersection; @@ -426,10 +464,10 @@ Intersection IntersectionGenerator::MergeSegregatedRoads(const NodeID intersecti for (std::size_t i = 1; i + 1 < intersection.size(); ++i) intersection[i].turn.angle += correction_factor; - // FIXME if we have a left-sided country, we need to switch this off and enable it below + // FIXME if we have a left-sided country, we need to switch this off and enable it + // below intersection[0] = merge(intersection.front(), intersection.back()); intersection[0].turn.angle = 0; - intersection.pop_back(); } else if (CanMerge(intersection_node, intersection, 0, 1)) @@ -511,14 +549,6 @@ Intersection IntersectionGenerator::AdjustForJoiningRoads(const NodeID node_at_i for (std::size_t index = 1; index < intersection.size(); ++index) { auto &road = intersection[index]; - // to find out about the above situation, we need to look at the next intersection (at d in - // the example). If the initial road can be merged to the left/right, we are about to adjust - // the angle. - const auto next_intersection_along_road = - GetConnectedRoads(node_at_intersection, road.turn.eid); - if (next_intersection_along_road.size() <= 1) - continue; - const auto node_at_next_intersection = node_based_graph.GetTarget(road.turn.eid); const util::Coordinate coordinate_at_next_intersection = node_info_list[node_at_next_intersection]; @@ -535,6 +565,16 @@ Intersection IntersectionGenerator::AdjustForJoiningRoads(const NodeID node_at_i return angle; }; + const auto range = node_based_graph.GetAdjacentEdgeRange(node_at_next_intersection); + if (range.size() <= 1) + continue; + + // to find out about the above situation, we need to look at the next intersection (at d in + // the example). If the initial road can be merged to the left/right, we are about to adjust + // the angle. + const auto next_intersection_along_road = + GetConnectedRoads(node_at_intersection, road.turn.eid); + // check if the u-turn edge at the next intersection could be merged to the left/right. If // this is the case and the road is not far away (see previous distance check), if // influences the perceived angle. @@ -545,6 +585,7 @@ Intersection IntersectionGenerator::AdjustForJoiningRoads(const NodeID node_at_i // at the target intersection, we merge to the right, so we need to shift the current // angle to the left road.turn.angle = adjustAngle(road.turn.angle, offset); + road.turn.bearing = adjustAngle(road.turn.bearing, offset); } else if (CanMerge(node_at_next_intersection, next_intersection_along_road, @@ -559,6 +600,7 @@ Intersection IntersectionGenerator::AdjustForJoiningRoads(const NodeID node_at_i // at the target intersection, we merge to the left, so we need to shift the current // angle to the right road.turn.angle = adjustAngle(road.turn.angle, -offset); + road.turn.bearing = adjustAngle(road.turn.bearing, -offset); } } return intersection; diff --git a/src/extractor/guidance/intersection_handler.cpp b/src/extractor/guidance/intersection_handler.cpp index 9598c7a1a..9b2322ca1 100644 --- a/src/extractor/guidance/intersection_handler.cpp +++ b/src/extractor/guidance/intersection_handler.cpp @@ -395,18 +395,6 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, const EdgeData &in_data = node_based_graph.GetEdgeData(via_edge); const auto in_classification = in_data.road_classification; - const auto obvious_by_road_class = [](const RoadClassification in_classification, - const RoadClassification obvious_candidate, - const RoadClassification compare_candidate) { - const bool has_high_priority = - PRIORITY_DISTINCTION_FACTOR * obvious_candidate.GetPriority() < - compare_candidate.GetPriority(); - const bool continues_on_same_class = in_classification == obvious_candidate; - return (has_high_priority && continues_on_same_class) || - (!obvious_candidate.IsLowPriorityRoadClass() && - compare_candidate.IsLowPriorityRoadClass()); - }; - for (std::size_t i = 1; i < intersection.size(); ++i) { const double deviation = angularDeviation(intersection[i].turn.angle, STRAIGHT_ANGLE); @@ -434,15 +422,19 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, node_based_graph.GetEdgeData(intersection[best_continue].turn.eid).road_classification; // don't prefer low priority classes - if (out_data.road_classification.IsLowPriorityRoadClass() && + if (best != 0 && out_data.road_classification.IsLowPriorityRoadClass() && !current_best_class.IsLowPriorityRoadClass()) continue; - const bool is_better_choice_by_priority = obvious_by_road_class( - in_data.road_classification, out_data.road_classification, current_best_class); + const bool is_better_choice_by_priority = + best == 0 || obviousByRoadClass(in_data.road_classification, + out_data.road_classification, + current_best_class); - const bool other_is_better_choice_by_priority = obvious_by_road_class( - in_data.road_classification, current_best_class, out_data.road_classification); + const bool other_is_better_choice_by_priority = + best != 0 && obviousByRoadClass(in_data.road_classification, + current_best_class, + out_data.road_classification); if ((!other_is_better_choice_by_priority && deviation < best_deviation) || is_better_choice_by_priority) @@ -503,11 +495,53 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, // has no obvious continued road const auto &best_data = node_based_graph.GetEdgeData(intersection[best].turn.eid); - if (best_continue == 0 || (!all_continues_are_narrow && - (num_continue_names.first >= 2 && intersection.size() >= 4)) || - (num_continue_names.second >= 2 && best_continue_deviation >= 2 * NARROW_TURN_ANGLE) || - (best_deviation != best_continue_deviation && best_deviation < FUZZY_ANGLE_DIFFERENCE && - !best_data.road_classification.IsRampClass())) + + const auto check_non_continue = [&]() { + // no continue road exists + if (best_continue == 0) + return true; + + // we have multiple continues and not all are narrow (treat all the same) + if (!all_continues_are_narrow && + (num_continue_names.first >= 2 && intersection.size() >= 4)) + return true; + + // if the best continue is not narrow and we also have at least 2 possible choices, the + // intersection size does not matter anymore + if (num_continue_names.second >= 2 && best_continue_deviation >= 2 * NARROW_TURN_ANGLE) + return true; + + // continue data now most certainly exists + const auto &continue_data = + node_based_graph.GetEdgeData(intersection[best_continue].turn.eid); + + if (obviousByRoadClass(in_data.road_classification, + continue_data.road_classification, + best_data.road_classification)) + return false; + + if (obviousByRoadClass(in_data.road_classification, + best_data.road_classification, + continue_data.road_classification)) + return true; + + // the best deviation is very straight and not a ramp + if (best_deviation < best_continue_deviation && best_deviation < FUZZY_ANGLE_DIFFERENCE && + !best_data.road_classification.IsRampClass()) + return true; + + // the continue road is of a lower priority, while the road continues on the same priority + // with a better angle + if (best_deviation < best_continue_deviation && + in_data.road_classification == best_data.road_classification && + continue_data.road_classification.GetPriority() > + best_data.road_classification.GetPriority()) + return true; + + return false; + }(); + + if (check_non_continue) { // Find left/right deviation // skipping over service roads @@ -517,9 +551,9 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, return index_candidate; const auto &candidate_data = node_based_graph.GetEdgeData(intersection[index_candidate].turn.eid); - if (obvious_by_road_class(in_data.road_classification, - best_data.road_classification, - candidate_data.road_classification)) + if (obviousByRoadClass(in_data.road_classification, + best_data.road_classification, + candidate_data.road_classification)) return (index_candidate + 1) % intersection.size(); else return index_candidate; @@ -532,9 +566,9 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, return index_candidate; const auto candidate_data = node_based_graph.GetEdgeData(intersection[index_candidate].turn.eid); - if (obvious_by_road_class(in_data.road_classification, - best_data.road_classification, - candidate_data.road_classification)) + if (obviousByRoadClass(in_data.road_classification, + best_data.road_classification, + candidate_data.road_classification)) return index_candidate - 1; else return index_candidate; @@ -553,13 +587,13 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, const auto &right_data = node_based_graph.GetEdgeData(intersection[right_index].turn.eid); const bool obvious_to_left = - left_index == 0 || obvious_by_road_class(in_data.road_classification, - best_data.road_classification, - left_data.road_classification); + left_index == 0 || obviousByRoadClass(in_data.road_classification, + best_data.road_classification, + left_data.road_classification); const bool obvious_to_right = - right_index == 0 || obvious_by_road_class(in_data.road_classification, - best_data.road_classification, - right_data.road_classification); + right_index == 0 || obviousByRoadClass(in_data.road_classification, + best_data.road_classification, + right_data.road_classification); // if the best turn isn't narrow, but there is a nearly straight turn, we don't consider the // turn obvious @@ -624,9 +658,9 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, const auto &turn_data = node_based_graph.GetEdgeData(intersection[i].turn.eid); const bool is_obvious_by_road_class = - obvious_by_road_class(in_data.road_classification, - continue_data.road_classification, - turn_data.road_classification); + obviousByRoadClass(in_data.road_classification, + continue_data.road_classification, + turn_data.road_classification); // if the main road is obvious by class, we ignore the current road as a potential // prevention of obviousness diff --git a/src/extractor/guidance/motorway_handler.cpp b/src/extractor/guidance/motorway_handler.cpp index d9ed02c08..935c3fb5d 100644 --- a/src/extractor/guidance/motorway_handler.cpp +++ b/src/extractor/guidance/motorway_handler.cpp @@ -494,12 +494,6 @@ Intersection MotorwayHandler::fallback(Intersection intersection) const { for (auto &road : intersection) { - const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); - - util::SimpleLogger().Write(logDEBUG) - << "road: " << toString(road) << " Name: " << out_data.name_id - << " Road Class: " << out_data.road_classification.ToString(); - if (!road.entry_allowed) continue; diff --git a/src/extractor/guidance/roundabout_handler.cpp b/src/extractor/guidance/roundabout_handler.cpp index 2b890e3c3..55cf94e08 100644 --- a/src/extractor/guidance/roundabout_handler.cpp +++ b/src/extractor/guidance/roundabout_handler.cpp @@ -32,7 +32,8 @@ RoundaboutHandler::RoundaboutHandler(const util::NodeBasedDynamicGraph &node_bas name_table, street_name_suffix_table, intersection_generator), - compressed_edge_container(compressed_edge_container), profile_properties(profile_properties) + compressed_edge_container(compressed_edge_container), profile_properties(profile_properties), + coordinate_extractor(node_based_graph, compressed_edge_container, node_info_list) { } @@ -180,13 +181,14 @@ bool RoundaboutHandler::qualifiesAsRoundaboutIntersection( // there is a single non-roundabout edge const auto src_coordinate = getCoordinate(node); - const auto next_coordinate = - getRepresentativeCoordinate(node, - node_based_graph.GetTarget(edge), - edge, - edge_data.reversed, - compressed_edge_container, - node_info_list); + + const auto next_coordinate = coordinate_extractor.GetCoordinateAlongRoad( + node, + edge, + edge_data.reversed, + node_based_graph.GetTarget(edge), + getLaneCountAtIntersection(node, node_based_graph)); + result.push_back( util::coordinate_calculation::bearing(src_coordinate, next_coordinate)); break; diff --git a/src/extractor/guidance/turn_classification.cpp b/src/extractor/guidance/turn_classification.cpp index 45ec22cef..5ca003266 100644 --- a/src/extractor/guidance/turn_classification.cpp +++ b/src/extractor/guidance/turn_classification.cpp @@ -27,49 +27,30 @@ struct TurnPossibility }; std::pair -classifyIntersection(NodeID nid, - const Intersection &intersection, - const util::NodeBasedDynamicGraph &node_based_graph, - const extractor::CompressedEdgeContainer &compressed_geometries, - const std::vector &query_nodes) +classifyIntersection(Intersection intersection) { if (intersection.empty()) return {}; - std::vector turns; - - const auto node_coordinate = util::Coordinate(query_nodes[nid].lon, query_nodes[nid].lat); - - // generate a list of all turn angles between a base edge, the node and a current edge - for (const auto &road : intersection) - { - const auto eid = road.turn.eid; - const auto edge_coordinate = getRepresentativeCoordinate( - nid, node_based_graph.GetTarget(eid), eid, false, compressed_geometries, query_nodes); - - const double bearing = - util::coordinate_calculation::bearing(node_coordinate, edge_coordinate); - turns.push_back({road.entry_allowed, bearing}); - } - - std::sort( - turns.begin(), turns.end(), [](const TurnPossibility left, const TurnPossibility right) { - return left.bearing < right.bearing; - }); + std::sort(intersection.begin(), + intersection.end(), + [](const ConnectedRoad &left, const ConnectedRoad &right) { + return left.turn.bearing < right.turn.bearing; + }); util::guidance::EntryClass entry_class; util::guidance::BearingClass bearing_class; const bool canBeDiscretized = [&]() { - if (turns.size() <= 1) + if (intersection.size() <= 1) return true; - DiscreteBearing last_discrete_bearing = - util::guidance::BearingClass::getDiscreteBearing(std::round(turns.back().bearing)); - for (const auto turn : turns) + DiscreteBearing last_discrete_bearing = util::guidance::BearingClass::getDiscreteBearing( + std::round(intersection.back().turn.bearing)); + for (const auto road : intersection) { const DiscreteBearing discrete_bearing = - util::guidance::BearingClass::getDiscreteBearing(std::round(turn.bearing)); + util::guidance::BearingClass::getDiscreteBearing(std::round(road.turn.bearing)); if (discrete_bearing == last_discrete_bearing) return false; last_discrete_bearing = discrete_bearing; @@ -81,18 +62,18 @@ classifyIntersection(NodeID nid, std::size_t number = 0; if (canBeDiscretized) { - if (util::guidance::BearingClass::getDiscreteBearing(turns.back().bearing) < - util::guidance::BearingClass::getDiscreteBearing(turns.front().bearing)) + if (util::guidance::BearingClass::getDiscreteBearing(intersection.back().turn.bearing) < + util::guidance::BearingClass::getDiscreteBearing(intersection.front().turn.bearing)) { - turns.insert(turns.begin(), turns.back()); - turns.pop_back(); + intersection.insert(intersection.begin(), intersection.back()); + intersection.pop_back(); } - for (const auto turn : turns) + for (const auto &road : intersection) { - if (turn.entry_allowed) + if (road.entry_allowed) entry_class.activate(number); auto discrete_bearing_class = - util::guidance::BearingClass::getDiscreteBearing(std::round(turn.bearing)); + util::guidance::BearingClass::getDiscreteBearing(std::round(road.turn.bearing)); bearing_class.add(std::round(discrete_bearing_class * util::guidance::BearingClass::discrete_step_size)); ++number; @@ -100,11 +81,11 @@ classifyIntersection(NodeID nid, } else { - for (const auto turn : turns) + for (const auto &road : intersection) { - if (turn.entry_allowed) + if (road.entry_allowed) entry_class.activate(number); - bearing_class.add(std::round(turn.bearing)); + bearing_class.add(std::round(road.turn.bearing)); ++number; } } diff --git a/src/extractor/guidance/turn_handler.cpp b/src/extractor/guidance/turn_handler.cpp index 75ef5c609..f2ee0496f 100644 --- a/src/extractor/guidance/turn_handler.cpp +++ b/src/extractor/guidance/turn_handler.cpp @@ -136,7 +136,7 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection OOOOOOO */ const auto fork_range = findFork(via_edge, intersection); - if (fork_range.first == 1 && fork_range.second == 2) + if (fork_range.first == 1 && fork_range.second == 2 && obvious_index == 0) assignFork(via_edge, intersection[2], intersection[1]); /* T Intersection @@ -525,6 +525,7 @@ std::pair TurnHandler::findFork(const EdgeID via_edge, return false; }(); + // A fork can only happen between edges of similar types where none of the ones is obvious const bool has_compatible_classes = [&]() { const bool ramp_class = node_based_graph.GetEdgeData(intersection[right].turn.eid) .road_classification.IsLinkClass(); @@ -533,6 +534,27 @@ std::pair TurnHandler::findFork(const EdgeID via_edge, node_based_graph.GetEdgeData(intersection[index].turn.eid) .road_classification.IsLinkClass()) return false; + + const auto in_classification = + node_based_graph.GetEdgeData(intersection[0].turn.eid).road_classification; + for (std::size_t base_index = right; base_index <= left; ++base_index) + { + const auto base_classification = + node_based_graph.GetEdgeData(intersection[base_index].turn.eid) + .road_classification; + for (std::size_t compare_index = right; compare_index <= left; ++compare_index) + { + if (base_index == compare_index) + continue; + + const auto compare_classification = + node_based_graph.GetEdgeData(intersection[compare_index].turn.eid) + .road_classification; + if (obviousByRoadClass( + in_classification, base_classification, compare_classification)) + return false; + } + } return true; }(); @@ -657,20 +679,20 @@ void TurnHandler::handleDistinctConflict(const EdgeID via_edge, if (getTurnDirection(left.turn.angle) == DirectionModifier::Right) { - if (angularDeviation(left.turn.angle, 90) > angularDeviation(right.turn.angle, 90)) - { - left.turn.instruction = {left_type, DirectionModifier::SlightRight}; - right.turn.instruction = {right_type, DirectionModifier::Right}; - } - else + if (angularDeviation(left.turn.angle, 85) >= angularDeviation(right.turn.angle, 85)) { left.turn.instruction = {left_type, DirectionModifier::Right}; right.turn.instruction = {right_type, DirectionModifier::SharpRight}; } + else + { + left.turn.instruction = {left_type, DirectionModifier::SlightRight}; + right.turn.instruction = {right_type, DirectionModifier::Right}; + } } else { - if (angularDeviation(left.turn.angle, 270) > angularDeviation(right.turn.angle, 270)) + if (angularDeviation(left.turn.angle, 265) >= angularDeviation(right.turn.angle, 265)) { left.turn.instruction = {left_type, DirectionModifier::SharpLeft}; right.turn.instruction = {right_type, DirectionModifier::Left}; diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index 8fadfb018..299c3b640 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -169,7 +169,10 @@ void LuaScriptingEnvironment::InitContext(LuaScriptingContext &context) &guidance::RoadClassification::SetLowPriorityFlag) .property("road_priority_class", &guidance::RoadClassification::GetClass, - &guidance::RoadClassification::SetClass), + &guidance::RoadClassification::SetClass) + .property("num_lanes", + &guidance::RoadClassification::GetNumberOfLanes, + &guidance::RoadClassification::SetNumberOfLanes), luabind::class_("ResultWay") // .def(luabind::constructor<>()) diff --git a/src/storage/storage.cpp b/src/storage/storage.cpp index 1c883db55..085bc2060 100644 --- a/src/storage/storage.cpp +++ b/src/storage/storage.cpp @@ -242,6 +242,10 @@ Storage::ReturnCode Storage::Run(int max_wait) number_of_original_edges); shared_layout_ptr->SetBlockSize(SharedDataLayout::TRAVEL_MODE, number_of_original_edges); + shared_layout_ptr->SetBlockSize(SharedDataLayout::PRE_TURN_BEARING, + number_of_original_edges); + shared_layout_ptr->SetBlockSize( + SharedDataLayout::POST_TURN_BEARING, number_of_original_edges); shared_layout_ptr->SetBlockSize( SharedDataLayout::TURN_INSTRUCTION, number_of_original_edges); shared_layout_ptr->SetBlockSize(SharedDataLayout::LANE_DATA_ID, @@ -550,6 +554,12 @@ Storage::ReturnCode Storage::Run(int max_wait) extractor::TravelMode *travel_mode_ptr = shared_layout_ptr->GetBlockPtr(shared_memory_ptr, SharedDataLayout::TRAVEL_MODE); + util::guidance::TurnBearing *pre_turn_bearing_ptr = + shared_layout_ptr->GetBlockPtr( + shared_memory_ptr, SharedDataLayout::PRE_TURN_BEARING); + util::guidance::TurnBearing *post_turn_bearing_ptr = + shared_layout_ptr->GetBlockPtr( + shared_memory_ptr, SharedDataLayout::POST_TURN_BEARING); LaneDataID *lane_data_id_ptr = shared_layout_ptr->GetBlockPtr( shared_memory_ptr, SharedDataLayout::LANE_DATA_ID); @@ -571,6 +581,8 @@ Storage::ReturnCode Storage::Run(int max_wait) lane_data_id_ptr[i] = current_edge_data.lane_data_id; turn_instructions_ptr[i] = current_edge_data.turn_instruction; entry_class_id_ptr[i] = current_edge_data.entry_classid; + pre_turn_bearing_ptr[i] = current_edge_data.pre_turn_bearing; + post_turn_bearing_ptr[i] = current_edge_data.post_turn_bearing; } edges_input_stream.close(); @@ -774,20 +786,24 @@ Storage::ReturnCode Storage::Run(int max_wait) static_cast(data_type_memory->Ptr()); { - boost::interprocess::scoped_lock + boost::interprocess::scoped_lock current_regions_exclusive_lock; if (max_wait > 0) { - util::SimpleLogger().Write() << "Waiting for " << max_wait << " seconds to write new dataset timestamp"; - auto end_time = boost::posix_time::microsec_clock::universal_time() + boost::posix_time::seconds(max_wait); + util::SimpleLogger().Write() << "Waiting for " << max_wait + << " seconds to write new dataset timestamp"; + auto end_time = boost::posix_time::microsec_clock::universal_time() + + boost::posix_time::seconds(max_wait); current_regions_exclusive_lock = boost::interprocess::scoped_lock( std::move(current_regions_lock), end_time); if (!current_regions_exclusive_lock.owns()) { - util::SimpleLogger().Write(logWARNING) << "Aquiring the lock timed out after " << max_wait << " seconds. Claiming the lock by force."; + util::SimpleLogger().Write(logWARNING) << "Aquiring the lock timed out after " + << max_wait + << " seconds. Claiming the lock by force."; current_regions_lock.unlock(); current_regions_lock.release(); storage::SharedBarriers::resetCurrentRegions(); diff --git a/src/util/coordinate_calculation.cpp b/src/util/coordinate_calculation.cpp index 9dff34a1c..5d9090939 100644 --- a/src/util/coordinate_calculation.cpp +++ b/src/util/coordinate_calculation.cpp @@ -292,6 +292,29 @@ Coordinate interpolateLinear(double factor, const Coordinate from, const Coordin return {std::move(interpolated_lon), std::move(interpolated_lat)}; } +// compute the signed area of a triangle +double signedArea(const Coordinate first_coordinate, + const Coordinate second_coordinate, + const Coordinate third_coordinate) +{ + const auto lat_1 = static_cast(toFloating(first_coordinate.lat)); + const auto lon_1 = static_cast(toFloating(first_coordinate.lon)); + const auto lat_2 = static_cast(toFloating(second_coordinate.lat)); + const auto lon_2 = static_cast(toFloating(second_coordinate.lon)); + const auto lat_3 = static_cast(toFloating(third_coordinate.lat)); + const auto lon_3 = static_cast(toFloating(third_coordinate.lon)); + return 0.5 * (-lon_2 * lat_1 + lon_3 * lat_1 + lon_1 * lat_2 - lon_3 * lat_2 - lon_1 * lat_3 + + lon_2 * lat_3); +} + +// check if a set of three coordinates is given in CCW order +bool isCCW(const Coordinate first_coordinate, + const Coordinate second_coordinate, + const Coordinate third_coordinate) +{ + return signedArea(first_coordinate, second_coordinate, third_coordinate) > 0; +} + } // ns coordinate_calculation } // ns util } // ns osrm diff --git a/src/util/guidance/turn_bearing.cpp b/src/util/guidance/turn_bearing.cpp new file mode 100644 index 000000000..a92748065 --- /dev/null +++ b/src/util/guidance/turn_bearing.cpp @@ -0,0 +1,24 @@ +#include "util/guidance/turn_bearing.hpp" + +#include + +namespace osrm +{ +namespace util +{ +namespace guidance +{ + +constexpr double bearing_scale = 360.0 / 256.0; + +// discretizes a bearing into distinct units of 1.4 degrees +TurnBearing::TurnBearing(const double value) : bearing(value / bearing_scale) +{ + BOOST_ASSERT_MSG(value >= 0 && value <= 360.0, "Bearing value needs to be between 0 and 360"); +} + +double TurnBearing::Get() const { return bearing * bearing_scale; } + +} // namespace guidance +} // namespace util +} // namespace osrm diff --git a/taginfo.json b/taginfo.json index 2513279e6..4e1770a02 100644 --- a/taginfo.json +++ b/taginfo.json @@ -209,6 +209,8 @@ {"key": "highway", "value": "default"}, {"key": "width", "description": "Penalties for narrow streets"}, {"key": "lanes", "description": "Penalties for shared single lane streets"}, + {"key": "lanes:forward", "description": "Lanes in forward direction"}, + {"key": "lanes:backward", "description": "Lanes in backward direction"}, {"key": "surface", "value": "asphalt"}, {"key": "surface", "value": "concrete"}, {"key": "surface", "value": "concrete:plates"}, diff --git a/unit_tests/mocks/mock_datafacade.hpp b/unit_tests/mocks/mock_datafacade.hpp index b2c754514..ca448954c 100644 --- a/unit_tests/mocks/mock_datafacade.hpp +++ b/unit_tests/mocks/mock_datafacade.hpp @@ -9,6 +9,7 @@ #include "engine/datafacade/datafacade_base.hpp" #include "util/guidance/bearing_class.hpp" #include "util/guidance/entry_class.hpp" +#include "util/guidance/turn_bearing.hpp" #include "util/typedefs.hpp" namespace osrm @@ -193,7 +194,7 @@ class MockDataFacade final : public engine::datafacade::BaseDataFacade const int /*bearing_range*/) const override { return {}; - }; + } unsigned GetCheckSum() const override { return 0; } bool IsCoreNode(const NodeID /* id */) const override { return false; } @@ -205,9 +206,18 @@ class MockDataFacade final : public engine::datafacade::BaseDataFacade std::size_t GetCoreSize() const override { return 0; } std::string GetTimestamp() const override { return ""; } bool GetContinueStraightDefault() const override { return true; } - BearingClassID GetBearingClassID(const NodeID /*id*/) const override { return 0; }; + BearingClassID GetBearingClassID(const NodeID /*id*/) const override { return 0; } EntryClassID GetEntryClassID(const EdgeID /*id*/) const override { return 0; } + util::guidance::TurnBearing PreTurnBearing(const EdgeID /*eid*/) const override final + { + return util::guidance::TurnBearing{0.0}; + } + util::guidance::TurnBearing PostTurnBearing(const EdgeID /*eid*/) const override final + { + return util::guidance::TurnBearing{0.0}; + } + bool hasLaneData(const EdgeID /*id*/) const override final { return true; }; util::guidance::LaneTupleIdPair GetLaneData(const EdgeID /*id*/) const override final {