refactor merging of segregated roads

adjust to generalFindMaximum function
moved parallel detection to ratio/absolute based regression testing
considerably improved detection quality using normalised regression lines
only follow initial direction/narrow turns for parallel detection
This commit is contained in:
Moritz Kobitzsch 2016-12-06 13:22:51 +01:00
parent f7ad2e1e26
commit e6ff17ab2a
40 changed files with 2397 additions and 949 deletions

View File

@ -20,6 +20,7 @@ A guard (ScopedGeojsonLoggerGuard) requires a logging policy. Per default we pro
The initialisation to do so looks like this:
`util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString> geojson_guard( "debug.geojson", data-for-conversion);`
Make sure to give the guar a name, so it actually gets a lifetime.
The field `data-for-conversion` can be an arbitrary long set of features and needs to match the parameters used for constructing our policy (in this case `util::NodeIdVectorToLineString`).

View File

@ -144,36 +144,32 @@ Feature: Collapse
Given the node map
"""
n m
| |
| |
| |
| |
| |
g h
c b a
d e f
c - b - a
d - e - f
j i
| |
| |
| |
| |
| |
k l
"""
And the ways
| nodes | highway | name | oneway |
| ab | primary | first | yes |
| bc | primary | first | yes |
| de | primary | first | yes |
| ef | primary | first | yes |
| be | primary | first | no |
| ngbhm | primary | second | yes |
| liejk | primary | second | yes |
| nodes | highway | name | oneway | lanes |
| ab | primary | first | yes | |
| bc | primary | first | yes | |
| de | primary | first | yes | |
| ef | primary | first | yes | |
| be | primary | first | no | |
| ngbhm | primary | second | yes | 5 |
| liejk | primary | second | yes | 5 |
When I route I should get
| waypoints | route | turns |
@ -198,32 +194,36 @@ Feature: Collapse
Given the node map
"""
n m
| |
| |
| |
| |
| |
| |
g h
c b a
d e f
\ /
c - b - a
d - e - f
/ \
j i
| |
| |
| |
| |
| |
| |
k l
"""
And the ways
| nodes | highway | name | oneway |
| ab | primary | first | yes |
| bc | primary | first | yes |
| de | primary | first | yes |
| ef | primary | first | yes |
| be | primary | second | no |
| ngbhm | primary | second | yes |
| liejk | primary | second | yes |
| nodes | highway | name | oneway | lanes |
| ab | primary | first | yes | |
| bc | primary | first | yes | |
| de | primary | first | yes | |
| ef | primary | first | yes | |
| be | primary | second | no | |
| ngbhm | primary | second | yes | 5 |
| liejk | primary | second | yes | 5 |
When I route I should get
| waypoints | route | turns |
@ -359,7 +359,7 @@ Feature: Collapse
| a,g | first,second,second | depart,turn left,arrive |
| d,g | first,second,second | depart,turn right,arrive |
| g,f | second,first,first | depart,turn right,arrive |
| g,c | second,first,first | depart,turn left,arrive |
| g,c | second,first,first | depart,end of road left,arrive |
Scenario: Do not collapse turning roads
Given the node map
@ -425,18 +425,28 @@ Feature: Collapse
Scenario: Pankenbruecke
Given the node map
"""
j h i
b c d e f g
k a
k j
| |
| |
| |
a h
b
c
d
e
f-i
|
|
|
g
"""
And the ways
| nodes | highway | name | oneway |
| kabhj | primary | inroad | yes |
| bc | primary | inroad | no |
| cd | primary | bridge | no |
| defg | primary | outroad | no |
| fi | primary | cross | no |
| nodes | highway | name | oneway | lanes |
| kabhj | primary | inroad | yes | 4 |
| bc | primary | inroad | no | |
| cd | primary | bridge | no | |
| defg | primary | outroad | no | |
| fi | primary | cross | no | |
When I route I should get
| waypoints | route | turns |

View File

@ -0,0 +1,525 @@
@guidance @merge-segregated
Feature: Merge Segregated Roads
Background:
Given the profile "car"
Given a grid size of 3 meters
#http://www.openstreetmap.org/#map=18/52.49950/13.33916
@negative
Scenario: oneway link road
Given the node map
"""
f - - - - - - -_-_e - - - - d
...''
a - - - b'- - - - - - - - - c
"""
And the ways
| nodes | name | oneway |
| abc | road | yes |
| def | road | yes |
| be | road | yes |
When I route I should get
| waypoints | route | intersections |
| a,c | road,road | true:90,true:75 true:90 false:270;true:270 |
| d,f | road,road | true:270,false:90 false:255 true:270;true:90 |
#http://www.openstreetmap.org/#map=18/52.48337/13.36184
@negative
Scenario: Square Area - Same Name as road for in/out
Given the node map
"""
i
|
|
|
g
/ \
/ \
/ \
/ \
/ \
a - - - - c e - - - - f
\ /
\ /
\ /
\ /
\ /
d
|
|
|
j
"""
And the ways
| nodes | name | oneway |
| ac | road | no |
| ef | road | no |
| cdegc | road | yes |
| ig | top | no |
| jd | bot | no |
When I route I should get
| waypoints | route | intersections |
| a,f | road,road,road,road | true:90,false:45 true:135 false:270;true:45 true:180 false:315;true:90 false:225 true:315;true:270 |
#https://www.openstreetmap.org/#map=19/52.50003/13.33915
@negative
Scenario: Short Segment due to different roads
Given the node map
"""
. d
. '
. '
. '
. '
a - - - - - - - b - - c - - - - - - e
. .
. .
. .
. .
.
f
|
|
|
|
g
"""
And the ways
| nodes | name | oneway |
| abce | pass | no |
| db | pass | yes |
| fg | aug | no |
| bfc | aug | yes |
When I route I should get
| waypoints | route | intersections |
| a,e | pass,pass | true:90,false:60 true:90 true:165 false:270,true:90 false:195 false:270;true:270 |
@negative
Scenario: Tripple Merge should not be possible
Given the node map
"""
. f - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - g
.
a - - - - b - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - e
'
' c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - d
"""
And the ways
| nodes | name | oneway |
| ab | in | no |
| gfb | merge | yes |
| be | merge | yes |
| dcb | merge | yes |
When I route I should get
| waypoints | route | intersections |
| a,e | in,merge,merge | true:90;false:60 true:90 false:120 false:270;true:270 |
Scenario: Tripple Merge should not be possible
Given the node map
"""
. f - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - g
.
a - - - - b - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - e
'
' c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - d
"""
And the ways
| nodes | name | oneway |
| ab | in | no |
| gfb | merge | yes |
| eb | merge | yes |
| bcd | merge | yes |
When I route I should get
| waypoints | route | intersections |
| a,d | in,merge,merge | true:90;false:60 false:90 true:120 false:270;true:270 |
@negative
Scenario: Don't accept turn-restrictions
Given the node map
"""
c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - d
/ \
a - - - b g - - h
\ /
e - - - - - - - - - - - - - - - - - - - - - - - - - - - - - f
"""
And the ways
| nodes | name | oneway |
| ab | road | yes |
| befgh | road | yes |
| bcdg | road | yes |
# This is an artificial scenario - not reasonable. It is only to test the merging on turn-restrictions
And the relations
| type | way:from | way:to | node:via | restriction |
| restriction | ab | bcdg | b | no_left_turn |
When I route I should get
| waypoints | route | intersections |
| a,h | road,road | true:90,false:60 true:120 false:270,true:90 false:240 false:300;true:270 |
@negative
Scenario: Actual Turn into segregated ways
Given the node map
"""
a - - - b - < - < - < - < - < - < - < - < - < - < - < c -
| \
| |
| |
d |
\ |
\ |
e > - > - > - > - > - > - > - > - > - > - > f - - - - - - g
"""
And the ways
| nodes | name | oneway |
| ab | road | no |
| fcb | road | yes |
| bdef | road | yes |
| fg | road | no |
When I route I should get
| waypoints | route | intersections |
| a,g | road,road | true:90,false:90 true:150 false:270,true:90 false:270 true:345;true:270 |
Scenario: Merging parallel roads with intermediate bridges
# https://www.mapillary.com/app/?lat=52.466483333333336&lng=13.431908333333332&z=17&focus=photo&pKey=LWXnKqoGqUNLnG0lofiO0Q
# http://www.openstreetmap.org/#map=19/52.46750/13.43171
Given the node map
"""
f
|
.e.
/ \
/ \
g d
| |
| |
| |
| |
| |
| |
| |
| |
h c
\ /
\ /
\ /
b
|
a
|
|
r - x - s
|
|
y
"""
And the ways
| nodes | name | highway | oneway | lanes |
| ab | Hermannstr | secondary | | 2 |
| bc | Hermannstr | secondary | yes | 2 |
| cd | Hermannbruecke | secondary | yes | 2 |
| de | Hermannstr | secondary | yes | 2 |
| ef | Hermannstr | secondary | | 4 |
| eg | Hermannstr | secondary | yes | 2 |
| gh | Hermannbruecke | secondary | yes | 2 |
| hb | Hermannstr | secondary | yes | 2 |
| xa | Hermannstr | secondary | | 4 |
| yx | Hermannstr | secondary | | 4 |
| rxs | Silbersteinstr | tertiary | | 1 |
And the nodes
| node | highway |
| x | traffic_signals |
#the intermediate intersections of degree two indicate short segments of new names. At some point, we probably want to get rid of these
When I route I should get
| waypoints | turns | route | intersections |
| a,f | depart,arrive | Hermannstr,Hermannstr | true:0,true:0 false:180,true:0 false:180;true:180 |
| f,a | depart,arrive | Hermannstr,Hermannstr | true:180,false:0 true:180,false:0 true:180;true:0 |
| y,f | depart,arrive | Hermannstr,Hermannstr | true:0,true:0 true:90 false:180 true:270,true:0 false:180,true:0 false:180;true:180 |
| f,y | depart,arrive | Hermannstr,Hermannstr | true:180,false:0 true:180,false:0 true:180,false:0 true:90 true:180 true:270;true:0 |
Scenario: Four Way Intersection Double Through Street Segregated
Given the node map
"""
q p
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
b c
\ /
\ /
\ /
j - - - - - - - - - - - - - - - - - i . \ / , d - - - - - - - - - - - - - - - - - o
. \/ .
> a <
. /\ '
. / \ '
k - - - - - - - - - - - - - - - - - h / \ e - - - - - - - - - - - - - - - - - n
/ \
/ \
g f
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
l m
"""
And the ways
| nodes | highway | oneway | name | lanes |
| khaij | primary | yes | first | 4 |
| odaen | primary | yes | first | 4 |
| qbacp | primary | yes | second | 4 |
| mfagl | primary | yes | second | 4 |
When I route I should get
| waypoints | route | turns |
| f,e | second,first,first | depart,turn right,arrive |
| f,c | second,second | depart,arrive |
| f,i | second,first,first | depart,turn left,arrive |
| f,g | second,second,second | depart,continue uturn,arrive |
| d,c | first,second,second | depart,turn right,arrive |
| d,i | first,first | depart,arrive |
| d,g | first,second,second | depart,turn left,arrive |
| d,e | first,first,first | depart,continue uturn,arrive |
| b,i | second,first,first | depart,turn right,arrive |
| b,g | second,second | depart,arrive |
| b,e | second,first,first | depart,turn left,arrive |
| b,c | second,second,second | depart,continue uturn,arrive |
| h,g | first,second,second | depart,turn right,arrive |
| h,e | first,first | depart,arrive |
| h,c | first,second,second | depart,turn left,arrive |
| h,i | first,first,first | depart,continue uturn,arrive |
Scenario: Middle Island Over Bridge
Given the node map
"""
a
|
.b.
c h
| |
| |
1 2
| |
d g
'e'
|
f
"""
And the ways
| nodes | name | oneway |
| ab | road | no |
| ef | road | no |
| bc | road | yes |
| cd | bridge | yes |
| de | road | yes |
| eg | road | yes |
| gh | bridge | yes |
| hb | road | yes |
When I route I should get
| waypoints | turns | route | intersections |
| a,f | depart,arrive | road,road | true:180,false:0 true:180,false:0 true:180;true:0 |
| c,f | depart,new name straight,arrive | bridge,road,road | true:180;false:0 true:180;true:0 |
| 1,f | depart,new name straight,arrive | bridge,road,road | true:180;false:0 true:180;true:0 |
| f,a | depart,arrive | road,road | true:0,true:0 false:180,true:0 false:180;true:180 |
| g,a | depart,new name straight,arrive | bridge,road,road | true:0;true:0 false:180;true:180 |
| 2,a | depart,new name straight,arrive | bridge,road,road | true:0;true:0 false:180;true:180 |
@negative
Scenario: Traffic Circle
Given the node map
"""
a - - - - b - - - e - - - c - - - - d
\ /
\ /
f
|
|
|
g
"""
And the ways
| nodes | name | oneway |
| ab | left | no |
| bfceb | circle | yes |
| fg | bottom | no |
| cd | right | no |
When I route I should get
| waypoints | route | intersections |
| a,d | left,circle,circle,right,right | true:90;false:90 true:120 false:270;true:60 true:180 false:300;true:90 false:240 true:270;true:270 |
| g,d | bottom,circle,right,right | true:0;true:60 false:180 false:300;true:90 false:240 true:270;true:270 |
Scenario: Middle Island
Given the node map
"""
a
|
b
c h
| |
| |
| |
| |
d g
e
|
f
"""
And the ways
| nodes | name | oneway |
| ab | road | no |
| ef | road | no |
| bcde | road | yes |
| eghb | road | yes |
When I route I should get
| waypoints | turns | route |
| a,f | depart,arrive | road,road |
| c,f | depart,arrive | road,road |
| f,a | depart,arrive | road,road |
| g,a | depart,arrive | road,road |
Scenario: Traffic Island
Given the node map
"""
f
a - - b < > d - - e
c
"""
And the ways
| nodes | name | oneway |
| ab | road | no |
| de | road | no |
| bcdfb | road | yes |
When I route I should get
| waypoints | route | intersections |
| a,e | road,road | true:90;true:270 |
@negative
Scenario: Turning Road, Don't remove sliproads
Given the node map
"""
h - - - - - g - - - - - - f - - - - - e
_ '
.
a - - - - - b - - - - - - c - - - - - d
|
|
|
i
"""
And the ways
| nodes | name | oneway |
| ab | road | yes |
| bcd | road | yes |
| efgh | road | yes |
| fb | road | yes |
| bi | turn | yes |
And the relations
| type | way:from | way:to | node:via | restriction |
| restriction | fb | bcd | b | no_left_turn |
When I route I should get
| waypoints | route | turns | intersections |
| a,d | road,road | depart,arrive | true:90,false:60 true:90 true:180 false:270;true:270 |
| e,h | road,road | depart,arrive | true:270,false:90 true:240 true:270;true:90 |
| e,i | road,turn,turn | depart,turn left,arrive | true:270;false:90 true:240 true:270,false:60 false:90 true:180 false:270;true:0 |
@negative
Scenario: Meeting Turn Roads
Given the node map
"""
k l
| |
| |
| |
h - - - - - g - - - - - - - f - - - - - e
| ' ' |
| x |
| . . |
a - - - - - b - - - - - - - c - - - - - d
| |
| |
| |
i j
"""
And the ways
| nodes | name | oneway |
| ab | horiz | yes |
| bc | horiz | yes |
| cd | horiz | yes |
| ef | horiz | yes |
| fg | horiz | yes |
| gh | horiz | yes |
| kg | vert | yes |
| gb | vert | yes |
| bi | vert | yes |
| jc | vert | yes |
| cf | vert | yes |
| fl | vert | yes |
| gx | horiz | no |
| xc | horiz | no |
| fx | horiz | no |
| xb | horiz | no |
And the relations
| type | way:from | way:to | node:via | restriction |
| restriction | bc | cf | c | no_left_turn |
| restriction | fg | gb | g | no_left_turn |
| restriction | cf | fg | f | no_left_turn |
| restriction | gb | bc | b | no_left_turn |
| restriction | xb | bc | b | no_left_turn |
| restriction | xc | cf | c | no_left_turn |
| restriction | xf | fg | f | no_left_turn |
| restriction | xg | gb | g | no_left_turn |
# the goal here should be not to mention the intersection in the middle at all and also suppress the segregated parts
When I route I should get
| waypoints | route | intersections |
| a,l | horiz,vert,vert | true:90;false:0 true:60 true:90 true:180 false:270,true:60 true:120 false:240 true:300,true:0 false:90 false:180 false:240 false:270;true:180 |
| a,d | horiz,horiz | true:90,false:0 true:60 true:90 true:180 false:270,false:0 true:90 false:180 false:270 true:300;true:270 |
| j,h | vert,horiz,horiz | true:0;true:0 true:90 false:180 false:270 true:300,true:60 false:120 true:240 true:300,false:0 false:90 false:120 false:180 true:270;true:90 |
| j,l | vert,vert | true:0,true:0 true:90 false:180 false:270 true:300,true:0 false:90 false:180 true:240 false:270;true:180 |

View File

