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:
parent
f7ad2e1e26
commit
e6ff17ab2a
@ -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`).
|
||||
|
||||
|
@ -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 |
|
||||
|
525
features/guidance/merge-segregated-roads.feature
Normal file
525
features/guidance/merge-segregated-roads.feature
Normal 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 |
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
||||
|
@ -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 |
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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_*/
|
@ -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.
|
||||
|
155
include/extractor/guidance/mergable_road_detector.hpp
Normal file
155
include/extractor/guidance/mergable_road_detector.hpp
Normal 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
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 = [¤t_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 = [¤t_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
|
||||
|
@ -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),
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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 */
|
@ -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())
|
||||
|
@ -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] ";
|
||||
|
@ -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 =
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
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 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;
|
||||
};
|
||||
|
||||
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;
|
||||
// use lazy evaluation to check only if mergable
|
||||
return can_merge && is_distinct();
|
||||
}
|
||||
|
||||
const auto coordinate_at_target = node_coordinates[target_id];
|
||||
const auto coordinate_at_other_target = node_coordinates[other_target_id];
|
||||
IntersectionNormalizationOperation
|
||||
IntersectionNormalizer::DetermineMergeDirection(const IntersectionShapeData &lhs,
|
||||
const IntersectionShapeData &rhs) const
|
||||
{
|
||||
if (node_based_graph.GetEdgeData(lhs.eid).reversed)
|
||||
return {lhs.eid, rhs.eid};
|
||||
else
|
||||
return {rhs.eid, lhs.eid};
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
// 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
|
||||
return second_index == 1 ? 0 : 1;
|
||||
}();
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// needs to be same road coming in
|
||||
const auto &third_data = node_based_graph.GetEdgeData(intersection[third_index].eid);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
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;
|
||||
|
469
src/extractor/guidance/mergable_road_detector.cpp
Normal file
469
src/extractor/guidance/mergable_road_detector.cpp
Normal 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
|
@ -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
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "util/guidance/name_announcements.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
|
||||
using osrm::extractor::guidance::getTurnDirection;
|
||||
using osrm::util::angularDeviation;
|
||||
|
||||
|
@ -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 = [¤t_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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user