@ -13,23 +13,28 @@ Feature: Simple Turns
^
/ \
c d
|\
| e
|
f
| |\
| | e
| |
| |
| |
| |
| |
| |
g f
"""
And the ways
| nodes | name | highway | oneway |
| ab | road | primary | no |
| bc | road | primary | yes |
| bcg | road | primary | yes |
| fdb | road | primary | yes |
| de | turn | primary | no |
| ed | turn | primary | yes |
When I route I should get
| waypoints | turns | route |
| f,a | depart,arrive | road,road |
| e,a | depart,turn slight right,arrive | turn,road,road |
| waypoints | turns | route | intersections |
| f,a | depart,arrive | road,road | true:0,true:0 false:150 false:180;true:180 |
| e,a | depart,turn slight right,arrive | turn,road,road | true:333;true:0 false:150 false:180;true:180 |
Scenario: Turning into splitting road
Given the node map
@ -39,16 +44,22 @@ Feature: Simple Turns
/\
/ \
c d
|\
| e
|
f
| |\
| | e
| |
| |
| |
| |
| |
| |
| |
h f
"""
And the ways
| nodes | name | highway | oneway |
| ab | road | primary | no |
| bc | road | primary | yes |
| bch | road | primary | yes |
| fdb | road | primary | yes |
| de | turn | primary | no |
| bg | left | primary | yes |
@ -61,108 +72,6 @@ Feature: Simple Turns
| f,g | depart,turn left,arrive | road,left,left |
| f,c | depart,continue uturn,arrive | road,road,road |
Scenario: Middle Island
Given the node map
"""
a
b
c h
d g
e
f
"""
And the ways
| nodes | name | oneway |
| ab | road | no |
| ef | road | no |
| bcde | road | yes |
| eghb | road | yes |
When I route I should get
| waypoints | turns | route |
| a,f | depart,arrive | road,road |
| c,f | depart,arrive | road,road |
| f,a | depart,arrive | road,road |
| g,a | depart,arrive | road,road |
Scenario: Middle Island Over Bridge
Given the node map
"""
a
|
.b.
c h
| |
| |
1 2
| |
d g
'e'
|
f
"""
And the ways
| nodes | name | oneway |
| ab | road | no |
| ef | road | no |
| bc | road | yes |
| cd | bridge | yes |
| de | road | yes |
| eg | road | yes |
| gh | bridge | yes |
| hb | road | yes |
When I route I should get
| waypoints | turns | route |
| a,f | depart,arrive | road,road |
| c,f | depart,new name straight,arrive | bridge,road,road |
| 1,f | depart,new name straight,arrive | bridge,road,road |
| f,a | depart,arrive | road,road |
| g,a | depart,new name straight,arrive | bridge,road,road |
| 2,a | depart,new name straight,arrive | bridge,road,road |
@negative
Scenario: Don't Collapse Places:
Given the node map
"""
h
g
a b e f
c
d
"""
And the ways
| nodes | name | oneway |
| ab | place | no |
| cd | bottom | no |
| ef | place | no |
| gh | top | no |
| bcegb | place | yes |
When I route I should get
| waypoints | turns | route |
| a,d | depart,turn right,arrive | place,bottom,bottom |
| a,f | depart,continue left,continue right,arrive | place,place,place,place |
| d,f | depart,turn right,continue right,arrive | bottom,place,place,place |
| d,h | depart,turn right,continue left,turn right,arrive | bottom,place,place,top,top |
@bug @not-sorted @3179
Scenario: Adjusting road angles to not be sorted
Given the node map

View File

@ -768,6 +768,30 @@ Feature: Simple Turns
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
g h
"""
@ -784,15 +808,15 @@ Feature: Simple Turns
| 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 |
| waypoints | route | turns | intersections |
| a,g | Perle,Heide,Heide | depart,turn right,arrive | true:90;true:90 true:180 false:270 true:345;true:18 |
| a,k | Perle,Friede,Friede | depart,turn left,arrive | true:90;true:90 true:180 false:270 true:345;true:153 |
| a,e | Perle,Perle | depart,arrive | true:90,true:90 true:180 false:270 true:345;true:270 |
| e,k | Perle,Friede,Friede | depart,turn right,arrive | true:270;false:90 true:180 true:270 true:345;true:153 |
| e,g | Perle,Heide,Heide | depart,turn left,arrive | true:270;false:90 true:180 true:270 true:345;true:18 |
| h,k | Heide,Friede,Friede | depart,new name straight,arrive | true:16;true:90 true:180 true:270 true:345;true:153 |
| h,e | Heide,Perle,Perle | depart,turn right,arrive | true:16;true:90 true:180 true:270 true:345;true:270 |
| h,a | Heide,Perle,Perle | depart,turn left,arrive | true:16;true:90 true:180 true:270 true:345;true:90 |
#http://www.openstreetmap.org/#map=19/52.53293/13.32956
Scenario: Curved Exit from Curved Road
@ -930,6 +954,36 @@ Feature: Simple Turns
. . .
. . .
i . .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
e a
"""

View File

@ -599,22 +599,21 @@ Feature: Turn Lane Guidance
Scenario: Segregated Intersection Merges With Lanes
Given the node map
"""
f
e d
c g
a b
h
a e
| |
| |
b d
h c
' -- g - - f
"""
And the ways
| nodes | name | turn:lanes:forward | oneway | highway |
| abc | road | left\|left\|left\|through\|through | yes | primary |
| cde | road | | yes | primary |
| hc | cross | | yes | secondary |
| cg | straight | | no | tertiary |
| cf | left | | yes | primary |
| nodes | name | turn:lanes:forward | oneway | highway | lanes |
| abc | road | left\|left\|left\|through\|through | yes | primary | 5 |
| cde | road | | yes | primary | 3 |
| hc | cross | | yes | secondary | |
| cg | straight | | no | tertiary | |
| cf | left | | yes | primary | |
When I route I should get
| waypoints | route | turns | lanes |

View File

@ -278,52 +278,6 @@ Feature: Simple Turns
| x | z | xy,yz,yz | depart,turn right,arrive |
| z | x | yz,xy,xy | depart,turn left,arrive |
Scenario: Four Way Intersection Double Through Street Segregated
Given the node map
"""
q p
b c
j i d o
a
k h e n
g f
l m
"""
And the ways
| nodes | highway | oneway | name |
| khaij | primary | yes | first |
| odaen | primary | yes | first |
| qbacp | primary | yes | second |
| mfagl | primary | yes | second |
When I route I should get
| waypoints | route | turns |
| f,e | second,first,first | depart,turn right,arrive |
| f,c | second,second | depart,arrive |
| f,i | second,first,first | depart,turn left,arrive |
| f,g | second,second,second | depart,continue uturn,arrive |
| d,c | first,second,second | depart,turn right,arrive |
| d,i | first,first | depart,arrive |
| d,g | first,second,second | depart,turn left,arrive |
| d,e | first,first,first | depart,continue uturn,arrive |
| b,i | second,first,first | depart,turn right,arrive |
| b,g | second,second | depart,arrive |
| b,e | second,first,first | depart,turn left,arrive |
| b,c | second,second,second | depart,continue uturn,arrive |
| h,g | first,second,second | depart,turn right,arrive |
| h,e | first,first | depart,arrive |
| h,c | first,second,second | depart,turn left,arrive |
| h,i | first,first,first | depart,continue uturn,arrive |
Scenario: Three Way Similar Sharp Turns
Given the node map
"""
@ -1080,100 +1034,6 @@ Feature: Simple Turns
| a,d | depart,new name straight,arrive | Molkenmarkt,Stralauer Str,Stralauer Str |
| e,d | depart,new name slight left,arrive | Molkenmarkt,Stralauer Str,Stralauer Str |
# https://www.mapillary.com/app/?lat=52.466483333333336&lng=13.431908333333332&z=17&focus=photo&pKey=LWXnKqoGqUNLnG0lofiO0Q
# http://www.openstreetmap.org/#map=19/52.46750/13.43171
Scenario: Collapse Turn Instruction, Issue #2725
Given the node map
"""
f
e
g d
h c
b
a
r x s
y
"""
And the ways
| nodes | name | highway | oneway |
| ab | Hermannstr | secondary | |
| bc | Hermannstr | secondary | yes |
| cd | Hermannbruecke | secondary | yes |
| de | Hermannstr | secondary | yes |
| ef | Hermannstr | secondary | |
| eg | Hermannstr | secondary | yes |
| gh | Hermannbruecke | secondary | yes |
| hb | Hermannstr | secondary | yes |
| xa | Hermannstr | secondary | |
| yx | Hermannstr | secondary | |
| rxs | Silbersteinstr | tertiary | |
And the nodes
| node | highway |
| x | traffic_signals |
When I route I should get
| waypoints | turns | route |
| a,f | depart,arrive | Hermannstr,Hermannstr |
| f,a | depart,arrive | Hermannstr,Hermannstr |
| y,f | depart,arrive | Hermannstr,Hermannstr |
| f,y | depart,arrive | Hermannstr,Hermannstr |
Scenario: Collapse Turn Instruction, Issue #2725 - not trivially mergable at e
# https://www.mapillary.com/app/?lat=52.466483333333336&lng=13.431908333333332&z=17&focus=photo&pKey=LWXnKqoGqUNLnG0lofiO0Q
# http://www.openstreetmap.org/#map=19/52.46750/13.43171
Given the node map
"""
f
e
g d
h c
b
a
r x s
y
"""
And the ways
| nodes | name | highway | oneway |
| ab | Hermannstr | secondary | |
| bc | Hermannstr | secondary | yes |
| cd | Hermannbruecke | secondary | yes |
| de | Hermannstr | secondary | yes |
| ef | Hermannstr | secondary | |
| eg | Hermannstr | secondary | yes |
| gh | Hermannbruecke | secondary | yes |
| hb | Hermannstr | secondary | yes |
| xa | Hermannstr | secondary | |
| yx | Hermannstr | secondary | |
| rxs | Silbersteinstr | tertiary | |
And the nodes
| node | highway |
| x | traffic_signals |
When I route I should get
| waypoints | turns | route |
| a,f | depart,arrive | Hermannstr,Hermannstr |
| f,a | depart,arrive | Hermannstr,Hermannstr |
| y,f | depart,arrive | Hermannstr,Hermannstr |
| f,y | depart,arrive | Hermannstr,Hermannstr |
# http://www.openstreetmap.org/#map=18/39.28158/-76.62291
@3002
Scenario: Obvious Index wigh very narrow turn to the right

View File

@ -154,7 +154,7 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
intersection.entry.push_back(entry_class.allowsEntry(idx));
}
std::int16_t bearing_in_driving_direction =
util::reverseBearing(std::round(bearings.first));
util::bearing::reverse(std::round(bearings.first));
maneuver = {intersection.location,
bearing_in_driving_direction,
bearings.second,
@ -214,8 +214,9 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
BOOST_ASSERT(segment_index == number_of_segments - 1);
bearings = detail::getArriveBearings(leg_geometry);
intersection = {target_node.location,
std::vector<short>({static_cast<short>(util::reverseBearing(bearings.first))}),
intersection = {
target_node.location,
std::vector<short>({static_cast<short>(util::bearing::reverse(bearings.first))}),
std::vector<bool>({true}),
0,
IntermediateIntersection::NO_INDEX,

View File

@ -1,6 +1,7 @@
#ifndef OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES
#define OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES
#include <algorithm>
#include <vector>
#include "extractor/query_node.hpp"
@ -11,6 +12,8 @@
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/guidance/intersection.hpp"
#include "util/coordinate.hpp"
#include "util/geojson_debug_policy_toolkit.hpp"
#include <boost/optional.hpp>
@ -18,8 +21,9 @@ namespace osrm
{
namespace extractor
{
// generate a visualisation of an intersection, printing the coordinates used for angle calculation
struct IntersectionPrinter
template <typename IntersectionType> struct IntersectionPrinter
{
IntersectionPrinter(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<extractor::QueryNode> &node_coordinates,
@ -28,7 +32,7 @@ struct IntersectionPrinter
// renders the used coordinate locations for all entries/as well as the resulting
// intersection-classification
util::json::Array operator()(const NodeID intersection_node,
const extractor::guidance::Intersection &intersection,
const IntersectionType &intersection,
const boost::optional<util::json::Object> &node_style = {},
const boost::optional<util::json::Object> &way_style = {}) const;
@ -37,6 +41,66 @@ struct IntersectionPrinter
const extractor::guidance::CoordinateExtractor &coordinate_extractor;
};
// IMPLEMENTATION
template <typename IntersectionType>
IntersectionPrinter<IntersectionType>::IntersectionPrinter(
const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<extractor::QueryNode> &node_coordinates,
const extractor::guidance::CoordinateExtractor &coordinate_extractor)
: node_based_graph(node_based_graph), node_coordinates(node_coordinates),
coordinate_extractor(coordinate_extractor)
{
}
template <typename IntersectionType>
util::json::Array IntersectionPrinter<IntersectionType>::
operator()(const NodeID intersection_node,
const IntersectionType &intersection,
const boost::optional<util::json::Object> &node_style,
const boost::optional<util::json::Object> &way_style) const
{
// request the number of lanes. This process needs to be in sync with what happens over at
// intersection_generator
const auto intersection_lanes =
intersection.FindMaximum(guidance::makeExtractLanesForRoad(node_based_graph));
std::vector<util::Coordinate> coordinates;
coordinates.reserve(intersection.size());
coordinates.push_back(node_coordinates[intersection_node]);
const auto road_to_coordinate = [&](const auto &road) {
const constexpr auto FORWARD = false;
const auto to_node = node_based_graph.GetTarget(road.eid);
return coordinate_extractor.GetCoordinateAlongRoad(
intersection_node, road.eid, FORWARD, to_node, intersection_lanes);
};
std::transform(intersection.begin(),
intersection.end(),
std::back_inserter(coordinates),
road_to_coordinate);
util::json::Array features;
features.values.push_back(
util::makeFeature("MultiPoint", makeJsonArray(coordinates), node_style));
if (coordinates.size() > 1)
{
std::vector<util::Coordinate> line_coordinates(2);
line_coordinates[0] = coordinates.front();
const auto coordinate_to_line = [&](const util::Coordinate coordinate) {
line_coordinates[1] = coordinate;
return util::makeFeature("LineString", makeJsonArray(line_coordinates), way_style);
};
std::transform(std::next(coordinates.begin()),
coordinates.end(),
std::back_inserter(features.values),
coordinate_to_line);
}
return features;
}
} /* namespace extractor */
} /* namespace osrm */

View File

@ -12,6 +12,7 @@ const bool constexpr INVERT = true;
// what angle is interpreted as going straight
const double constexpr STRAIGHT_ANGLE = 180.;
const double constexpr ORTHOGONAL_ANGLE = 90.;
// if a turn deviates this much from going straight, it will be kept straight
const double constexpr MAXIMAL_ALLOWED_NO_TURN_DEVIATION = 3.;
// angle that lies between two nearly indistinguishable roads
@ -36,6 +37,12 @@ const int constexpr MAX_SLIPROAD_THRESHOLD = 250;
// category).
const double constexpr PRIORITY_DISTINCTION_FACTOR = 1.75;
// the lane width we assume for a single lane
const auto constexpr ASSUMED_LANE_WIDTH = 3.25;
// how far apart can roads be at the most, when thinking about merging them?
const auto constexpr MERGABLE_ANGLE_DIFFERENCE = 95.0;
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -8,15 +8,15 @@
#include <type_traits>
#include <vector>
#include "extractor/guidance/turn_instruction.hpp"
#include "util/bearing.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp" // EdgeID
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include "extractor/guidance/turn_instruction.hpp"
#include <boost/assert.hpp>
#include <boost/range/algorithm/min_element.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/algorithm/count_if.hpp>
namespace osrm
{
@ -36,8 +36,8 @@ struct IntersectionShapeData
inline auto makeCompareShapeDataByBearing(const double base_bearing)
{
return [base_bearing](const auto &lhs, const auto &rhs) {
return util::angleBetweenBearings(base_bearing, lhs.bearing) <
util::angleBetweenBearings(base_bearing, rhs.bearing);
return util::bearing::angleBetween(lhs.bearing, base_bearing) <
util::bearing::angleBetween(rhs.bearing, base_bearing);
};
}
@ -48,6 +48,13 @@ inline auto makeCompareAngularDeviation(const double angle)
};
}
inline auto makeExtractLanesForRoad(const util::NodeBasedDynamicGraph &node_based_graph)
{
return [&node_based_graph](const auto &road) {
return node_based_graph.GetEdgeData(road.eid).road_classification.GetNumberOfLanes();
};
}
// When viewing an intersection from an incoming edge, we can transform a shape into a view which
// gives additional information on angles and whether a turn is allowed
struct IntersectionViewData : IntersectionShapeData
@ -108,11 +115,60 @@ struct ConnectedRoad final : IntersectionViewData
};
// small helper function to print the content of a connected road
std::string toString(const IntersectionShapeData &shape);
std::string toString(const IntersectionViewData &view);
std::string toString(const ConnectedRoad &road);
// Intersections are sorted roads: [0] being the UTurn road, then from sharp right to sharp left.
// common operations shared amongst all intersection types
template <typename Self> struct EnableShapeOps
{
// same as closest turn, but for bearings
auto FindClosestBearing(double bearing) const
{
auto comp = makeCompareShapeDataByBearing(bearing);
return std::min_element(self()->begin(), self()->end(), comp);
}
using IntersectionShape = std::vector<IntersectionShapeData>;
// search a given eid in the intersection
auto FindEid(const EdgeID eid) const
{
return boost::range::find_if(
*self(), [eid](const auto &road) { return road.eid == eid; });
}
// find the maximum value based on a conversion operator
template <typename UnaryProjection> auto FindMaximum(UnaryProjection converter) const
{
BOOST_ASSERT(!self()->empty());
auto initial = converter(self()->front());
const auto extract_maximal_value = [&initial, converter](const auto &road) {
initial = std::max(initial, converter(road));
return false;
};
boost::range::find_if(*self(), extract_maximal_value);
return initial;
}
// find the maximum value based on a conversion operator and a predefined initial value
template <typename UnaryPredicate> auto Count(UnaryPredicate detector) const
{
BOOST_ASSERT(!self()->empty());
return boost::range::count_if(*self(), detector);
}
private:
auto self() { return static_cast<Self *>(this); }
auto self() const { return static_cast<const Self *>(this); }
};
struct IntersectionShape final : std::vector<IntersectionShapeData>, //
EnableShapeOps<IntersectionShape> //
{
using Base = std::vector<IntersectionShapeData>;
};
// Common operations shared among IntersectionView and Intersections.
// Inherit to enable those operations on your compatible type. CRTP pattern.
@ -123,12 +179,13 @@ template <typename Self> struct EnableIntersectionOps
auto findClosestTurn(double angle) const
{
auto comp = makeCompareAngularDeviation(angle);
return std::min_element(self()->begin(), self()->end(), comp);
return boost::range::min_element(*self(), comp);
}
// Check validity of the intersection object. We assume a few basic properties every set of
// connected roads should follow throughout guidance pre-processing. This utility function
// allows checking intersections for validity
/* Check validity of the intersection object. We assume a few basic properties every set of
* connected roads should follow throughout guidance pre-processing. This utility function
* allows checking intersections for validity
*/
auto valid() const
{
if (self()->empty())
@ -149,26 +206,6 @@ template <typename Self> struct EnableIntersectionOps
return true;
}
// Given all possible turns which is the highest connected number of lanes per turn.
// This value is used for example during generation of intersections.
auto getHighestConnectedLaneCount(const util::NodeBasedDynamicGraph &graph) const
{
const std::function<std::uint8_t(const ConnectedRoad &)> to_lane_count =
[&](const ConnectedRoad &road) {
return graph.GetEdgeData(road.eid).road_classification.GetNumberOfLanes();
};
std::uint8_t max_lanes = 0;
const auto extract_maximal_value = [&max_lanes](std::uint8_t value) {
max_lanes = std::max(max_lanes, value);
return false;
};
const auto view = *self() | boost::adaptors::transformed(to_lane_count);
boost::range::find_if(view, extract_maximal_value);
return max_lanes;
}
// Returns the UTurn road we took to arrive at this intersection.
const auto &getUTurnRoad() const { return self()->operator[](0); }
@ -191,31 +228,52 @@ template <typename Self> struct EnableIntersectionOps
auto isDeadEnd() const
{
auto pred = [](const auto &road) { return road.entry_allowed; };
return !std::any_of(self()->begin() + 1, self()->end(), pred);
return std::none_of(self()->begin() + 1, self()->end(), pred);
}
// Returns the number of roads we can enter at this intersection, respectively.
auto countEnterable() const
{
auto pred = [](const auto &road) { return road.entry_allowed; };
return std::count_if(self()->begin(), self()->end(), pred);
return boost::range::count_if(*self(), pred);
}
// Returns the number of roads we can not enter at this intersection, respectively.
auto countNonEnterable() const { return self()->size() - self()->countEnterable(); }
// same as find closests turn but with an additional predicate to allow filtering
// the filter has to return `true` for elements that should be ignored
template <typename UnaryPredicate>
auto findClosestTurn(const double angle, const UnaryPredicate filter) const
{
BOOST_ASSERT(!self()->empty());
const auto candidate = boost::range::min_element(
*self(), [angle, &filter](const auto &lhs, const auto &rhs) {
const auto filtered_lhs = filter(lhs), filtered_rhs = filter(rhs);
const auto deviation_lhs = util::angularDeviation(lhs.angle, angle),
deviation_rhs = util::angularDeviation(rhs.angle, angle);
return std::tie(filtered_lhs, deviation_lhs) <
std::tie(filtered_rhs, deviation_rhs);
});
// make sure only to return valid elements
return filter(*candidate) ? self()->end() : candidate;
}
private:
auto self() { return static_cast<Self *>(this); }
auto self() const { return static_cast<const Self *>(this); }
};
struct IntersectionView final : std::vector<IntersectionViewData>, //
EnableShapeOps<IntersectionView>, //
EnableIntersectionOps<IntersectionView> //
{
using Base = std::vector<IntersectionViewData>;
};
struct Intersection final : std::vector<ConnectedRoad>, //
EnableShapeOps<Intersection>, //
EnableIntersectionOps<Intersection> //
{
using Base = std::vector<ConnectedRoad>;

View File

@ -4,6 +4,7 @@
#include "extractor/compressed_edge_container.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_normalization_operation.hpp"
#include "extractor/query_node.hpp"
#include "extractor/restriction_map.hpp"
#include "util/attributes.hpp"
@ -22,6 +23,13 @@ namespace extractor
{
namespace guidance
{
struct IntersectionGenerationParameters
{
NodeID nid;
EdgeID via_eid;
};
// The Intersection Generator is given a turn location and generates an intersection representation
// from it. For this all turn possibilities are analysed.
// We consider turn restrictions to indicate possible turns. U-turns are generated based on profile
@ -63,7 +71,7 @@ class IntersectionGenerator
// more than a single next road. This function skips over degree two nodes to find coorect input
// for GetConnectedRoads.
OSRM_ATTR_WARN_UNUSED
std::pair<NodeID, EdgeID> SkipDegreeTwoNodes(const NodeID starting_node,
IntersectionGenerationParameters SkipDegreeTwoNodes(const NodeID starting_node,
const EdgeID via_edge) const;
// Allow access to the coordinate extractor for all owners
@ -73,7 +81,7 @@ class IntersectionGenerator
// the node reached from `from_node` via `via_eid`. The resulting candidates have to be analysed
// for their actual instructions later on.
// The switch for `use_low_precision_angles` enables a faster mode that will procude less
// accurate coordinates. It should be good enough to check order of turns, find striaghtmost
// accurate coordinates. It should be good enough to check order of turns, find straightmost
// turns. Even good enough to do some simple angle verifications. It is mostly available to
// allow for faster graph traversal in the extraction phase.
OSRM_ATTR_WARN_UNUSED
@ -98,7 +106,7 @@ class IntersectionGenerator
const EdgeID entering_via_edge,
const IntersectionShape &normalised_intersection,
const IntersectionShape &intersection,
const std::vector<std::pair<EdgeID, EdgeID>> &merging_map) const;
const std::vector<IntersectionNormalizationOperation> &merging_map) const;
private:
const util::NodeBasedDynamicGraph &node_based_graph;

View File

@ -508,15 +508,15 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge,
// even reverse the direction. Since we don't want to compute actual turns but simply
// try to find whether there is a turn going to the opposite direction of our obvious
// turn, this should be alright.
NodeID new_node;
const auto previous_intersection = [&]() {
EdgeID turn_edge;
std::tie(new_node, turn_edge) = intersection_generator.SkipDegreeTwoNodes(
const auto previous_intersection = [&]() -> IntersectionView {
const auto parameters = intersection_generator.SkipDegreeTwoNodes(
node_at_intersection, intersection[0].eid);
return intersection_generator.GetConnectedRoads(new_node, turn_edge);
if (node_based_graph.GetTarget(parameters.via_eid) == node_at_intersection)
return {};
return intersection_generator.GetConnectedRoads(parameters.nid, parameters.via_eid);
}();
if (new_node != node_at_intersection)
if (!previous_intersection.empty())
{
const auto continue_road = intersection[best_continue];
for (const auto &comparison_road : previous_intersection)

View File

@ -0,0 +1,25 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZATION_OPERATION_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZATION_OPERATION_HPP_
#include "util/typedefs.hpp"
namespace osrm
{
namespace extractor
{
namespace guidance
{
struct IntersectionNormalizationOperation
{
// the source of the merge, not part of the intersection after the merge is performed.
EdgeID merged_eid;
// the edge that is covering the `merged_eid`
EdgeID into_eid;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /*OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZATION_OPERATION_HPP_*/

View File

@ -1,17 +1,17 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZER_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZER_HPP_
#include "util/typedefs.hpp"
#include "util/attributes.hpp"
#include "util/name_table.hpp"
#include "util/typedefs.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/intersection_normalization_operation.hpp"
#include "extractor/guidance/mergable_road_detector.hpp"
#include "extractor/query_node.hpp"
#include "extractor/suffix_table.hpp"
#include "util/name_table.hpp"
#include <utility>
#include <vector>
@ -37,6 +37,11 @@ namespace guidance
class IntersectionNormalizer
{
public:
struct NormalizationResult
{
IntersectionShape normalized_shape;
std::vector<IntersectionNormalizationOperation> performed_merges;
};
IntersectionNormalizer(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<extractor::QueryNode> &node_coordinates,
const util::NameTable &name_table,
@ -46,16 +51,13 @@ class IntersectionNormalizer
// The function takes an intersection an converts it to a `perceived` intersection which closer
// represents how a human might experience the intersection
OSRM_ATTR_WARN_UNUSED
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>>
operator()(const NodeID node_at_intersection, IntersectionShape intersection) const;
NormalizationResult operator()(const NodeID node_at_intersection,
IntersectionShape intersection) const;
private:
const util::NodeBasedDynamicGraph &node_based_graph;
const std::vector<extractor::QueryNode> &node_coordinates;
const util::NameTable &name_table;
const SuffixTable &street_name_suffix_table;
const IntersectionGenerator &intersection_generator;
const MergableRoadDetector mergable_road_detector;
/* check if two indices in an intersection can be seen as a single road in the perceived
* intersection representation. See below for an example. Utility function for
@ -73,12 +75,15 @@ class IntersectionNormalizer
std::size_t first_index,
std::size_t second_index) const;
// A tool called by CanMerge. It checks whether two indices can be merged, not concerned without
// remaining parts of the intersection.
bool InnerCanMerge(const NodeID intersection_node,
const IntersectionShape &intersection,
std::size_t first_index,
std::size_t second_index) const;
// Perform an Actual Merge
IntersectionNormalizationOperation
DetermineMergeDirection(const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const;
IntersectionShapeData MergeRoads(const IntersectionShapeData &destination,
const IntersectionShapeData &source) const;
IntersectionShapeData MergeRoads(const IntersectionNormalizationOperation direction,
const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const;
// Merge segregated roads to omit invalid turns in favor of treating segregated roads as
// one.
@ -92,8 +97,8 @@ class IntersectionNormalizer
// The treatment results in a straight turn angle of 180º rather than a turn angle of approx
// 160
OSRM_ATTR_WARN_UNUSED
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>>
MergeSegregatedRoads(const NodeID intersection_node, IntersectionShape intersection) const;
NormalizationResult MergeSegregatedRoads(const NodeID intersection_node,
IntersectionShape intersection) const;
// The counterpiece to mergeSegregatedRoads. While we can adjust roads that split up at the
// intersection itself, it can also happen that intersections are connected to joining roads.

View File

@ -0,0 +1,155 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_MERGEABLE_ROADS
#define OSRM_EXTRACTOR_GUIDANCE_MERGEABLE_ROADS
#include "extractor/guidance/intersection.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp"
#include <cstdint>
#include <functional>
#include <limits>
#include <vector>
namespace osrm
{
//FWD declarations
namespace util
{
class NameTable;
} // namespace util
namespace extractor
{
struct QueryNode;
class SuffixTable;
namespace guidance
{
class IntersectionGenerator;
class CoordinateExtractor;
class MergableRoadDetector
{
public:
// in case we have to change the mode we are operating on
using MergableRoadData = IntersectionShapeData;
MergableRoadDetector(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_coordinates,
const IntersectionGenerator &intersection_generator,
const CoordinateExtractor &coordinate_extractor,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table);
// OSM ways tend to be modelled as separate ways for different directions. This is often due to
// small gras strips in the middle between the two directions or due to pedestrian islands at
// intersections.
//
// To reduce unnecessary information due to these artificial intersections (which are not
// actually perceived as such) we try and merge these for our internal representation to both
// get better perceived turn angles and get a better reprsentation of our intersections in
// general.
//
// i h i,h
// | | |
// | | |
// b - - - v - - - g |
// > a < is transformed into: b,c - - - a - - - g,f
// c - - - ^ - - - f |
// | | |
// | | |
// d e d,e
bool CanMergeRoad(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// check if a road cannot influence the merging of the other. This is necessary to prevent
// situations with more than two roads that could participate in a merge
bool IsDistinctFrom(const MergableRoadData &lhs, const MergableRoadData &rhs) const;
private:
// check if two name ids can be seen as identical (in presence of refs/others)
// in our case this translates into no name announcement in either direction (lhs->rhs and
// rhs->lhs)
bool HaveIdenticalNames(const NameID lhs, const NameID rhs) const;
// When it comes to merging roads, we need to find out if two ways actually represent the
// same road. This check tries to identify roads which are the same road in opposite directions
bool EdgeDataSupportsMerge(const util::NodeBasedEdgeData &lhs_edge_data,
const util::NodeBasedEdgeData &rhs_edge_data) const;
// Detect traffic loops.
// Since OSRM cannot handle loop edges, we cannot directly see a connection between a node and
// itself. We need to skip at least a single node in between.
bool IsTrafficLoop(const NodeID intersection_node, const MergableRoadData &road) const;
// Detector to check if we are looking at roads splitting up just prior to entering an
// intersection:
//
// c
// / |
// a -< |
// \ |
// b
//
// A common scheme in OSRM is that roads spit up in separate ways when approaching an
// intersection. This detector tries to detect these narrow triangles which usually just offer a
// small island for pedestrians in the middle.
bool IsNarrowTriangle(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// Detector to check for whether two roads are following the same direction.
// If roads don't end up right at a connected intersection, we could look at a situation like
//
// __________________________
// /
// ---
// \__________________________
//
// This detector tries to find out about whether two roads are parallel after the separation
bool HaveSameDirection(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// Detector for small traffic islands. If a road is splitting up, just to connect again later,
// we don't wan't to have this information within our list of intersections/possible turn
// locations.
//
// ___________
// ---<___________>-----
//
//
// Would feel just like a single straight road to a driver and should be represented as such in
// our engine
bool IsTrafficIsland(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// A negative detector, preventing a merge, trying to detect link roads between two main roads.
//
// d - - - - - - - - e - f
// . / '
// a - - - b - - - - - - c
//
// The detector wants to prevent merges that are connected to `b-e`
bool IsLinkRoad(const NodeID intersection_node, const MergableRoadData &road) const;
const util::NodeBasedDynamicGraph &node_based_graph;
const std::vector<QueryNode> &node_coordinates;
const IntersectionGenerator &intersection_generator;
const CoordinateExtractor &coordinate_extractor;
// name detection
const util::NameTable &name_table;
const SuffixTable &street_name_suffix_table;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif

View File

@ -57,7 +57,6 @@ struct LengthLimitedCoordinateAccumulator
{
LengthLimitedCoordinateAccumulator(
const extractor::guidance::CoordinateExtractor &coordinate_extractor,
const util::NodeBasedDynamicGraph &node_based_graph,
const double max_length);
/*
@ -78,11 +77,12 @@ struct LengthLimitedCoordinateAccumulator
*/
void update(const NodeID from_node, const EdgeID via_edge, const NodeID to_node);
const extractor::guidance::CoordinateExtractor &coordinate_extractor;
const util::NodeBasedDynamicGraph &node_based_graph;
const double max_length;
double accumulated_length;
double accumulated_length = 0;
std::vector<util::Coordinate> coordinates;
private:
const extractor::guidance::CoordinateExtractor &coordinate_extractor;
const double max_length;
};
/*
@ -105,13 +105,40 @@ struct SelectRoadByNameOnlyChoiceAndStraightness
*/
boost::optional<EdgeID> operator()(const NodeID nid,
const EdgeID via_edge_id,
const Intersection &intersection,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const;
private:
const NameID desired_name_id;
const bool requires_entry;
};
/* Following only a straight road
* Follow only the straightmost turn, as long as its the only choice or has the desired name
*/
struct SelectStraightmostRoadByNameAndOnlyChoice
{
SelectStraightmostRoadByNameAndOnlyChoice(const NameID desired_name_id,
const double initial_bearing,
const bool requires_entry);
/*
* !! REQUIRED - Function for the use of TraverseRoad in the graph walker.
* The operator() needs to return (if any is found) the next road to continue in the graph
* traversal. If no such edge is found, return {} is allowed. Usually you want to choose some
* form of obious turn to follow.
*/
boost::optional<EdgeID> operator()(const NodeID nid,
const EdgeID via_edge_id,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const;
private:
const NameID desired_name_id;
const double initial_bearing;
const bool requires_entry;
};
// find the next intersection given a hop limit
struct IntersectionFinderAccumulator
{
@ -166,8 +193,9 @@ NodeBasedGraphWalker::TraverseRoad(NodeID current_node_id,
return {};
// look at the next intersection
const auto next_intersection =
intersection_generator.GetConnectedRoads(current_node_id, current_edge_id);
const constexpr auto LOW_PRECISION = true;
const auto next_intersection = intersection_generator.GetConnectedRoads(
current_node_id, current_edge_id, LOW_PRECISION);
// don't follow u-turns or go past our initial intersection
if (next_intersection.size() <= 1)
@ -235,7 +263,7 @@ struct DistanceToNextIntersectionAccumulator
using namespace util::coordinate_calculation;
const auto coords = extractor.GetForwardCoordinatesAlongRoad(start, onto);
distance += getLength(coords, &haversineDistance);
distance += getLength(coords.begin(), coords.end(), &haversineDistance);
}
const extractor::guidance::CoordinateExtractor &extractor;

View File

@ -4,6 +4,7 @@
#include "extractor/compressed_edge_container.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/intersection_normalization_operation.hpp"
#include "extractor/guidance/intersection_normalizer.hpp"
#include "extractor/guidance/motorway_handler.hpp"
#include "extractor/guidance/roundabout_handler.hpp"
@ -62,11 +63,9 @@ class TurnAnalysis
{
// the basic shape, containing all turns
IntersectionShape intersection_shape;
// normalised shape, merged some roads into others, adjusted bearings
// see intersection_normaliser for further explanations
IntersectionShape normalised_intersection_shape;
// map containing information about which road was merged into which
std::vector<std::pair<EdgeID, EdgeID>> merging_map;
// normalized shape, merged some roads into others, adjusted bearings
// see intersection_normalizer for further explanations
IntersectionNormalizer::NormalizationResult annotated_normalized_shape;
};
OSRM_ATTR_WARN_UNUSED
ShapeResult ComputeIntersectionShapes(const NodeID node_at_center_of_intersection) const;

View File

@ -91,9 +91,8 @@ inline bool CheckInBounds(const int A, const int B, const int range)
return normalized_B - range <= normalized_A && normalized_A <= normalized_B + range;
}
}
} // namespace bearing
inline double reverseBearing(const double bearing)
inline double reverse(const double bearing)
{
if (bearing >= 180)
return bearing - 180.;
@ -119,8 +118,9 @@ inline double reverseBearing(const double bearing)
// % 360;
// All other cases are handled by first rotating both bearings to an
// entry_bearing of 0.
inline double angleBetweenBearings(const double entry_bearing, const double exit_bearing)
inline double angleBetween(const double entry_bearing, const double exit_bearing)
{
// transform bearing from cw into ccw order
const double offset = 360 - entry_bearing;
const double rotated_exit = [](double bearing, const double offset) {
bearing += offset;
@ -131,13 +131,39 @@ inline double angleBetweenBearings(const double entry_bearing, const double exit
return angle >= 360 ? angle - 360 : angle;
}
// minimal difference between two angles/bearings going left or right
inline double angularDeviation(const double angle, const double from)
} // namespace bearing
// compute the minimum distance in degree between two angles/bearings
inline double angularDeviation(const double angle_or_bearing, const double from)
{
const double deviation = std::abs(angle - from);
const double deviation = std::abs(angle_or_bearing - from);
return std::min(360 - deviation, deviation);
}
/* Angles in OSRM are expressed in the range of [0,360). During calculations, we might violate
* this range via offsets. This function helps to ensure the range is kept. */
inline double restrictAngleToValidRange(const double angle)
{
if (angle < 0)
return restrictAngleToValidRange(angle + 360.);
else if (angle > 360)
return restrictAngleToValidRange(angle - 360.);
else
return angle;
}
// finds the angle between two angles, based on the minum difference between the two
inline double angleBetween(const double lhs, const double rhs)
{
const auto difference = std::abs(lhs - rhs);
const auto is_clockwise_difference = difference <= 180;
const auto angle_between_candidate = .5 * (lhs + rhs);
if (is_clockwise_difference)
return angle_between_candidate;
else
return restrictAngleToValidRange(angle_between_candidate + 180);
}
} // namespace util
} // namespace osrm

View File

@ -3,9 +3,12 @@
#include "util/coordinate.hpp"
#include <boost/math/constants/constants.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <utility>
#include <vector>
@ -23,6 +26,18 @@ const constexpr long double RAD_TO_DEGREE = 1. / DEGREE_TO_RAD;
// earth radius varies between 6,356.750-6,378.135 km (3,949.901-3,963.189mi)
// The IUGG value for the equatorial radius is 6378.137 km (3963.19 miles)
const constexpr long double EARTH_RADIUS = 6372797.560856;
inline double degToRad(const double degree)
{
using namespace boost::math::constants;
return degree * (pi<double>() / 180.0);
}
inline double radToDeg(const double radian)
{
using namespace boost::math::constants;
return radian * (180.0 * (1. / pi<double>()));
}
}
//! Takes the squared euclidean distance of the input coordinates. Does not return meters!
@ -33,22 +48,8 @@ double haversineDistance(const Coordinate first_coordinate, const Coordinate sec
double greatCircleDistance(const Coordinate first_coordinate, const Coordinate second_coordinate);
// get the length of a full coordinate vector, using one of our basic functions to compute distances
template <class BinaryOperation>
double getLength(const std::vector<Coordinate> &coordinates, BinaryOperation op)
{
if (coordinates.empty())
return 0.;
double result = 0;
const auto functor = [&result, op](const Coordinate lhs, const Coordinate rhs) {
result += op(lhs, rhs);
return false;
};
// side-effect find adding up distances
std::adjacent_find(coordinates.begin(), coordinates.end(), functor);
return result;
}
template <class BinaryOperation, typename iterator_type>
double getLength(iterator_type begin, const iterator_type end, BinaryOperation op);
// Find the closest distance and location between coordinate and the line connecting source and
// target:
@ -59,41 +60,35 @@ double getLength(const std::vector<Coordinate> &coordinates, BinaryOperation op)
// returns x as well as the distance between source and x as ratio ([0,1])
inline std::pair<double, FloatCoordinate> projectPointOnSegment(const FloatCoordinate &source,
const FloatCoordinate &target,
const FloatCoordinate &coordinate)
{
const FloatCoordinate slope_vector{target.lon - source.lon, target.lat - source.lat};
const FloatCoordinate rel_coordinate{coordinate.lon - source.lon, coordinate.lat - source.lat};
// dot product of two un-normed vectors
const auto unnormed_ratio = static_cast<double>(slope_vector.lon * rel_coordinate.lon) +
static_cast<double>(slope_vector.lat * rel_coordinate.lat);
// squared length of the slope vector
const auto squared_length = static_cast<double>(slope_vector.lon * slope_vector.lon) +
static_cast<double>(slope_vector.lat * slope_vector.lat);
const FloatCoordinate &coordinate);
if (squared_length < std::numeric_limits<double>::epsilon())
{
return {0, source};
}
// find the closest distance between a coordinate and a segment
// O(1)
double findClosestDistance(const Coordinate coordinate,
const Coordinate segment_begin,
const Coordinate segment_end);
const double normed_ratio = unnormed_ratio / squared_length;
double clamped_ratio = normed_ratio;
if (clamped_ratio > 1.)
{
clamped_ratio = 1.;
}
else if (clamped_ratio < 0.)
{
clamped_ratio = 0.;
}
// find the closest distance between a coordinate and a set of coordinates
// O(|coordinates|)
template <typename iterator_type>
double findClosestDistance(const Coordinate coordinate,
const iterator_type begin,
const iterator_type end);
return {clamped_ratio,
{
FloatLongitude{1.0 - clamped_ratio} * source.lon +
target.lon * FloatLongitude{clamped_ratio},
FloatLatitude{1.0 - clamped_ratio} * source.lat +
target.lat * FloatLatitude{clamped_ratio},
}};
}
// find the closes distance between two sets of coordinates
// O(|lhs| * |rhs|)
template <typename iterator_type>
double findClosestDistance(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end);
// checks if two sets of coordinates describe a parallel set of ways
template <typename iterator_type>
bool areParallel(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end);
double perpendicularDistance(const Coordinate segment_source,
const Coordinate segment_target,
@ -136,8 +131,252 @@ bool isCCW(const Coordinate first_coordinate,
const Coordinate second_coordinate,
const Coordinate third_coordinate);
std::pair<util::Coordinate, util::Coordinate>
leastSquareRegression(const std::vector<util::Coordinate> &coordinates);
template <typename iterator_type>
std::pair<Coordinate, Coordinate> leastSquareRegression(const iterator_type begin,
const iterator_type end);
// rotates a coordinate around the point (0,0). This function can be used to normalise a few
// computations around regression vectors
Coordinate rotateCCWAroundZero(Coordinate coordinate, double angle_in_radians);
// compute the difference vector of two coordinates lhs - rhs
Coordinate difference(const Coordinate lhs, const Coordinate rhs);
// TEMPLATE/INLINE DEFINITIONS
inline std::pair<double, FloatCoordinate> projectPointOnSegment(const FloatCoordinate &source,
const FloatCoordinate &target,
const FloatCoordinate &coordinate)
{
const FloatCoordinate slope_vector{target.lon - source.lon, target.lat - source.lat};
const FloatCoordinate rel_coordinate{coordinate.lon - source.lon, coordinate.lat - source.lat};
// dot product of two un-normed vectors
const auto unnormed_ratio = static_cast<double>(slope_vector.lon * rel_coordinate.lon) +
static_cast<double>(slope_vector.lat * rel_coordinate.lat);
// squared length of the slope vector
const auto squared_length = static_cast<double>(slope_vector.lon * slope_vector.lon) +
static_cast<double>(slope_vector.lat * slope_vector.lat);
if (squared_length < std::numeric_limits<double>::epsilon())
{
return {0, source};
}
const double normed_ratio = unnormed_ratio / squared_length;
double clamped_ratio = normed_ratio;
if (clamped_ratio > 1.)
{
clamped_ratio = 1.;
}
else if (clamped_ratio < 0.)
{
clamped_ratio = 0.;
}
return {clamped_ratio,
{
FloatLongitude{1.0 - clamped_ratio} * source.lon +
target.lon * FloatLongitude{clamped_ratio},
FloatLatitude{1.0 - clamped_ratio} * source.lat +
target.lat * FloatLatitude{clamped_ratio},
}};
}
template <class BinaryOperation, typename iterator_type>
double getLength(iterator_type begin, const iterator_type end, BinaryOperation op)
{
double result = 0;
const auto functor = [&result, op](const Coordinate lhs, const Coordinate rhs) {
result += op(lhs, rhs);
return false;
};
// side-effect find adding up distances
std::adjacent_find(begin, end, functor);
return result;
}
template <typename iterator_type>
double
findClosestDistance(const Coordinate coordinate, const iterator_type begin, const iterator_type end)
{
double current_min = std::numeric_limits<double>::max();
// comparator updating current_min without ever finding an element
const auto compute_minimum_distance = [&current_min, coordinate](const Coordinate lhs,
const Coordinate rhs) {
current_min = std::min(current_min, findClosestDistance(coordinate, lhs, rhs));
return false;
};
std::adjacent_find(begin, end, compute_minimum_distance);
return current_min;
}
template <typename iterator_type>
double findClosestDistance(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end)
{
double current_min = std::numeric_limits<double>::max();
const auto compute_minimum_distance_in_rhs = [&current_min, rhs_begin, rhs_end](
const Coordinate coordinate) {
current_min = std::min(current_min, findClosestDistance(coordinate, rhs_begin, rhs_end));
return false;
};
std::find_if(lhs_begin, lhs_end, compute_minimum_distance_in_rhs);
return current_min;
}
template <typename iterator_type>
std::pair<Coordinate, Coordinate> leastSquareRegression(const iterator_type begin,
const iterator_type end)
{
// following the formulas of https://faculty.elgin.edu/dkernler/statistics/ch04/4-2.html
const auto number_of_coordinates = std::distance(begin, end);
BOOST_ASSERT(number_of_coordinates >= 2);
const auto extract_lon = [](const Coordinate coordinate) {
return static_cast<double>(toFloating(coordinate.lon));
};
const auto extract_lat = [](const Coordinate coordinate) {
return static_cast<double>(toFloating(coordinate.lat));
};
double min_lon = extract_lon(*begin);
double max_lon = extract_lon(*begin);
double min_lat = extract_lat(*begin);
double max_lat = extract_lat(*begin);
for (auto coordinate_iterator = begin; coordinate_iterator != end; ++coordinate_iterator)
{
const auto c = *coordinate_iterator;
const auto lon = extract_lon(c);
min_lon = std::min(min_lon, lon);
max_lon = std::max(max_lon, lon);
const auto lat = extract_lat(c);
min_lat = std::min(min_lat, lat);
max_lat = std::max(max_lat, lat);
}
// very small difference in longitude -> would result in inaccurate calculation, check if lat is
// better
if ((max_lat - min_lat) > 2 * (max_lon - min_lon))
{
std::vector<util::Coordinate> rotated_coordinates(number_of_coordinates);
// rotate all coordinates to the right
std::transform(begin, end, rotated_coordinates.begin(), [](const auto coordinate) {
return rotateCCWAroundZero(coordinate, detail::degToRad(-90));
});
const auto rotated_regression =
leastSquareRegression(rotated_coordinates.begin(), rotated_coordinates.end());
return {rotateCCWAroundZero(rotated_regression.first, detail::degToRad(90)),
rotateCCWAroundZero(rotated_regression.second, detail::degToRad(90))};
}
const auto make_accumulate = [](const auto extraction_function) {
return [extraction_function](const double sum_so_far, const Coordinate coordinate) {
return sum_so_far + extraction_function(coordinate);
};
};
const auto accumulated_lon = std::accumulate(begin, end, 0., make_accumulate(extract_lon));
const auto accumulated_lat = std::accumulate(begin, end, 0., make_accumulate(extract_lat));
const auto mean_lon = accumulated_lon / number_of_coordinates;
const auto mean_lat = accumulated_lat / number_of_coordinates;
const auto make_variance = [](const auto mean, const auto extraction_function) {
return [extraction_function, mean](const double sum_so_far, const Coordinate coordinate) {
const auto difference = extraction_function(coordinate) - mean;
return sum_so_far + difference * difference;
};
};
// using the unbiased version, we divide by num_samples - 1 (see
// http://mathworld.wolfram.com/SampleVariance.html)
const auto sample_variance_lon =
std::sqrt(std::accumulate(begin, end, 0., make_variance(mean_lon, extract_lon)) /
(number_of_coordinates - 1));
// if we don't change longitude, return the vertical line as is
if (std::abs(sample_variance_lon) <
std::numeric_limits<decltype(sample_variance_lon)>::epsilon())
return {*begin, *(end - 1)};
const auto sample_variance_lat =
std::sqrt(std::accumulate(begin, end, 0., make_variance(mean_lat, extract_lat)) /
(number_of_coordinates - 1));
if (std::abs(sample_variance_lat) <
std::numeric_limits<decltype(sample_variance_lat)>::epsilon())
return {*begin, *(end - 1)};
const auto linear_correlation =
std::accumulate(begin,
end,
0.,
[&](const auto sum_so_far, const auto current_coordinate) {
return sum_so_far +
(extract_lon(current_coordinate) - mean_lon) *
(extract_lat(current_coordinate) - mean_lat) /
(sample_variance_lon * sample_variance_lat);
}) /
(number_of_coordinates - 1);
const auto slope = linear_correlation * sample_variance_lat / sample_variance_lon;
const auto intercept = mean_lat - slope * mean_lon;
const auto GetLatAtLon = [intercept,
slope](const util::FloatLongitude longitude) -> util::FloatLatitude {
return {intercept + slope * static_cast<double>((longitude))};
};
const double offset = 0.00001;
const Coordinate regression_first = {
toFixed(util::FloatLongitude{min_lon - offset}),
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{min_lon - offset})))};
const Coordinate regression_end = {
toFixed(util::FloatLongitude{max_lon + offset}),
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{max_lon + offset})))};
return {regression_first, regression_end};
}
template <typename iterator_type>
bool areParallel(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end)
{
const auto regression_lhs = leastSquareRegression(lhs_begin, lhs_end);
const auto regression_rhs = leastSquareRegression(rhs_begin, rhs_end);
const auto null_island = Coordinate(FixedLongitude{0}, FixedLatitude{0});
const auto difference_lhs = difference(regression_lhs.first, regression_lhs.second);
const auto difference_rhs = difference(regression_rhs.first, regression_rhs.second);
// we normalise the left slope to be zero, so we rotate the coordinates around 0,0 to match 90
// degrees
const auto bearing_lhs = bearing(null_island, difference_lhs);
// we rotate to have one of the lines facing horizontally to the right (bearing 90 degree)
const auto rotation_angle_radians = detail::degToRad(bearing_lhs - 90);
const auto rotated_difference_rhs = rotateCCWAroundZero(difference_rhs, rotation_angle_radians);
const auto get_slope = [](const Coordinate from, const Coordinate to) {
const auto diff_lat = static_cast<int>(from.lat) - static_cast<int>(to.lat);
const auto diff_lon = static_cast<int>(from.lon) - static_cast<int>(to.lon);
if (diff_lon == 0)
return std::numeric_limits<double>::max();
return static_cast<double>(diff_lat) / static_cast<double>(diff_lon);
};
const auto slope_rhs = get_slope(null_island, rotated_difference_rhs);
// the left hand side has a slope of `0` after the rotation. We can check the slope of the right
// hand side to ensure we only considering slight slopes
return std::abs(slope_rhs) < 0.20; // twenty percent incline at the most
}
} // ns coordinate_calculation
} // ns util

View File

@ -22,7 +22,6 @@ namespace util
{
namespace guidance
{
// Name Change Logic
// Used both during Extraction as well as during Post-Processing
@ -157,6 +156,9 @@ inline bool requiresNameAnnounced(const NameID from_name_id,
const util::NameTable &name_table,
const extractor::SuffixTable &suffix_table)
{
if (from_name_id == to_name_id)
return false;
else
return requiresNameAnnounced(name_table.GetNameForID(from_name_id),
name_table.GetRefForID(from_name_id),
name_table.GetPronunciationForID(from_name_id),
@ -170,6 +172,9 @@ inline bool requiresNameAnnounced(const NameID from_name_id,
const NameID to_name_id,
const util::NameTable &name_table)
{
if (from_name_id == to_name_id)
return false;
else
return requiresNameAnnounced(name_table.GetNameForID(from_name_id),
name_table.GetRefForID(from_name_id),
name_table.GetPronunciationForID(from_name_id),

View File

@ -260,8 +260,8 @@ void closeOffRoundabout(const bool on_roundabout,
TurnType::EnterRoundaboutIntersectionAtExit)
{
BOOST_ASSERT(!propagation_step.intersections.empty());
const double angle = util::angleBetweenBearings(
util::reverseBearing(entry_intersection.bearings[entry_intersection.in]),
const double angle = util::bearing::angleBetween(
util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]),
exit_bearing);
auto bearings = propagation_step.intersections.front().bearings;
@ -306,7 +306,7 @@ bool isUTurn(const RouteStep &in_step, const RouteStep &out_step, const RouteSte
(isLinkroad(in_step) && out_step.name_id != EMPTY_NAMEID &&
pre_in_step.name_id != EMPTY_NAMEID && !isNoticeableNameChange(pre_in_step, out_step));
const bool takes_u_turn = bearingsAreReversed(
util::reverseBearing(
util::bearing::reverse(
in_step.intersections.front().bearings[in_step.intersections.front().in]),
out_step.intersections.front().bearings[out_step.intersections.front().out]);
@ -318,20 +318,20 @@ double findTotalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_ste
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::reverseBearing(exit_intersection.bearings[exit_intersection.in]);
util::bearing::reverse(exit_intersection.bearings[exit_intersection.in]);
const auto entry_intersection = entry_step.intersections.front();
const auto entry_step_entry_bearing =
util::reverseBearing(entry_intersection.bearings[entry_intersection.in]);
util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]);
const auto entry_step_exit_bearing = entry_intersection.bearings[entry_intersection.out];
const auto exit_angle =
util::angleBetweenBearings(exit_step_entry_bearing, exit_step_exit_bearing);
util::bearing::angleBetween(exit_step_entry_bearing, exit_step_exit_bearing);
const auto entry_angle =
util::angleBetweenBearings(entry_step_entry_bearing, entry_step_exit_bearing);
util::bearing::angleBetween(entry_step_entry_bearing, entry_step_exit_bearing);
const double total_angle =
util::angleBetweenBearings(entry_step_entry_bearing, exit_step_exit_bearing);
util::bearing::angleBetween(entry_step_entry_bearing, exit_step_exit_bearing);
// We allow for minor deviations from a straight line
if (((entry_step.distance < MAX_COLLAPSE_DISTANCE && exit_step.intersections.size() == 1) ||
(entry_angle <= 185 && exit_angle <= 185) || (entry_angle >= 175 && exit_angle >= 175)) &&
@ -391,7 +391,7 @@ void collapseUTurn(std::vector<RouteStep> &steps,
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
// additionall collapse a name-change as well
const auto next_step_index = step_index + 1;
const bool continues_with_name_change =
(next_step_index < steps.size()) && compatible(steps[step_index], steps[next_step_index]) &&
@ -531,18 +531,18 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
if (continue_or_suppressed || turning_name)
{
const auto in_bearing = [](const RouteStep &step) {
return util::reverseBearing(
return util::bearing::reverse(
step.intersections.front().bearings[step.intersections.front().in]);
};
const auto out_bearing = [](const RouteStep &step) {
return step.intersections.front().bearings[step.intersections.front().out];
};
const auto first_angle = util::angleBetweenBearings(in_bearing(one_back_step),
const auto first_angle = util::bearing::angleBetween(in_bearing(one_back_step),
out_bearing(one_back_step));
const auto second_angle =
util::angleBetweenBearings(in_bearing(current_step), out_bearing(current_step));
const auto bearing_turn_angle = util::angleBetweenBearings(
const auto second_angle = util::bearing::angleBetween(in_bearing(current_step),
out_bearing(current_step));
const auto bearing_turn_angle = util::bearing::angleBetween(
in_bearing(one_back_step), out_bearing(current_step));
// When looking at an intersection, some angles, even though present, feel more like
@ -676,7 +676,7 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
};
// If we Merge onto the same street, we end up with a u-turn in some cases
if (bearingsAreReversed(util::reverseBearing(getBearing(true, one_back_step)),
if (bearingsAreReversed(util::bearing::reverse(getBearing(true, one_back_step)),
getBearing(false, current_step)))
{
steps[one_back_index].maneuver.instruction.direction_modifier =
@ -760,7 +760,7 @@ bool isStaggeredIntersection(const std::vector<RouteStep> &steps,
const auto &intersection = step.intersections.front();
const auto entry_bearing = intersection.bearings[intersection.in];
const auto exit_bearing = intersection.bearings[intersection.out];
return util::angleBetweenBearings(entry_bearing, exit_bearing);
return util::bearing::angleBetween(entry_bearing, exit_bearing);
};
// Instead of using turn modifiers (e.g. as in isRightTurn) we want to be more strict here.
@ -915,7 +915,7 @@ std::vector<RouteStep> postProcess(std::vector<RouteStep> steps)
// unterminated roundabout
// Move backwards through the instructions until the start and remove the exit number
// A roundabout without exit translates to enter-roundabout.
// A roundabout without exit translates to enter-roundabout
if (has_entered_roundabout || on_roundabout)
{
fixFinalRoundabout(steps);
@ -1238,10 +1238,8 @@ void trimShortSegments(std::vector<RouteStep> &steps, LegGeometry &geometry)
if (zero_length_step)
{
// since we are not only checking for epsilon but for a full meter, we can have multiple
// coordinates here.
// move offsets to front
// geometry offsets have to be adjusted. Move all offsets to the front and reduce by
// one. (This is an inplace forward one and reduce by one)
// coordinates here. Move all offsets to the front and reduce by one. (This is an
// inplace forward one and reduce by one)
std::transform(geometry.segment_offsets.begin() + 1,
geometry.segment_offsets.end(),
geometry.segment_offsets.begin(),
@ -1378,7 +1376,7 @@ void trimShortSegments(std::vector<RouteStep> &steps, LegGeometry &geometry)
geometry.locations[next_to_last_step.geometry_end - 2],
geometry.locations[last_step.geometry_begin]));
last_step.maneuver.bearing_before = bearing;
last_step.intersections.front().bearings.front() = util::reverseBearing(bearing);
last_step.intersections.front().bearings.front() = util::bearing::reverse(bearing);
}
BOOST_ASSERT(steps.back().geometry_end == geometry.locations.size());

View File

@ -415,9 +415,9 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
turn_analysis.GetIntersectionGenerator().TransformIntersectionShapeIntoView(
node_along_road_entering,
incoming_edge,
shape_result.normalised_intersection_shape,
shape_result.annotated_normalized_shape.normalized_shape,
shape_result.intersection_shape,
shape_result.merging_map);
shape_result.annotated_normalized_shape.performed_merges);
auto intersection = turn_analysis.AssignTurnTypes(
node_along_road_entering, incoming_edge, intersection_with_flags_and_angles);

View File

@ -251,6 +251,7 @@ int Extractor::run(ScriptingEnvironment &scripting_environment)
std::vector<bool> node_is_startpoint;
std::vector<EdgeWeight> edge_based_node_weights;
std::vector<QueryNode> internal_to_external_node_map;
auto graph_size = BuildEdgeExpandedGraph(scripting_environment,
internal_to_external_node_map,
edge_based_node_list,

View File

@ -139,8 +139,6 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
return;
}
// FIXME this need to be moved into the profiles
const guidance::RoadClassification road_classification = parsed_way.road_classification;
const auto laneStringToDescription = [](const std::string &lane_string) -> TurnLaneDescription {
if (lane_string.empty())
return {};
@ -237,6 +235,8 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
const auto turn_lane_id_forward = requestId(parsed_way.turn_lanes_forward);
const auto turn_lane_id_backward = requestId(parsed_way.turn_lanes_backward);
const auto road_classification = parsed_way.road_classification;
const constexpr auto MAX_STRING_LENGTH = 255u;
// Get the unique identifier for the street name, destination, and ref
const auto name_iterator = string_map.find(

View File

@ -1,69 +0,0 @@
#include "extractor/geojson_debug_policies.hpp"
#include "util/coordinate.hpp"
#include "util/geojson_debug_policy_toolkit.hpp"
#include <algorithm>
namespace osrm
{
namespace extractor
{
IntersectionPrinter::IntersectionPrinter(
const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<extractor::QueryNode> &node_coordinates,
const extractor::guidance::CoordinateExtractor &coordinate_extractor)
: node_based_graph(node_based_graph), node_coordinates(node_coordinates),
coordinate_extractor(coordinate_extractor)
{
}
util::json::Array IntersectionPrinter::
operator()(const NodeID intersection_node,
const extractor::guidance::Intersection &intersection,
const boost::optional<util::json::Object> &node_style,
const boost::optional<util::json::Object> &way_style) const
{
// request the number of lanes. This process needs to be in sync with what happens over at
// intersection_generator
const auto intersection_lanes = intersection.getHighestConnectedLaneCount(node_based_graph);
std::vector<util::Coordinate> coordinates;
coordinates.reserve(intersection.size());
coordinates.push_back(node_coordinates[intersection_node]);
const auto road_to_coordinate = [&](const extractor::guidance::ConnectedRoad &connected_road) {
const constexpr auto FORWARD = false;
const auto to_node = node_based_graph.GetTarget(connected_road.eid);
return coordinate_extractor.GetCoordinateAlongRoad(
intersection_node, connected_road.eid, FORWARD, to_node, intersection_lanes);
};
std::transform(intersection.begin(),
intersection.end(),
std::back_inserter(coordinates),
road_to_coordinate);
util::json::Array features;
features.values.push_back(
util::makeFeature("MultiPoint", makeJsonArray(coordinates), node_style));
if (coordinates.size() > 1)
{
std::vector<util::Coordinate> line_coordinates(2);
line_coordinates[0] = coordinates.front();
const auto coordinate_to_line = [&](const util::Coordinate coordinate) {
line_coordinates[1] = coordinate;
return util::makeFeature("LineString", makeJsonArray(line_coordinates), way_style);
};
std::transform(std::next(coordinates.begin()),
coordinates.end(),
std::back_inserter(features.values),
coordinate_to_line);
}
return features;
}
} /* namespace extractor */
} /* namespace osrm */

View File

@ -31,7 +31,6 @@ 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 = 40.0;
// The count of lanes assumed when no lanes are present. Since most roads will have lanes for both
@ -650,7 +649,7 @@ bool CoordinateExtractor::IsCurve(const std::vector<util::Coordinate> &coordinat
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);
const auto total_angle = util::angularDeviation(begin_bearing, end_bearing);
return total_angle > 0.5 * NARROW_TURN_ANGLE;
}();
@ -754,8 +753,10 @@ bool CoordinateExtractor::IsCurve(const std::vector<util::Coordinate> &coordinat
return turn_angles;
}();
const bool curve_is_valid =
[&turn_angles, &segment_distances, &segment_length, &considered_lane_width]() {
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
@ -769,8 +770,8 @@ bool CoordinateExtractor::IsCurve(const std::vector<util::Coordinate> &coordinat
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);
(util::angularDeviation(previous_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) &&
(util::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);
@ -779,7 +780,7 @@ bool CoordinateExtractor::IsCurve(const std::vector<util::Coordinate> &coordinat
if (both_actually_turn && turn_direction_switches)
return true;
const bool is_straight = angularDeviation(current_angle, STRAIGHT_ANGLE) < 5;
const bool is_straight = util::angularDeviation(current_angle, STRAIGHT_ANGLE) < 5;
++distance_itr;
if (is_straight)
{
@ -1147,8 +1148,8 @@ CoordinateExtractor::RegressionLine(const std::vector<util::Coordinate> &coordin
return {coordinates.front(), coordinates.back()};
// compute the regression vector based on the sum of least squares
const auto regression_line =
util::coordinate_calculation::leastSquareRegression(sampled_coordinates);
const auto regression_line = util::coordinate_calculation::leastSquareRegression(
sampled_coordinates.begin(), sampled_coordinates.end());
const auto coord_between_front =
util::coordinate_calculation::projectPointOnSegment(
regression_line.first, regression_line.second, coordinates.front())

View File

@ -34,7 +34,7 @@ void ConnectedRoad::mirror()
DirectionModifier::MaxDirectionModifier,
"The list of mirrored modifiers needs to match the available modifiers in size.");
if (angularDeviation(angle, 0) > std::numeric_limits<double>::epsilon())
if (util::angularDeviation(angle, 0) > std::numeric_limits<double>::epsilon())
{
angle = 360 - angle;
instruction.direction_modifier = mirrored_modifiers[instruction.direction_modifier];
@ -48,6 +48,26 @@ ConnectedRoad ConnectedRoad::getMirroredCopy() const
return copy;
}
std::string toString(const IntersectionShapeData &shape)
{
std::string result =
"[shape] " + std::to_string(shape.eid) + " bearing: " + std::to_string(shape.bearing);
return result;
}
std::string toString(const IntersectionViewData &view)
{
std::string result = "[view] ";
result += std::to_string(view.eid);
result += " allows entry: ";
result += std::to_string(view.entry_allowed);
result += " angle: ";
result += std::to_string(view.angle);
result += " bearing: ";
result += std::to_string(view.bearing);
return result;
}
std::string toString(const ConnectedRoad &road)
{
std::string result = "[connection] ";

View File

@ -1,5 +1,8 @@
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/geojson_debug_policies.hpp"
#include "util/geojson_debug_logger.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
@ -76,7 +79,9 @@ IntersectionGenerator::ComputeIntersectionShape(const NodeID node_at_center_of_i
node_at_center_of_intersection, edge_connected_to_intersection, !INVERT, to_node);
const auto segment_length = util::coordinate_calculation::getLength(
coordinates, util::coordinate_calculation::haversineDistance);
coordinates.begin(),
coordinates.end(),
util::coordinate_calculation::haversineDistance);
const auto extract_coordinate = [&](const NodeID from_node,
const EdgeID via_eid,
@ -116,9 +121,9 @@ IntersectionGenerator::ComputeIntersectionShape(const NodeID node_at_center_of_i
return node_based_graph.GetTarget(data.eid) == *sorting_base;
});
if (itr != intersection.end())
return util::reverseBearing(itr->bearing);
return util::bearing::reverse(itr->bearing);
}
return util::reverseBearing(intersection.begin()->bearing);
return util::bearing::reverse(intersection.begin()->bearing);
}();
std::sort(
intersection.begin(), intersection.end(), makeCompareShapeDataByBearing(base_bearing));
@ -154,8 +159,8 @@ IntersectionView IntersectionGenerator::GetConnectedRoads(const NodeID from_node
return TransformIntersectionShapeIntoView(from_node, via_eid, std::move(intersection));
}
std::pair<NodeID, EdgeID> IntersectionGenerator::SkipDegreeTwoNodes(const NodeID starting_node,
const EdgeID via_edge) const
IntersectionGenerationParameters
IntersectionGenerator::SkipDegreeTwoNodes(const NodeID starting_node, const EdgeID via_edge) const
{
NodeID query_node = starting_node;
EdgeID query_edge = via_edge;
@ -177,16 +182,17 @@ std::pair<NodeID, EdgeID> IntersectionGenerator::SkipDegreeTwoNodes(const NodeID
visited_nodes.insert(query_node);
const auto next_node = node_based_graph.GetTarget(query_edge);
const auto next_edge = get_next_edge(query_node, query_edge);
query_node = next_node;
query_edge = next_edge;
if (!node_based_graph.GetEdgeData(query_edge)
.IsCompatibleTo(node_based_graph.GetEdgeData(next_edge)) ||
node_based_graph.GetTarget(next_edge) == starting_node)
break;
query_node = next_node;
query_edge = next_edge;
}
return std::make_pair(query_node, query_edge);
return {query_node, query_edge};
}
IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
@ -205,9 +211,9 @@ IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
const NodeID previous_node,
const EdgeID entering_via_edge,
const IntersectionShape &normalised_intersection,
const IntersectionShape &normalized_intersection,
const IntersectionShape &intersection,
const std::vector<std::pair<EdgeID, EdgeID>> &performed_merges) const
const std::vector<IntersectionNormalizationOperation> &performed_merges) const
{
const auto node_at_intersection = node_based_graph.GetTarget(entering_via_edge);
@ -259,40 +265,40 @@ IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
const auto uturn_bearing = [&]() {
const auto merge_entry = std::find_if(
performed_merges.begin(), performed_merges.end(), [&uturn_edge_itr](const auto entry) {
return entry.first == uturn_edge_itr->eid;
return entry.merged_eid == uturn_edge_itr->eid;
});
if (merge_entry != performed_merges.end())
{
const auto merged_into_id = merge_entry->second;
const auto merged_into_id = merge_entry->into_eid;
const auto merged_u_turn = std::find_if(
normalised_intersection.begin(),
normalised_intersection.end(),
normalized_intersection.begin(),
normalized_intersection.end(),
[&](const IntersectionShapeData &road) { return road.eid == merged_into_id; });
BOOST_ASSERT(merged_u_turn != normalised_intersection.end());
return util::reverseBearing(merged_u_turn->bearing);
BOOST_ASSERT(merged_u_turn != normalized_intersection.end());
return util::bearing::reverse(merged_u_turn->bearing);
}
else
{
const auto uturn_edge_at_normalised_intersection_itr =
std::find_if(normalised_intersection.begin(),
normalised_intersection.end(),
const auto uturn_edge_at_normalized_intersection_itr =
std::find_if(normalized_intersection.begin(),
normalized_intersection.end(),
connect_to_previous_node);
BOOST_ASSERT(uturn_edge_at_normalised_intersection_itr !=
normalised_intersection.end());
return util::reverseBearing(uturn_edge_at_normalised_intersection_itr->bearing);
BOOST_ASSERT(uturn_edge_at_normalized_intersection_itr !=
normalized_intersection.end());
return util::bearing::reverse(uturn_edge_at_normalized_intersection_itr->bearing);
}
}();
IntersectionView intersection_view;
intersection_view.reserve(normalised_intersection.size());
std::transform(normalised_intersection.begin(),
normalised_intersection.end(),
intersection_view.reserve(normalized_intersection.size());
std::transform(normalized_intersection.begin(),
normalized_intersection.end(),
std::back_inserter(intersection_view),
[&](const IntersectionShapeData &road) {
return IntersectionViewData(
road,
is_allowed_turn(road),
util::angleBetweenBearings(uturn_bearing, road.bearing));
util::bearing::angleBetween(uturn_bearing, road.bearing));
});
const auto uturn_edge_at_intersection_view_itr =

View File

@ -5,10 +5,14 @@
#include "util/guidance/name_announcements.hpp"
#include "util/log.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include <algorithm>
#include <cstddef>
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;
namespace osrm
@ -384,19 +388,17 @@ IntersectionHandler::getNextIntersection(const NodeID at, const EdgeID via) cons
// Starting at node `a` via edge `e0` the intersection generator returns the intersection at `c`
// writing `tl` (traffic signal) node and the edge `e1` which has the intersection as target.
NodeID node = SPECIAL_NODEID;
EdgeID edge = SPECIAL_EDGEID;
std::tie(node, edge) = intersection_generator.SkipDegreeTwoNodes(at, via);
auto intersection = intersection_generator(node, edge);
const auto intersection_parameters = intersection_generator.SkipDegreeTwoNodes(at, via);
// This should never happen, guard against nevertheless
if (node == SPECIAL_NODEID || edge == SPECIAL_EDGEID)
if (intersection_parameters.nid == SPECIAL_NODEID ||
intersection_parameters.via_eid == SPECIAL_EDGEID)
{
return boost::none;
}
auto intersection_node = node_based_graph.GetTarget(edge);
auto intersection =
intersection_generator(intersection_parameters.nid, intersection_parameters.via_eid);
auto intersection_node = node_based_graph.GetTarget(intersection_parameters.via_eid);
if (intersection.size() <= 2 || intersection.isTrafficSignalOrBarrier())
{

View File

@ -1,7 +1,6 @@
#include "extractor/guidance/intersection_normalizer.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include <tuple>
#include <utility>
@ -21,34 +20,40 @@ IntersectionNormalizer::IntersectionNormalizer(
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table,
const IntersectionGenerator &intersection_generator)
: node_based_graph(node_based_graph), node_coordinates(node_coordinates),
name_table(name_table), street_name_suffix_table(street_name_suffix_table),
intersection_generator(intersection_generator)
: node_based_graph(node_based_graph), intersection_generator(intersection_generator),
mergable_road_detector(node_based_graph,
node_coordinates,
intersection_generator,
intersection_generator.GetCoordinateExtractor(),
name_table,
street_name_suffix_table)
{
}
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>> IntersectionNormalizer::
IntersectionNormalizer::NormalizationResult IntersectionNormalizer::
operator()(const NodeID node_at_intersection, IntersectionShape intersection) const
{
const auto intersection_copy = intersection;
auto merged_shape_and_merges =
MergeSegregatedRoads(node_at_intersection, std::move(intersection));
merged_shape_and_merges.first = AdjustBearingsForMergeAtDestination(
node_at_intersection, std::move(merged_shape_and_merges.first));
merged_shape_and_merges.normalized_shape = AdjustBearingsForMergeAtDestination(
node_at_intersection, std::move(merged_shape_and_merges.normalized_shape));
return merged_shape_and_merges;
}
bool IntersectionNormalizer::CanMerge(const NodeID intersection_node,
const IntersectionShape &intersection,
std::size_t first_index,
std::size_t second_index) const
std::size_t fist_index_in_ccw,
std::size_t second_index_in_ccw) const
{
BOOST_ASSERT(((first_index + 1) % intersection.size()) == second_index);
BOOST_ASSERT(((fist_index_in_ccw + 1) % intersection.size()) == second_index_in_ccw);
// call wrapper to capture intersection_node and intersection
const auto mergable = [this, intersection_node, &intersection](const std::size_t left_index,
const std::size_t right_index) {
return InnerCanMerge(intersection_node, intersection, left_index, right_index);
};
// don't merge on degree two, since it's most likely a bollard/traffic light or a round way
if (intersection.size() <= 2)
return false;
const auto can_merge = mergable_road_detector.CanMergeRoad(
intersection_node, intersection[fist_index_in_ccw], intersection[second_index_in_ccw]);
/*
* Merging should never depend on order/never merge more than two roads. To ensure that we don't
@ -56,143 +61,71 @@ bool IntersectionNormalizer::CanMerge(const NodeID intersection_node,
* parking lots/border checkpoints), we check if the neigboring roads would be merged as well.
* In that case, we cannot merge, since we would end up merging multiple items together
*/
if (mergable(first_index, second_index))
{
const auto is_distinct_merge =
!mergable(second_index, (second_index + 1) % intersection.size()) &&
!mergable((first_index + intersection.size() - 1) % intersection.size(), first_index) &&
!mergable(second_index,
(first_index + intersection.size() - 1) % intersection.size()) &&
!mergable(first_index, (second_index + 1) % intersection.size());
return is_distinct_merge;
}
else
return false;
const auto is_distinct = [&]() {
const auto next_index_in_ccw = (second_index_in_ccw + 1) % intersection.size();
const auto distinct_to_next_in_ccw = mergable_road_detector.IsDistinctFrom(
intersection[second_index_in_ccw], intersection[next_index_in_ccw]);
const auto prev_index_in_ccw =
(fist_index_in_ccw + intersection.size() - 1) % intersection.size();
const auto distinct_to_prev_in_ccw = mergable_road_detector.IsDistinctFrom(
intersection[prev_index_in_ccw], intersection[fist_index_in_ccw]);
return distinct_to_next_in_ccw && distinct_to_prev_in_ccw;
};
// use lazy evaluation to check only if mergable
return can_merge && is_distinct();
}
// Checks for mergability of two ways that represent the same intersection. For further
// information see interface documentation in header.
bool IntersectionNormalizer::InnerCanMerge(const NodeID node_at_intersection,
const IntersectionShape &intersection,
std::size_t first_index,
std::size_t second_index) const
IntersectionNormalizationOperation
IntersectionNormalizer::DetermineMergeDirection(const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const
{
const auto &first_data = node_based_graph.GetEdgeData(intersection[first_index].eid);
const auto &second_data = node_based_graph.GetEdgeData(intersection[second_index].eid);
// only merge named ids
if (first_data.name_id == EMPTY_NAMEID || second_data.name_id == EMPTY_NAMEID)
return false;
// need to be same name
if (util::guidance::requiresNameAnnounced(
first_data.name_id, second_data.name_id, name_table, street_name_suffix_table))
return false;
// needs to be symmetrical for names
if (util::guidance::requiresNameAnnounced(
second_data.name_id, first_data.name_id, name_table, street_name_suffix_table))
return false;
// compatibility is required
if (first_data.travel_mode != second_data.travel_mode)
return false;
if (first_data.road_classification != second_data.road_classification)
return false;
// may not be on a roundabout
if (first_data.roundabout || second_data.roundabout || first_data.circular ||
second_data.circular)
return false;
// exactly one of them has to be reversed
if (first_data.reversed == second_data.reversed)
return false;
// mergeable if the angle is not too big
const auto angle_between =
angularDeviation(intersection[first_index].bearing, intersection[second_index].bearing);
const auto coordinate_at_intersection = node_coordinates[node_at_intersection];
if (angle_between >= 120)
return false;
const auto isValidYArm = [this, intersection, coordinate_at_intersection, node_at_intersection](
const std::size_t index, const std::size_t other_index) {
const auto GetActualTarget = [&](const std::size_t index) {
EdgeID edge_id;
std::tie(std::ignore, edge_id) = intersection_generator.SkipDegreeTwoNodes(
node_at_intersection, intersection[index].eid);
return node_based_graph.GetTarget(edge_id);
};
const auto target_id = GetActualTarget(index);
const auto other_target_id = GetActualTarget(other_index);
if (target_id == node_at_intersection || other_target_id == node_at_intersection)
return false;
const auto coordinate_at_target = node_coordinates[target_id];
const auto coordinate_at_other_target = node_coordinates[other_target_id];
const auto turn_bearing =
util::coordinate_calculation::bearing(coordinate_at_intersection, coordinate_at_target);
const auto other_turn_bearing = util::coordinate_calculation::bearing(
coordinate_at_intersection, coordinate_at_other_target);
// fuzzy becomes narrower due to minor differences in angle computations, yay floating point
const bool becomes_narrower =
angularDeviation(turn_bearing, other_turn_bearing) < NARROW_TURN_ANGLE &&
angularDeviation(turn_bearing, other_turn_bearing) <=
angularDeviation(intersection[index].bearing, intersection[other_index].bearing) +
MAXIMAL_ALLOWED_NO_TURN_DEVIATION;
return becomes_narrower;
};
const bool is_y_arm_first = isValidYArm(first_index, second_index);
const bool is_y_arm_second = isValidYArm(second_index, first_index);
// Only merge valid y-arms
if (!is_y_arm_first || !is_y_arm_second)
return false;
if (angle_between < 60)
return true;
// Finally, we also allow merging if all streets offer the same name, it is only three roads and
// the angle is not fully extreme:
if (intersection.size() != 3)
return false;
// since we have an intersection of size three now, there is only one index we are not looking
// at right now. The final index in the intersection is calculated next:
const std::size_t third_index = [first_index, second_index]() {
if (first_index == 0)
return second_index == 2 ? 1 : 2;
else if (first_index == 1)
return second_index == 2 ? 0 : 2;
if (node_based_graph.GetEdgeData(lhs.eid).reversed)
return {lhs.eid, rhs.eid};
else
return second_index == 1 ? 0 : 1;
}();
return {rhs.eid, lhs.eid};
}
// needs to be same road coming in
const auto &third_data = node_based_graph.GetEdgeData(intersection[third_index].eid);
IntersectionShapeData IntersectionNormalizer::MergeRoads(const IntersectionShapeData &into,
const IntersectionShapeData &from) const
{
// 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;
};
if (third_data.name_id != EMPTY_NAMEID &&
util::guidance::requiresNameAnnounced(
third_data.name_id, first_data.name_id, name_table, street_name_suffix_table))
return false;
// 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
{
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;
}
};
// we only allow collapsing of a Y like fork. So the angle to the third index has to be
// roughly equal:
const auto y_angle_difference = angularDeviation(
angularDeviation(intersection[third_index].bearing, intersection[first_index].bearing),
angularDeviation(intersection[third_index].bearing, intersection[second_index].bearing));
// Allow larger angles if its three roads only of the same name
// This is a heuristic and might need to be revised.
const bool assume_y_intersection =
angle_between < 100 && y_angle_difference < FUZZY_ANGLE_DIFFERENCE;
return assume_y_intersection;
auto result = into;
BOOST_ASSERT(!node_based_graph.GetEdgeData(into.eid).reversed);
result.bearing = combineAngles(into.bearing, from.bearing);
BOOST_ASSERT(0 <= result.bearing && result.bearing < 360.0);
return result;
}
IntersectionShapeData
IntersectionNormalizer::MergeRoads(const IntersectionNormalizationOperation direction,
const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const
{
if (direction.merged_eid == lhs.eid)
return MergeRoads(rhs, lhs);
else
return MergeRoads(lhs, rhs);
}
/*
@ -218,7 +151,7 @@ bool IntersectionNormalizer::InnerCanMerge(const NodeID node_at_intersection,
* Anything containing the first u-turn in a merge affects all other angles
* and is handled separately from all others.
*/
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>>
IntersectionNormalizer::NormalizationResult
IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
IntersectionShape intersection) const
{
@ -226,50 +159,25 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
return (index + intersection.size() - 1) % intersection.size();
};
// 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;
};
// 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
{
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;
}
};
// This map stores for all edges that participated in a merging operation in which edge id they
// end up in the end. We only store what we have merged into other edges.
std::vector<std::pair<EdgeID, EdgeID>> merging_map;
const auto merge = [this, combineAngles, &merging_map](const IntersectionShapeData &first,
std::vector<IntersectionNormalizationOperation> merging_map;
const auto merge = [this, &merging_map](const IntersectionShapeData &first,
const IntersectionShapeData &second) {
IntersectionShapeData result =
!node_based_graph.GetEdgeData(first.eid).reversed ? first : second;
result.bearing = combineAngles(first.bearing, second.bearing);
BOOST_ASSERT(0 <= result.bearing && result.bearing < 360.0);
// the other ID
const auto merged_from = result.eid == first.eid ? second.eid : first.eid;
const auto direction = DetermineMergeDirection(first, second);
BOOST_ASSERT(
std::find_if(merging_map.begin(), merging_map.end(), [merged_from](const auto pair) {
return pair.first == merged_from;
std::find_if(merging_map.begin(), merging_map.end(), [direction](const auto pair) {
return pair.merged_eid == direction.merged_eid;
}) == merging_map.end());
merging_map.push_back(std::make_pair(merged_from, result.eid));
return result;
merging_map.push_back(direction);
return MergeRoads(direction, first, second);
};
if (intersection.size() <= 1)
return std::make_pair(intersection, merging_map);
return {intersection, merging_map};
const auto intersection_copy = intersection;
// check for merges including the basic u-turn
// these result in an adjustment of all other angles. This is due to how these angles are
// perceived. Considering the following example:
@ -301,11 +209,9 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
// If the merge occurs at the u-turn edge, we need to adjust all angles, though, since they are
// with respect to the now changed perceived location of a. If we move (a) to the left, we add
// the difference to all angles. Otherwise we subtract it.
bool merged_first = false;
// these result in an adjustment of all other angles
if (CanMerge(intersection_node, intersection, intersection.size() - 1, 0))
{
merged_first = true;
// moving `a` to the left
intersection[0] = merge(intersection.front(), intersection.back());
// FIXME if we have a left-sided country, we need to switch this off and enable it
@ -314,7 +220,6 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
}
else if (CanMerge(intersection_node, intersection, 0, 1))
{
merged_first = true;
intersection[0] = merge(intersection.front(), intersection[1]);
intersection.erase(intersection.begin() + 1);
}
@ -331,8 +236,7 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
--index;
}
}
return std::make_pair(intersection, merging_map);
return {intersection, merging_map};
}
// OSM can have some very steep angles for joining roads. Considering the following intersection:
@ -432,7 +336,7 @@ IntersectionNormalizer::AdjustBearingsForMergeAtDestination(const NodeID node_at
intersection[(intersection.size() + index - 1) % intersection.size()]);
// at the target intersection, we merge to the right, so we need to shift the current
// angle to the left
road.bearing = adjustAngle(road.bearing, -corrected_offset);
road.bearing = adjustAngle(road.bearing, corrected_offset);
}
else if (CanMerge(node_at_next_intersection,
next_intersection_along_road,
@ -447,7 +351,7 @@ IntersectionNormalizer::AdjustBearingsForMergeAtDestination(const NodeID node_at
get_corrected_offset(offset, road, intersection[(index + 1) % intersection.size()]);
// at the target intersection, we merge to the left, so we need to shift the current
// angle to the right
road.bearing = adjustAngle(road.bearing, corrected_offset);
road.bearing = adjustAngle(road.bearing, -corrected_offset);
}
}
return intersection;

View File

@ -0,0 +1,469 @@
#include "extractor/guidance/mergable_road_detector.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/node_based_graph_walker.hpp"
#include "extractor/query_node.hpp"
#include "extractor/suffix_table.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include "util/name_table.hpp"
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace
{
// check a connected road for equality of a name
inline auto makeCheckRoadForName(const NameID name_id,
const util::NodeBasedDynamicGraph &node_based_graph,
const util::NameTable &name_table,
const SuffixTable &suffix_table)
{
return [name_id, &node_based_graph, &name_table, &suffix_table](
const MergableRoadDetector::MergableRoadData &road) {
// since we filter here, we don't want any other name than the one we are looking for
const auto road_name = node_based_graph.GetEdgeData(road.eid).name_id;
if (name_id == EMPTY_NAMEID || road_name == EMPTY_NAMEID)
return true;
const auto requires_announcement =
util::guidance::requiresNameAnnounced(name_id, road_name, name_table, suffix_table) ||
util::guidance::requiresNameAnnounced(road_name, name_id, name_table, suffix_table);
return requires_announcement;
};
}
}
MergableRoadDetector::MergableRoadDetector(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_coordinates,
const IntersectionGenerator &intersection_generator,
const CoordinateExtractor &coordinate_extractor,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: node_based_graph(node_based_graph), node_coordinates(node_coordinates),
intersection_generator(intersection_generator), coordinate_extractor(coordinate_extractor),
name_table(name_table), street_name_suffix_table(street_name_suffix_table)
{
}
bool MergableRoadDetector::CanMergeRoad(const NodeID intersection_node,
const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const
{
// roads should be somewhat close
if (angularDeviation(lhs.bearing, rhs.bearing) > MERGABLE_ANGLE_DIFFERENCE)
return false;
const auto &lhs_edge_data = node_based_graph.GetEdgeData(lhs.eid);
const auto &rhs_edge_data = node_based_graph.GetEdgeData(rhs.eid);
// and they need to describe the same road
if (!EdgeDataSupportsMerge(lhs_edge_data, rhs_edge_data))
return false;
/* don't use any circular links, since they mess up detection we jump out early.
*
* / -- \
* a ---- b - - /
*/
const auto road_target = [this](const MergableRoadData &road) {
return node_based_graph.GetTarget(road.eid);
};
// TODO might have to skip over trivial intersections
if (road_target(lhs) == intersection_node || road_target(lhs) == intersection_node)
return false;
// Don't merge turning circles/traffic loops
if (IsTrafficLoop(intersection_node, lhs) || IsTrafficLoop(intersection_node, rhs))
return false;
// needs to be checked prior to link roads, since connections can seem like links
if (IsTrafficIsland(intersection_node, lhs, rhs))
return true;
// Don't merge link roads
if (IsLinkRoad(intersection_node, lhs) || IsLinkRoad(intersection_node, rhs))
return false;
// check if we simply split up prior to an intersection
if (IsNarrowTriangle(intersection_node, lhs, rhs))
return true;
// finally check if two roads describe the direction
return HaveSameDirection(intersection_node, lhs, rhs);
}
bool MergableRoadDetector::HaveIdenticalNames(const NameID lhs, const NameID rhs) const
{
const auto non_empty = (lhs != EMPTY_NAMEID) && (rhs != EMPTY_NAMEID);
// symmetrical check for announcements
return non_empty &&
!util::guidance::requiresNameAnnounced(lhs, rhs, name_table, street_name_suffix_table) &&
!util::guidance::requiresNameAnnounced(rhs, lhs, name_table, street_name_suffix_table);
}
bool MergableRoadDetector::IsDistinctFrom(const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
// needs to be far away
if (angularDeviation(lhs.bearing, rhs.bearing) > MERGABLE_ANGLE_DIFFERENCE)
return true;
else // or it cannot have the same name
return !HaveIdenticalNames(node_based_graph.GetEdgeData(lhs.eid).name_id,
node_based_graph.GetEdgeData(rhs.eid).name_id);
}
bool MergableRoadDetector::EdgeDataSupportsMerge(const util::NodeBasedEdgeData &lhs_edge_data,
const util::NodeBasedEdgeData &rhs_edge_data) const
{
// roundabouts are special, simply don't hurt them. We might not want to bear the
// consequences
if (lhs_edge_data.roundabout || rhs_edge_data.roundabout)
return false;
/* to describe the same road, but in opposite directions (which is what we require for a
* merge), the roads have to feature one reversed and one non-reversed edge
*/
if (lhs_edge_data.reversed == rhs_edge_data.reversed)
return false;
/* The travel mode should be the same for both roads. If we were to merge different travel
* modes, we would hide information/run the risk of loosing valid choices (e.g. short period
* of pushing)
*/
if (lhs_edge_data.travel_mode != rhs_edge_data.travel_mode)
return false;
// we require valid names
if (!HaveIdenticalNames(lhs_edge_data.name_id, rhs_edge_data.name_id))
return false;
return lhs_edge_data.road_classification == rhs_edge_data.road_classification;
}
bool MergableRoadDetector::IsTrafficLoop(const NodeID intersection_node,
const MergableRoadData &road) const
{
const auto connection = intersection_generator.SkipDegreeTwoNodes(intersection_node, road.eid);
return intersection_node == node_based_graph.GetTarget(connection.via_eid);
}
bool MergableRoadDetector::IsNarrowTriangle(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
// selection data to the right and left
const auto constexpr SMALL_RANDOM_HOPLIMIT = 5;
IntersectionFinderAccumulator left_accumulator(SMALL_RANDOM_HOPLIMIT, intersection_generator),
right_accumulator(SMALL_RANDOM_HOPLIMIT, intersection_generator);
/* Standard following the straightmost road
* Since both items have the same id, we can `select` based on any setup
*/
SelectStraightmostRoadByNameAndOnlyChoice selector(
node_based_graph.GetEdgeData(lhs.eid).name_id, lhs.bearing, /*requires entry=*/false);
NodeBasedGraphWalker graph_walker(node_based_graph, intersection_generator);
graph_walker.TraverseRoad(intersection_node, lhs.eid, left_accumulator, selector);
/* if the intersection does not have a right turn, we continue onto the next one once
* (skipping over a single small side street)
*/
if (angularDeviation(left_accumulator.intersection.findClosestTurn(ORTHOGONAL_ANGLE)->angle,
ORTHOGONAL_ANGLE) > NARROW_TURN_ANGLE)
{
graph_walker.TraverseRoad(
node_based_graph.GetTarget(left_accumulator.via_edge_id),
left_accumulator.intersection.findClosestTurn(STRAIGHT_ANGLE)->eid,
left_accumulator,
selector);
}
const auto distance_to_triangle = util::coordinate_calculation::haversineDistance(
node_coordinates[intersection_node],
node_coordinates[node_based_graph.GetTarget(left_accumulator.via_edge_id)]);
// don't move too far down the road
const constexpr auto RANGE_TO_TRIANGLE_LIMIT = 80;
if (distance_to_triangle > RANGE_TO_TRIANGLE_LIMIT)
return false;
graph_walker.TraverseRoad(intersection_node, rhs.eid, right_accumulator, selector);
if (angularDeviation(right_accumulator.intersection.findClosestTurn(270)->angle, 270) >
NARROW_TURN_ANGLE)
{
graph_walker.TraverseRoad(
node_based_graph.GetTarget(right_accumulator.via_edge_id),
right_accumulator.intersection.findClosestTurn(STRAIGHT_ANGLE)->eid,
right_accumulator,
selector);
}
BOOST_ASSERT(!left_accumulator.intersection.empty() && !right_accumulator.intersection.empty());
// find the closes resembling a right turn
const auto connector_turn = left_accumulator.intersection.findClosestTurn(ORTHOGONAL_ANGLE);
/* check if that right turn connects to the right_accumulator intersection (i.e. we have a
* triangle)
* a connection should be somewhat to the right, when looking at the left side of the
* triangle
*
* b ..... c
* \ /
* \ /
* \ /
* a
*
* e.g. here when looking at `a,b`, a narrow triangle should offer a turn to the right, when
* we want to connect to c
*/
if (angularDeviation(connector_turn->angle, ORTHOGONAL_ANGLE) > NARROW_TURN_ANGLE)
return false;
const auto num_lanes = [this](const MergableRoadData &road) {
return std::max<std::uint8_t>(
node_based_graph.GetEdgeData(road.eid).road_classification.GetNumberOfLanes(), 1);
};
// the width we can bridge at the intersection
const auto assumed_road_width = (num_lanes(lhs) + num_lanes(rhs)) * ASSUMED_LANE_WIDTH;
const constexpr auto MAXIMAL_ALLOWED_TRAFFIC_ISLAND_WIDTH = 10;
const auto distance_between_triangle_corners = util::coordinate_calculation::haversineDistance(
node_coordinates[node_based_graph.GetTarget(left_accumulator.via_edge_id)],
node_coordinates[node_based_graph.GetTarget(right_accumulator.via_edge_id)]);
if (distance_between_triangle_corners >
(assumed_road_width + MAXIMAL_ALLOWED_TRAFFIC_ISLAND_WIDTH))
return false;
// check if both intersections are connected
IntersectionFinderAccumulator connect_accumulator(SMALL_RANDOM_HOPLIMIT,
intersection_generator);
graph_walker.TraverseRoad(node_based_graph.GetTarget(left_accumulator.via_edge_id),
connector_turn->eid,
connect_accumulator,
selector);
// the if both items are connected
return node_based_graph.GetTarget(connect_accumulator.via_edge_id) ==
node_based_graph.GetTarget(right_accumulator.via_edge_id);
}
bool MergableRoadDetector::HaveSameDirection(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
if (angularDeviation(lhs.bearing, rhs.bearing) > MERGABLE_ANGLE_DIFFERENCE)
return false;
// Find a coordinate following a road that is far away
NodeBasedGraphWalker graph_walker(node_based_graph, intersection_generator);
const auto getCoordinatesAlongWay = [&](const EdgeID edge_id, const double max_length) {
LengthLimitedCoordinateAccumulator accumulator(coordinate_extractor, max_length);
SelectStraightmostRoadByNameAndOnlyChoice selector(
node_based_graph.GetEdgeData(edge_id).name_id, lhs.bearing, /*requires_entry=*/false);
graph_walker.TraverseRoad(intersection_node, edge_id, accumulator, selector);
return std::make_pair(accumulator.accumulated_length, accumulator.coordinates);
};
std::vector<util::Coordinate> coordinates_to_the_left, coordinates_to_the_right;
double distance_traversed_to_the_left, distance_traversed_to_the_right;
// many roads only do short parallel segments. To get a good impression of how `parallel` two
// roads are, we look 100 meters down the road (wich can be quite short for very broad roads).
const double constexpr distance_to_extract = 100;
std::tie(distance_traversed_to_the_left, coordinates_to_the_left) =
getCoordinatesAlongWay(lhs.eid, distance_to_extract);
// tuned parameter, if we didn't get as far as 40 meters, we might barely look past an
// intersection.
const auto constexpr MINIMUM_LENGTH_FOR_PARALLEL_DETECTION = 40;
// quit early if the road is not very long
if (distance_traversed_to_the_left <= MINIMUM_LENGTH_FOR_PARALLEL_DETECTION)
return false;
std::tie(distance_traversed_to_the_right, coordinates_to_the_right) =
getCoordinatesAlongWay(rhs.eid, distance_to_extract);
if (distance_traversed_to_the_right <= MINIMUM_LENGTH_FOR_PARALLEL_DETECTION)
return false;
const auto connect_again = (coordinates_to_the_left.back() == coordinates_to_the_right.back());
// sampling to correctly weight longer segments in regression calculations
const auto constexpr SAMPLE_INTERVAL = 5;
coordinates_to_the_left = coordinate_extractor.SampleCoordinates(
std::move(coordinates_to_the_left), distance_to_extract, SAMPLE_INTERVAL);
coordinates_to_the_right = coordinate_extractor.SampleCoordinates(
std::move(coordinates_to_the_right), distance_to_extract, SAMPLE_INTERVAL);
/* extract the number of lanes for a road
* restricts a vector to the last two thirds of the length
*/
const auto prune = [](auto &data_vector) {
BOOST_ASSERT(data_vector.size() >= 3);
//erase the first third of the vector
data_vector.erase(data_vector.begin(), data_vector.begin() + data_vector.size() / 3);
};
/* if the coordinates meet up again, e.g. due to a split and join, pruning can have a negative
* effect. We therefore only prune away the beginning, if the roads don't meet up again as well.
*/
if (!connect_again)
{
prune(coordinates_to_the_left);
prune(coordinates_to_the_right);
}
const auto are_parallel =
util::coordinate_calculation::areParallel(coordinates_to_the_left.begin(),
coordinates_to_the_left.end(),
coordinates_to_the_right.begin(),
coordinates_to_the_right.end());
if (!are_parallel)
return false;
// compare reference distance:
const auto distance_between_roads = util::coordinate_calculation::findClosestDistance(
coordinates_to_the_left[coordinates_to_the_left.size() / 2],
coordinates_to_the_right.begin(),
coordinates_to_the_right.end());
const auto lane_count_lhs = std::max<int>(
1, node_based_graph.GetEdgeData(lhs.eid).road_classification.GetNumberOfLanes());
const auto lane_count_rhs = std::max<int>(
1, node_based_graph.GetEdgeData(rhs.eid).road_classification.GetNumberOfLanes());
const auto combined_road_width = 0.5 * (lane_count_lhs + lane_count_rhs) * ASSUMED_LANE_WIDTH;
const auto constexpr MAXIMAL_ALLOWED_SEPARATION_WIDTH = 8;
return distance_between_roads <= combined_road_width + MAXIMAL_ALLOWED_SEPARATION_WIDTH;
}
bool MergableRoadDetector::IsTrafficIsland(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
/* compute the set of all intersection_nodes along the way of an edge, until it reaches a
* location with the same name repeatet at least three times
*/
const auto left_connection =
intersection_generator.SkipDegreeTwoNodes(intersection_node, lhs.eid);
const auto right_connection =
intersection_generator.SkipDegreeTwoNodes(intersection_node, rhs.eid);
const auto left_candidate = node_based_graph.GetTarget(left_connection.via_eid);
const auto right_candidate = node_based_graph.GetTarget(right_connection.via_eid);
const auto candidate_is_valid =
left_candidate == right_candidate && left_candidate != intersection_node;
if (!candidate_is_valid)
return false;
// check if all entries at the destination or at the source are the same
const auto all_same_name_and_degree_three = [this](const NodeID nid) {
// check if the intersection found has degree three
if (node_based_graph.GetOutDegree(nid) != 3)
return false;
// check if all items share a name
const auto range = node_based_graph.GetAdjacentEdgeRange(nid);
const auto required_name_id = node_based_graph.GetEdgeData(range.front()).name_id;
const auto has_required_name = [this, required_name_id](const auto edge_id) {
const auto road_name = node_based_graph.GetEdgeData(edge_id).name_id;
if (required_name_id == EMPTY_NAMEID || road_name == EMPTY_NAMEID)
return false;
return !util::guidance::requiresNameAnnounced(
required_name_id, road_name, name_table, street_name_suffix_table) ||
!util::guidance::requiresNameAnnounced(
road_name, required_name_id, name_table, street_name_suffix_table);
};
/* the beautiful way would be:
* return range.end() == std::find_if_not(range.begin(), range.end(), has_required_name);
* but that does not work due to range concepts
*/
for (const auto eid : range)
if (!has_required_name(eid))
return false;
return true;
};
const auto degree_three_connect_in = all_same_name_and_degree_three(intersection_node);
const auto degree_three_connect_out = all_same_name_and_degree_three(left_candidate);
if (!degree_three_connect_in && !degree_three_connect_out)
return false;
const auto distance_between_candidates = util::coordinate_calculation::haversineDistance(
node_coordinates[intersection_node], node_coordinates[left_candidate]);
const auto both_split_join = degree_three_connect_in && degree_three_connect_out;
// allow longer separations if both are joining directly
// widths are chosen via tuning on traffic islands
return both_split_join ? (distance_between_candidates < 30)
: (distance_between_candidates < 15);
}
bool MergableRoadDetector::IsLinkRoad(const NodeID intersection_node,
const MergableRoadData &road) const
{
const auto next_intersection_parameters =
intersection_generator.SkipDegreeTwoNodes(intersection_node, road.eid);
const auto next_intersection_along_road = intersection_generator.GetConnectedRoads(
next_intersection_parameters.nid, next_intersection_parameters.via_eid);
const auto extract_name_id = [this](const MergableRoadData &road) {
return node_based_graph.GetEdgeData(road.eid).name_id;
};
const auto requested_name_id = extract_name_id(road);
const auto next_road_along_path = next_intersection_along_road.findClosestTurn(
STRAIGHT_ANGLE,
makeCheckRoadForName(
requested_name_id, node_based_graph, name_table, street_name_suffix_table));
// we need to have a continuing road to successfully detect a link road
if (next_road_along_path == next_intersection_along_road.end())
return false;
const auto opposite_of_next_road_along_path = next_intersection_along_road.findClosestTurn(
util::restrictAngleToValidRange(next_road_along_path->angle + STRAIGHT_ANGLE));
// we cannot be looking at the same road we came from
if (node_based_graph.GetTarget(opposite_of_next_road_along_path->eid) ==
next_intersection_parameters.nid)
return false;
/* check if the opposite of the next road decision was sane. It could have been just as well our
* incoming road.
*/
if (angularDeviation(angularDeviation(next_road_along_path->angle, STRAIGHT_ANGLE),
angularDeviation(opposite_of_next_road_along_path->angle, 0)) <
FUZZY_ANGLE_DIFFERENCE)
return false;
// near straight road that continues
return angularDeviation(opposite_of_next_road_along_path->angle, next_road_along_path->angle) >=
(STRAIGHT_ANGLE - FUZZY_ANGLE_DIFFERENCE) &&
EdgeDataSupportsMerge(
node_based_graph.GetEdgeData(next_road_along_path->eid),
node_based_graph.GetEdgeData(opposite_of_next_road_along_path->eid));
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -1,4 +1,7 @@
#include "extractor/guidance/node_based_graph_walker.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include <utility>
using osrm::util::angularDeviation;
@ -18,11 +21,8 @@ NodeBasedGraphWalker::NodeBasedGraphWalker(const util::NodeBasedDynamicGraph &no
}
LengthLimitedCoordinateAccumulator::LengthLimitedCoordinateAccumulator(
const extractor::guidance::CoordinateExtractor &coordinate_extractor,
const util::NodeBasedDynamicGraph &node_based_graph,
const double max_length)
: coordinate_extractor(coordinate_extractor), node_based_graph(node_based_graph),
max_length(max_length), accumulated_length(0)
const extractor::guidance::CoordinateExtractor &coordinate_extractor, const double max_length)
: accumulated_length(0), coordinate_extractor(coordinate_extractor), max_length(max_length)
{
}
@ -37,8 +37,10 @@ void LengthLimitedCoordinateAccumulator::update(const NodeID from_node,
auto current_coordinates =
coordinate_extractor.GetForwardCoordinatesAlongRoad(from_node, via_edge);
const auto length = util::coordinate_calculation::getLength(
current_coordinates, util::coordinate_calculation::haversineDistance);
const auto length =
util::coordinate_calculation::getLength(current_coordinates.begin(),
current_coordinates.end(),
util::coordinate_calculation::haversineDistance);
// in case we get too many coordinates, we limit them to our desired length
if (length + accumulated_length > max_length)
@ -48,6 +50,7 @@ void LengthLimitedCoordinateAccumulator::update(const NodeID from_node,
coordinates.insert(coordinates.end(), current_coordinates.begin(), current_coordinates.end());
accumulated_length += length;
accumulated_length = std::min(accumulated_length, max_length);
}
// ---------------------------------------------------------------------------------
@ -60,17 +63,15 @@ SelectRoadByNameOnlyChoiceAndStraightness::SelectRoadByNameOnlyChoiceAndStraight
boost::optional<EdgeID> SelectRoadByNameOnlyChoiceAndStraightness::
operator()(const NodeID /*nid*/,
const EdgeID /*via_edge_id*/,
const Intersection &intersection,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const
{
BOOST_ASSERT(!intersection.empty());
const auto comparator = [this, &node_based_graph](const ConnectedRoad &lhs,
const ConnectedRoad &rhs) {
const auto comparator = [this, &node_based_graph](const IntersectionViewData &lhs,
const IntersectionViewData &rhs) {
// the score of an elemnt results in an ranking preferring valid entries, if required over
// invalid
// requested name_ids over non-requested
// narrow deviations over non-narrow
const auto score = [this, &node_based_graph](const ConnectedRoad &road) {
// invalid requested name_ids over non-requested narrow deviations over non-narrow
const auto score = [this, &node_based_graph](const IntersectionViewData &road) {
double result_score = 0;
// since angular deviation is limited by 0-180, we add 360 for invalid
if (requires_entry && !road.entry_allowed)
@ -95,6 +96,75 @@ operator()(const NodeID /*nid*/,
return (*min_element).eid;
}
// ---------------------------------------------------------------------------------
SelectStraightmostRoadByNameAndOnlyChoice::SelectStraightmostRoadByNameAndOnlyChoice(
const NameID desired_name_id, const double initial_bearing, const bool requires_entry)
: desired_name_id(desired_name_id), initial_bearing(initial_bearing),
requires_entry(requires_entry)
{
}
boost::optional<EdgeID> SelectStraightmostRoadByNameAndOnlyChoice::
operator()(const NodeID /*nid*/,
const EdgeID /*via_edge_id*/,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const
{
BOOST_ASSERT(!intersection.empty());
if (intersection.size() == 1)
return {};
const auto comparator = [this, &node_based_graph](const IntersectionViewData &lhs,
const IntersectionViewData &rhs) {
// the score of an elemnt results in an ranking preferring valid entries, if required over
// invalid requested name_ids over non-requested narrow deviations over non-narrow
const auto score = [this, &node_based_graph](const IntersectionViewData &road) {
double result_score = 0;
// since angular deviation is limited by 0-180, we add 360 for invalid
if (requires_entry && !road.entry_allowed)
result_score += 360.;
// 180 for undesired name-ids
if (desired_name_id != node_based_graph.GetEdgeData(road.eid).name_id)
result_score += 180;
return result_score + angularDeviation(road.angle, STRAIGHT_ANGLE);
};
return score(lhs) < score(rhs);
};
const auto count_desired_name =
std::count_if(std::begin(intersection),
std::end(intersection),
[this, &node_based_graph](const auto &road) {
return node_based_graph.GetEdgeData(road.eid).name_id == desired_name_id;
});
if (count_desired_name > 2)
return {};
const auto min_element =
std::min_element(std::next(std::begin(intersection)), std::end(intersection), comparator);
const auto is_valid_choice = !requires_entry || min_element->entry_allowed;
const auto is_only_choice_with_same_name =
count_desired_name <= 2 && // <= in case we come from a bridge
node_based_graph.GetEdgeData(min_element->eid).name_id == desired_name_id &&
angularDeviation(min_element->angle, STRAIGHT_ANGLE) < 100; // don't do crazy turns
const auto has_valid_angle =
((intersection.size() == 2 ||
intersection.findClosestTurn(STRAIGHT_ANGLE) == min_element) &&
angularDeviation(min_element->angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE) &&
angularDeviation(initial_bearing, min_element->bearing) < NARROW_TURN_ANGLE;
// in cases where we have two edges between roads, we can have quite severe angles due to the
// random split OSRM does to break up parallel edges at any coordinate
if (!is_valid_choice || !(is_only_choice_with_same_name || has_valid_angle))
return {};
else
return (*min_element).eid;
}
// ---------------------------------------------------------------------------------
IntersectionFinderAccumulator::IntersectionFinderAccumulator(
const std::uint8_t hop_limit, const IntersectionGenerator &intersection_generator)
@ -123,7 +193,7 @@ void IntersectionFinderAccumulator::update(const NodeID from_node,
nid = from_node;
via_edge_id = via_edge;
intersection = intersection_generator.GetConnectedRoads(from_node, via_edge);
intersection = intersection_generator.GetConnectedRoads(from_node, via_edge, true);
}
} // namespace guidance

View File

@ -5,7 +5,6 @@
#include "util/guidance/name_announcements.hpp"
#include <cmath>
#include <algorithm>
#include <iterator>
#include <limits>

View File

@ -80,13 +80,13 @@ Intersection TurnAnalysis::operator()(const NodeID node_prior_to_intersection,
TurnAnalysis::ShapeResult shape_result =
ComputeIntersectionShapes(node_based_graph.GetTarget(entering_via_edge));
// assign valid flags to normalised_shape
// assign valid flags to normalized_shape
const auto intersection_view = intersection_generator.TransformIntersectionShapeIntoView(
node_prior_to_intersection,
entering_via_edge,
shape_result.normalised_intersection_shape,
shape_result.annotated_normalized_shape.normalized_shape,
shape_result.intersection_shape,
shape_result.merging_map);
shape_result.annotated_normalized_shape.performed_merges);
// assign the turn types to the intersection
return AssignTurnTypes(node_prior_to_intersection, entering_via_edge, intersection_view);
@ -171,9 +171,8 @@ TurnAnalysis::ComputeIntersectionShapes(const NodeID node_at_center_of_intersect
intersection_shape.intersection_shape =
intersection_generator.ComputeIntersectionShape(node_at_center_of_intersection);
std::tie(intersection_shape.normalised_intersection_shape, intersection_shape.merging_map) =
intersection_normalizer(node_at_center_of_intersection,
intersection_shape.intersection_shape);
intersection_shape.annotated_normalized_shape = intersection_normalizer(
node_at_center_of_intersection, intersection_shape.intersection_shape);
return intersection_shape;
}

View File

@ -1,5 +1,6 @@
#include "extractor/guidance/turn_discovery.hpp"
#include "extractor/guidance/constants.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
using osrm::util::angularDeviation;
@ -43,8 +44,11 @@ bool findPreviousIntersection(const NodeID node_v,
const constexpr double COMBINE_DISTANCE_CUTOFF = 30;
const auto coordinate_extractor = intersection_generator.GetCoordinateExtractor();
const auto via_edge_length = util::coordinate_calculation::getLength(
coordinate_extractor.GetForwardCoordinatesAlongRoad(node_v, via_edge),
const auto coordinates_along_via_edge =
coordinate_extractor.GetForwardCoordinatesAlongRoad(node_v, via_edge);
const auto via_edge_length =
util::coordinate_calculation::getLength(coordinates_along_via_edge.begin(),
coordinates_along_via_edge.end(),
&util::coordinate_calculation::haversineDistance);
// we check if via-edge is too short. In this case the previous turn cannot influence the turn

View File

@ -10,7 +10,6 @@
#include <boost/assert.hpp>
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;

View File

@ -5,8 +5,8 @@
#include <boost/assert.hpp>
#include <cmath>
#include <algorithm>
#include <iterator>
#include <limits>
#include <utility>
@ -125,29 +125,17 @@ Coordinate centroid(const Coordinate lhs, const Coordinate rhs)
return centroid;
}
double degToRad(const double degree)
{
using namespace boost::math::constants;
return degree * (pi<double>() / 180.0);
}
double radToDeg(const double radian)
{
using namespace boost::math::constants;
return radian * (180.0 * (1. / pi<double>()));
}
double bearing(const Coordinate first_coordinate, const Coordinate second_coordinate)
{
const double lon_diff =
static_cast<double>(toFloating(second_coordinate.lon - first_coordinate.lon));
const double lon_delta = degToRad(lon_diff);
const double lat1 = degToRad(static_cast<double>(toFloating(first_coordinate.lat)));
const double lat2 = degToRad(static_cast<double>(toFloating(second_coordinate.lat)));
const double lon_delta = detail::degToRad(lon_diff);
const double lat1 = detail::degToRad(static_cast<double>(toFloating(first_coordinate.lat)));
const double lat2 = detail::degToRad(static_cast<double>(toFloating(second_coordinate.lat)));
const double y = std::sin(lon_delta) * std::cos(lat2);
const double x =
std::cos(lat1) * std::sin(lat2) - std::sin(lat1) * std::cos(lat2) * std::cos(lon_delta);
double result = radToDeg(std::atan2(y, x));
double result = detail::radToDeg(std::atan2(y, x));
while (result < 0.0)
{
result += 360.0;
@ -320,47 +308,72 @@ bool isCCW(const Coordinate first_coordinate,
return signedArea(first_coordinate, second_coordinate, third_coordinate) > 0;
}
std::pair<util::Coordinate, util::Coordinate>
leastSquareRegression(const std::vector<util::Coordinate> &coordinates)
// find the closest distance between a coordinate and a segment
double findClosestDistance(const Coordinate coordinate,
const Coordinate segment_begin,
const Coordinate segment_end)
{
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<double>(toFloating(coordinates.front().lon));
double max_lon = static_cast<double>(toFloating(coordinates.front().lon));
for (const auto coord : coordinates)
{
min_lon = std::min(min_lon, static_cast<double>(toFloating(coord.lon)));
max_lon = std::max(max_lon, static_cast<double>(toFloating(coord.lon)));
sum_lon += static_cast<double>(toFloating(coord.lon));
sum_lon_lon +=
static_cast<double>(toFloating(coord.lon)) * static_cast<double>(toFloating(coord.lon));
sum_lat += static_cast<double>(toFloating(coord.lat));
sum_lon_lat +=
static_cast<double>(toFloating(coord.lon)) * static_cast<double>(toFloating(coord.lat));
}
return haversineDistance(coordinate,
projectPointOnSegment(segment_begin, segment_end, coordinate).second);
}
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<double>::epsilon())
return std::make_pair(coordinates.front(), coordinates.back());
// find the closes distance between two sets of coordinates
double findClosestDistance(const std::vector<Coordinate> &lhs, const std::vector<Coordinate> &rhs)
{
double current_min = std::numeric_limits<double>::max();
// 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<double>((longitude))};
const auto compute_minimum_distance_in_rhs = [&current_min, &rhs](const Coordinate coordinate) {
current_min =
std::min(current_min, findClosestDistance(coordinate, rhs.begin(), rhs.end()));
return false;
};
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})))};
std::find_if(std::begin(lhs), std::end(lhs), compute_minimum_distance_in_rhs);
return current_min;
}
return {regression_first, regression_end};
std::vector<double> getDeviations(const std::vector<Coordinate> &from,
const std::vector<Coordinate> &to)
{
auto find_deviation = [&to](const Coordinate coordinate) {
return findClosestDistance(coordinate, to.begin(), to.end());
};
std::vector<double> deviations_from;
deviations_from.reserve(from.size());
std::transform(
std::begin(from), std::end(from), std::back_inserter(deviations_from), find_deviation);
return deviations_from;
}
Coordinate rotateCCWAroundZero(Coordinate coordinate, double angle_in_radians)
{
/*
* a rotation around 0,0 in vector space is defined as
*
* | cos a -sin a | . | lon |
* | sin a cos a | | lat |
*
* resulting in cos a lon - sin a lon for the new longitude and sin a lon + cos a lat for the
* new latitude
*/
const auto cos_alpha = cos(angle_in_radians);
const auto sin_alpha = sin(angle_in_radians);
const auto lon = static_cast<double>(toFloating(coordinate.lon));
const auto lat = static_cast<double>(toFloating(coordinate.lat));
return {util::FloatLongitude{cos_alpha * lon - sin_alpha * lat},
util::FloatLatitude{sin_alpha * lon + cos_alpha * lat}};
}
Coordinate difference(const Coordinate lhs, const Coordinate rhs)
{
const auto lon_diff_int = static_cast<int>(lhs.lon) - static_cast<int>(rhs.lon);
const auto lat_diff_int = static_cast<int>(lhs.lat) - static_cast<int>(rhs.lat);
return {util::FixedLongitude{lon_diff_int}, util::FixedLatitude{lat_diff_int}};
}
} // ns coordinate_calculation

View File

@ -36,6 +36,7 @@ NodeIdVectorToMultiPoint::NodeIdVectorToMultiPoint(
: node_coordinates(node_coordinates)
{
}
util::json::Object NodeIdVectorToMultiPoint::
operator()(const std::vector<NodeID> &node_ids,
const boost::optional<json::Object> &properties) const

View File

@ -1,6 +1,7 @@
#include <boost/numeric/conversion/cast.hpp>
#include <boost/test/unit_test.hpp>
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include <osrm/coordinate.hpp>
@ -325,4 +326,56 @@ BOOST_AUTO_TEST_CASE(squaredEuclideanDistance)
BOOST_CHECK_EQUAL(result, 162000000000000000ull);
}
BOOST_AUTO_TEST_CASE(vertical_regression)
{
// check a vertical line for its bearing
std::vector<Coordinate> coordinates;
for (std::size_t i = 0; i < 100; ++i)
coordinates.push_back(Coordinate(FloatLongitude{0.0}, FloatLatitude{i / 100.0}));
const auto regression =
util::coordinate_calculation::leastSquareRegression(coordinates.begin(), coordinates.end());
const auto is_valid =
util::angularDeviation(
util::coordinate_calculation::bearing(regression.first, regression.second), 0) < 2;
BOOST_CHECK(is_valid);
}
BOOST_AUTO_TEST_CASE(sinus_curve)
{
// create a full sinus curve, sampled in 3.6 degree
std::vector<Coordinate> coordinates;
for (std::size_t i = 0; i < 360; ++i)
coordinates.push_back(Coordinate(
FloatLongitude{i / 360.0},
FloatLatitude{sin(util::coordinate_calculation::detail::degToRad(i / 360.0))}));
const auto regression =
util::coordinate_calculation::leastSquareRegression(coordinates.begin(), coordinates.end());
const auto is_valid =
util::angularDeviation(
util::coordinate_calculation::bearing(regression.first, regression.second), 90) < 2;
BOOST_CHECK(is_valid);
}
BOOST_AUTO_TEST_CASE(parallel_lines_slight_offset)
{
std::vector<Coordinate> coordinates_lhs;
for (std::size_t i = 0; i < 100; ++i)
coordinates_lhs.push_back(Coordinate(util::FloatLongitude{(50 - (rand() % 101)) / 100000.0},
util::FloatLatitude{i / 100000.0}));
std::vector<Coordinate> coordinates_rhs;
for (std::size_t i = 0; i < 100; ++i)
coordinates_rhs.push_back(
Coordinate(util::FloatLongitude{(150 - (rand() % 101)) / 100000.0},
util::FloatLatitude{i / 100000.0}));
const auto are_parallel = util::coordinate_calculation::areParallel(coordinates_lhs.begin(),
coordinates_lhs.end(),
coordinates_rhs.begin(),
coordinates_rhs.end());
BOOST_CHECK(are_parallel);
}
BOOST_AUTO_TEST_SUITE_END()