From e6ff17ab2ac37518b9da108191d3aad2dce5d1b5 Mon Sep 17 00:00:00 2001 From: Moritz Kobitzsch Date: Tue, 6 Dec 2016 13:22:51 +0100 Subject: [PATCH] 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 --- docs/developing.md | 1 + features/guidance/collapse.feature | 148 ++--- .../guidance/merge-segregated-roads.feature | 525 ++++++++++++++++++ features/guidance/perception.feature | 141 +---- features/guidance/turn-angles.feature | 74 ++- features/guidance/turn-lanes.feature | 25 +- features/guidance/turn.feature | 140 ----- include/engine/guidance/assemble_steps.hpp | 17 +- include/extractor/geojson_debug_policies.hpp | 68 ++- include/extractor/guidance/constants.hpp | 7 + include/extractor/guidance/intersection.hpp | 124 +++-- .../guidance/intersection_generator.hpp | 16 +- .../guidance/intersection_handler.hpp | 12 +- .../intersection_normalization_operation.hpp | 25 + .../guidance/intersection_normalizer.hpp | 41 +- .../guidance/mergable_road_detector.hpp | 155 ++++++ .../guidance/node_based_graph_walker.hpp | 46 +- include/extractor/guidance/turn_analysis.hpp | 9 +- include/util/bearing.hpp | 38 +- include/util/coordinate_calculation.hpp | 339 +++++++++-- include/util/guidance/name_announcements.hpp | 33 +- src/engine/guidance/post_processing.cpp | 44 +- src/extractor/edge_based_graph_factory.cpp | 4 +- src/extractor/extractor.cpp | 1 + src/extractor/extractor_callbacks.cpp | 4 +- src/extractor/geojson_debug_policies.cpp | 69 --- .../guidance/coordinate_extractor.cpp | 97 ++-- src/extractor/guidance/intersection.cpp | 22 +- .../guidance/intersection_generator.cpp | 60 +- .../guidance/intersection_handler.cpp | 18 +- .../guidance/intersection_normalizer.cpp | 278 +++------- .../guidance/mergable_road_detector.cpp | 469 ++++++++++++++++ .../guidance/node_based_graph_walker.cpp | 100 +++- src/extractor/guidance/sliproad_handler.cpp | 1 - src/extractor/guidance/turn_analysis.cpp | 11 +- src/extractor/guidance/turn_discovery.cpp | 10 +- src/extractor/guidance/turn_handler.cpp | 1 - src/util/coordinate_calculation.cpp | 119 ++-- src/util/geojson_debug_policies.cpp | 1 + unit_tests/util/coordinate_calculation.cpp | 53 ++ 40 files changed, 2397 insertions(+), 949 deletions(-) create mode 100644 features/guidance/merge-segregated-roads.feature create mode 100644 include/extractor/guidance/intersection_normalization_operation.hpp create mode 100644 include/extractor/guidance/mergable_road_detector.hpp delete mode 100644 src/extractor/geojson_debug_policies.cpp create mode 100644 src/extractor/guidance/mergable_road_detector.cpp diff --git a/docs/developing.md b/docs/developing.md index f2ac4c656..0ab3d846f 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -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 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`). diff --git a/features/guidance/collapse.feature b/features/guidance/collapse.feature index 4cf4c38da..0a0f12f41 100644 --- a/features/guidance/collapse.feature +++ b/features/guidance/collapse.feature @@ -143,37 +143,33 @@ Feature: Collapse Scenario: Partly Segregated Intersection, Two Segregated Roads Given the node map """ - n m - - - - - - g h - - - c b a - d e f - - - j i - - - - - - k l + n m + | | + | | + | | + | | + | | + g h + 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 | @@ -197,33 +193,37 @@ Feature: Collapse Scenario: Partly Segregated Intersection, Two Segregated Roads, Intersection belongs to Second Given the node map """ - n m - - - - g h - - - c b a - d e f - - - j i - - - - k l + n m + | | + | | + | | + | | + | | + | | + g h + \ / + 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 | @@ -353,13 +353,13 @@ Feature: Collapse | ge | primary | second | no | When I route I should get - | waypoints | route | turns | - | d,c | first,first,first | depart,continue uturn,arrive | - | a,f | first,first,first | depart,continue uturn,arrive | - | 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 | + | waypoints | route | turns | + | d,c | first,first,first | depart,continue uturn,arrive | + | a,f | first,first,first | depart,continue uturn,arrive | + | 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,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 | diff --git a/features/guidance/merge-segregated-roads.feature b/features/guidance/merge-segregated-roads.feature new file mode 100644 index 000000000..644c669ed --- /dev/null +++ b/features/guidance/merge-segregated-roads.feature @@ -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 | diff --git a/features/guidance/perception.feature b/features/guidance/perception.feature index 3d92e2b7b..ad027eefa 100644 --- a/features/guidance/perception.feature +++ b/features/guidance/perception.feature @@ -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 diff --git a/features/guidance/turn-angles.feature b/features/guidance/turn-angles.feature index 2584971cf..1d2561a51 100644 --- a/features/guidance/turn-angles.feature +++ b/features/guidance/turn-angles.feature @@ -768,7 +768,31 @@ Feature: Simple Turns . . - g h + . . + + . . + + . . + + . . + + . . + + . . + + . . + + . . + + . . + + . . + + . . + + . . + + g h """ And the nodes @@ -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 """ diff --git a/features/guidance/turn-lanes.feature b/features/guidance/turn-lanes.feature index 391990833..e52940804 100644 --- a/features/guidance/turn-lanes.feature +++ b/features/guidance/turn-lanes.feature @@ -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 | diff --git a/features/guidance/turn.feature b/features/guidance/turn.feature index ce23c3eb4..12126d8c9 100644 --- a/features/guidance/turn.feature +++ b/features/guidance/turn.feature @@ -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 diff --git a/include/engine/guidance/assemble_steps.hpp b/include/engine/guidance/assemble_steps.hpp index a1c284f84..c555fac2f 100644 --- a/include/engine/guidance/assemble_steps.hpp +++ b/include/engine/guidance/assemble_steps.hpp @@ -154,7 +154,7 @@ inline std::vector 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,13 +214,14 @@ inline std::vector assembleSteps(const datafacade::BaseDataFacade &fa BOOST_ASSERT(segment_index == number_of_segments - 1); bearings = detail::getArriveBearings(leg_geometry); - intersection = {target_node.location, - std::vector({static_cast(util::reverseBearing(bearings.first))}), - std::vector({true}), - 0, - IntermediateIntersection::NO_INDEX, - util::guidance::LaneTuple(), - {}}; + intersection = { + target_node.location, + std::vector({static_cast(util::bearing::reverse(bearings.first))}), + std::vector({true}), + 0, + IntermediateIntersection::NO_INDEX, + util::guidance::LaneTuple(), + {}}; // This step has length zero, the only reason we need it is the target location maneuver = {intersection.location, diff --git a/include/extractor/geojson_debug_policies.hpp b/include/extractor/geojson_debug_policies.hpp index d98fd9800..4ba1f4474 100644 --- a/include/extractor/geojson_debug_policies.hpp +++ b/include/extractor/geojson_debug_policies.hpp @@ -1,6 +1,7 @@ #ifndef OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES #define OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES +#include #include #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 @@ -18,8 +21,9 @@ namespace osrm { namespace extractor { + // generate a visualisation of an intersection, printing the coordinates used for angle calculation -struct IntersectionPrinter +template struct IntersectionPrinter { IntersectionPrinter(const util::NodeBasedDynamicGraph &node_based_graph, const std::vector &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 &node_style = {}, const boost::optional &way_style = {}) const; @@ -37,6 +41,66 @@ struct IntersectionPrinter const extractor::guidance::CoordinateExtractor &coordinate_extractor; }; +// IMPLEMENTATION +template +IntersectionPrinter::IntersectionPrinter( + const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_coordinates, + const extractor::guidance::CoordinateExtractor &coordinate_extractor) + : node_based_graph(node_based_graph), node_coordinates(node_coordinates), + coordinate_extractor(coordinate_extractor) +{ +} + +template +util::json::Array IntersectionPrinter:: +operator()(const NodeID intersection_node, + const IntersectionType &intersection, + const boost::optional &node_style, + const boost::optional &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 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 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 */ diff --git a/include/extractor/guidance/constants.hpp b/include/extractor/guidance/constants.hpp index 890b8dfad..0dc1307a0 100644 --- a/include/extractor/guidance/constants.hpp +++ b/include/extractor/guidance/constants.hpp @@ -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 diff --git a/include/extractor/guidance/intersection.hpp b/include/extractor/guidance/intersection.hpp index a58ef16fa..82f27159a 100644 --- a/include/extractor/guidance/intersection.hpp +++ b/include/extractor/guidance/intersection.hpp @@ -8,15 +8,15 @@ #include #include -#include "extractor/guidance/turn_instruction.hpp" #include "util/bearing.hpp" #include "util/node_based_graph.hpp" #include "util/typedefs.hpp" // EdgeID -#include -#include +#include "extractor/guidance/turn_instruction.hpp" -#include +#include +#include +#include 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 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; + // 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 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 auto Count(UnaryPredicate detector) const + { + BOOST_ASSERT(!self()->empty()); + return boost::range::count_if(*self(), detector); + } + + private: + auto self() { return static_cast(this); } + auto self() const { return static_cast(this); } +}; + +struct IntersectionShape final : std::vector, // + EnableShapeOps // +{ + using Base = std::vector; +}; // Common operations shared among IntersectionView and Intersections. // Inherit to enable those operations on your compatible type. CRTP pattern. @@ -123,12 +179,13 @@ template 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 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 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 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 + 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(this); } auto self() const { return static_cast(this); } }; struct IntersectionView final : std::vector, // + EnableShapeOps, // EnableIntersectionOps // { using Base = std::vector; }; struct Intersection final : std::vector, // + EnableShapeOps, // EnableIntersectionOps // { using Base = std::vector; diff --git a/include/extractor/guidance/intersection_generator.hpp b/include/extractor/guidance/intersection_generator.hpp index bf7388243..4d23eff11 100644 --- a/include/extractor/guidance/intersection_generator.hpp +++ b/include/extractor/guidance/intersection_generator.hpp @@ -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,8 +71,8 @@ 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 SkipDegreeTwoNodes(const NodeID starting_node, - const EdgeID via_edge) const; + IntersectionGenerationParameters SkipDegreeTwoNodes(const NodeID starting_node, + const EdgeID via_edge) const; // Allow access to the coordinate extractor for all owners const CoordinateExtractor &GetCoordinateExtractor() const; @@ -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> &merging_map) const; + const std::vector &merging_map) const; private: const util::NodeBasedDynamicGraph &node_based_graph; diff --git a/include/extractor/guidance/intersection_handler.hpp b/include/extractor/guidance/intersection_handler.hpp index 7bdcff3a5..d436e80e7 100644 --- a/include/extractor/guidance/intersection_handler.hpp +++ b/include/extractor/guidance/intersection_handler.hpp @@ -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) diff --git a/include/extractor/guidance/intersection_normalization_operation.hpp b/include/extractor/guidance/intersection_normalization_operation.hpp new file mode 100644 index 000000000..57750d262 --- /dev/null +++ b/include/extractor/guidance/intersection_normalization_operation.hpp @@ -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_*/ diff --git a/include/extractor/guidance/intersection_normalizer.hpp b/include/extractor/guidance/intersection_normalizer.hpp index 42c2bf81a..80d705ebd 100644 --- a/include/extractor/guidance/intersection_normalizer.hpp +++ b/include/extractor/guidance/intersection_normalizer.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 #include @@ -37,6 +37,11 @@ namespace guidance class IntersectionNormalizer { public: + struct NormalizationResult + { + IntersectionShape normalized_shape; + std::vector performed_merges; + }; IntersectionNormalizer(const util::NodeBasedDynamicGraph &node_based_graph, const std::vector &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>> - 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 &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>> - 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. diff --git a/include/extractor/guidance/mergable_road_detector.hpp b/include/extractor/guidance/mergable_road_detector.hpp new file mode 100644 index 000000000..f0c1552a0 --- /dev/null +++ b/include/extractor/guidance/mergable_road_detector.hpp @@ -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 +#include +#include +#include + +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 &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 &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 diff --git a/include/extractor/guidance/node_based_graph_walker.hpp b/include/extractor/guidance/node_based_graph_walker.hpp index e477cbf33..d157a83de 100644 --- a/include/extractor/guidance/node_based_graph_walker.hpp +++ b/include/extractor/guidance/node_based_graph_walker.hpp @@ -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 coordinates; + + private: + const extractor::guidance::CoordinateExtractor &coordinate_extractor; + const double max_length; }; /* @@ -105,13 +105,40 @@ struct SelectRoadByNameOnlyChoiceAndStraightness */ boost::optional 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 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; diff --git a/include/extractor/guidance/turn_analysis.hpp b/include/extractor/guidance/turn_analysis.hpp index 332373bd2..4a92f4807 100644 --- a/include/extractor/guidance/turn_analysis.hpp +++ b/include/extractor/guidance/turn_analysis.hpp @@ -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> 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; diff --git a/include/util/bearing.hpp b/include/util/bearing.hpp index 6ab59ccd2..b9f38554e 100644 --- a/include/util/bearing.hpp +++ b/include/util/bearing.hpp @@ -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 diff --git a/include/util/coordinate_calculation.hpp b/include/util/coordinate_calculation.hpp index 2db09d37c..a824ba8df 100644 --- a/include/util/coordinate_calculation.hpp +++ b/include/util/coordinate_calculation.hpp @@ -3,9 +3,12 @@ #include "util/coordinate.hpp" +#include #include #include +#include +#include #include #include @@ -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() / 180.0); +} + +inline double radToDeg(const double radian) +{ + using namespace boost::math::constants; + return radian * (180.0 * (1. / pi())); +} } //! 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 -double getLength(const std::vector &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 +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 &coordinates, BinaryOperation op) // returns x as well as the distance between source and x as ratio ([0,1]) inline std::pair projectPointOnSegment(const FloatCoordinate &source, const FloatCoordinate &target, - const FloatCoordinate &coordinate) -{ - 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(slope_vector.lon * rel_coordinate.lon) + - static_cast(slope_vector.lat * rel_coordinate.lat); - // squared length of the slope vector - const auto squared_length = static_cast(slope_vector.lon * slope_vector.lon) + - static_cast(slope_vector.lat * slope_vector.lat); + const FloatCoordinate &coordinate); - if (squared_length < std::numeric_limits::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 +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 +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 +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 -leastSquareRegression(const std::vector &coordinates); +template +std::pair 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 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(slope_vector.lon * rel_coordinate.lon) + + static_cast(slope_vector.lat * rel_coordinate.lat); + // squared length of the slope vector + const auto squared_length = static_cast(slope_vector.lon * slope_vector.lon) + + static_cast(slope_vector.lat * slope_vector.lat); + + if (squared_length < std::numeric_limits::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 +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 +double +findClosestDistance(const Coordinate coordinate, const iterator_type begin, const iterator_type end) +{ + double current_min = std::numeric_limits::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 +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::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 +std::pair 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(toFloating(coordinate.lon)); + }; + + const auto extract_lat = [](const Coordinate coordinate) { + return static_cast(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 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::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::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((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 +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(from.lat) - static_cast(to.lat); + const auto diff_lon = static_cast(from.lon) - static_cast(to.lon); + if (diff_lon == 0) + return std::numeric_limits::max(); + return static_cast(diff_lat) / static_cast(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 diff --git a/include/util/guidance/name_announcements.hpp b/include/util/guidance/name_announcements.hpp index 5afc78709..ea0ad049a 100644 --- a/include/util/guidance/name_announcements.hpp +++ b/include/util/guidance/name_announcements.hpp @@ -22,7 +22,6 @@ namespace util { namespace guidance { - // Name Change Logic // Used both during Extraction as well as during Post-Processing @@ -157,25 +156,31 @@ inline bool requiresNameAnnounced(const NameID from_name_id, const util::NameTable &name_table, const extractor::SuffixTable &suffix_table) { - return requiresNameAnnounced(name_table.GetNameForID(from_name_id), - name_table.GetRefForID(from_name_id), - name_table.GetPronunciationForID(from_name_id), - name_table.GetNameForID(to_name_id), - name_table.GetRefForID(to_name_id), - name_table.GetPronunciationForID(to_name_id), - 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), + name_table.GetNameForID(to_name_id), + name_table.GetRefForID(to_name_id), + name_table.GetPronunciationForID(to_name_id), + suffix_table); } inline bool requiresNameAnnounced(const NameID from_name_id, const NameID to_name_id, const util::NameTable &name_table) { - return requiresNameAnnounced(name_table.GetNameForID(from_name_id), - name_table.GetRefForID(from_name_id), - name_table.GetPronunciationForID(from_name_id), - name_table.GetNameForID(to_name_id), - name_table.GetRefForID(to_name_id), - name_table.GetPronunciationForID(to_name_id)); + 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), + name_table.GetNameForID(to_name_id), + name_table.GetRefForID(to_name_id), + name_table.GetPronunciationForID(to_name_id)); } } // namespace guidance diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index d343c551f..2057d5b1d 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -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 &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 &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), - 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 first_angle = util::bearing::angleBetween(in_bearing(one_back_step), + out_bearing(one_back_step)); + const auto second_angle = util::bearing::angleBetween(in_bearing(current_step), + out_bearing(current_step)); + const auto bearing_turn_angle = util::bearing::angleBetween( in_bearing(one_back_step), out_bearing(current_step)); // When looking at an intersection, some angles, even though present, feel more like @@ -676,7 +676,7 @@ void collapseTurnAt(std::vector &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 &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 postProcess(std::vector 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 &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 &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()); diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index c8afbe360..e08cf9ec4 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -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); diff --git a/src/extractor/extractor.cpp b/src/extractor/extractor.cpp index 282854217..c7f8dc8ed 100644 --- a/src/extractor/extractor.cpp +++ b/src/extractor/extractor.cpp @@ -251,6 +251,7 @@ int Extractor::run(ScriptingEnvironment &scripting_environment) std::vector node_is_startpoint; std::vector edge_based_node_weights; std::vector internal_to_external_node_map; + auto graph_size = BuildEdgeExpandedGraph(scripting_environment, internal_to_external_node_map, edge_based_node_list, diff --git a/src/extractor/extractor_callbacks.cpp b/src/extractor/extractor_callbacks.cpp index 16a65eb9c..fcf04db47 100644 --- a/src/extractor/extractor_callbacks.cpp +++ b/src/extractor/extractor_callbacks.cpp @@ -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( diff --git a/src/extractor/geojson_debug_policies.cpp b/src/extractor/geojson_debug_policies.cpp deleted file mode 100644 index 783c1b86d..000000000 --- a/src/extractor/geojson_debug_policies.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "extractor/geojson_debug_policies.hpp" -#include "util/coordinate.hpp" -#include "util/geojson_debug_policy_toolkit.hpp" - -#include - -namespace osrm -{ -namespace extractor -{ - -IntersectionPrinter::IntersectionPrinter( - const util::NodeBasedDynamicGraph &node_based_graph, - const std::vector &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 &node_style, - const boost::optional &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 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 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 */ diff --git a/src/extractor/guidance/coordinate_extractor.cpp b/src/extractor/guidance/coordinate_extractor.cpp index d09abf817..5697a9abb 100644 --- a/src/extractor/guidance/coordinate_extractor.cpp +++ b/src/extractor/guidance/coordinate_extractor.cpp @@ -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 &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,59 +753,61 @@ bool CoordinateExtractor::IsCurve(const std::vector &coordinat return turn_angles; }(); - const bool curve_is_valid = - [&turn_angles, &segment_distances, &segment_length, &considered_lane_width]() { - // internal state for our lamdae - bool last_was_straight = false; - // a turn angle represents two segments between three coordinates. We initialize the - // distance with the very first segment length (in-segment) of the first turn-angle - double straight_distance = std::max(0., segment_distances[1] - considered_lane_width); - auto distance_itr = segment_distances.begin() + 1; + const bool curve_is_valid = [&turn_angles, + &segment_distances, + &segment_length, + &considered_lane_width]() { + // internal state for our lamdae + bool last_was_straight = false; + // a turn angle represents two segments between three coordinates. We initialize the + // distance with the very first segment length (in-segment) of the first turn-angle + double straight_distance = std::max(0., segment_distances[1] - considered_lane_width); + auto distance_itr = segment_distances.begin() + 1; - // every call to the lamda requires a call to the distances. They need to be aligned - BOOST_ASSERT(segment_distances.size() == turn_angles.size() + 2); + // every call to the lamda requires a call to the distances. They need to be aligned + BOOST_ASSERT(segment_distances.size() == turn_angles.size() + 2); - const auto detect_invalid_curve = [&](const double previous_angle, - const double current_angle) { - const auto both_actually_turn = - (angularDeviation(previous_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) && - (angularDeviation(current_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE); - // they cannot be straight, since they differ at least by FUZZY_ANGLE_DIFFERENCE - const auto turn_direction_switches = - (previous_angle > STRAIGHT_ANGLE) == (current_angle < STRAIGHT_ANGLE); + const auto detect_invalid_curve = [&](const double previous_angle, + const double current_angle) { + const auto both_actually_turn = + (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); - // a turn that switches direction mid-curve is not a valid curve - if (both_actually_turn && turn_direction_switches) - return true; + // a turn that switches direction mid-curve is not a valid curve + if (both_actually_turn && turn_direction_switches) + return true; - const bool is_straight = angularDeviation(current_angle, STRAIGHT_ANGLE) < 5; - ++distance_itr; - if (is_straight) + const bool is_straight = util::angularDeviation(current_angle, STRAIGHT_ANGLE) < 5; + ++distance_itr; + if (is_straight) + { + // since the angle is straight, we augment it by the second part of the segment + straight_distance += *distance_itr; + if (last_was_straight && straight_distance > 0.3 * segment_length) { - // since the angle is straight, we augment it by the second part of the segment - straight_distance += *distance_itr; - if (last_was_straight && straight_distance > 0.3 * segment_length) - { - return true; - } - } // if a segment on its own is long enough, thats fair game as well - else if (straight_distance > 0.3 * segment_length) return true; - else - { - // we reset the last distance, starting with the next in-segment again - straight_distance = *distance_itr; } - last_was_straight = is_straight; - return false; - }; + } // if a segment on its own is long enough, thats fair game as well + else if (straight_distance > 0.3 * segment_length) + return true; + else + { + // we reset the last distance, starting with the next in-segment again + straight_distance = *distance_itr; + } + last_was_straight = is_straight; + return false; + }; - const auto end_of_straight_segment = - std::adjacent_find(turn_angles.begin(), turn_angles.end(), detect_invalid_curve); + const auto end_of_straight_segment = + std::adjacent_find(turn_angles.begin(), turn_angles.end(), detect_invalid_curve); - // No curve should have a very long straight segment - return end_of_straight_segment == turn_angles.end(); - }(); + // No curve should have a very long straight segment + return end_of_straight_segment == turn_angles.end(); + }(); return (segment_length > 2 * considered_lane_width && curve_is_valid); } @@ -1147,8 +1148,8 @@ CoordinateExtractor::RegressionLine(const std::vector &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()) diff --git a/src/extractor/guidance/intersection.cpp b/src/extractor/guidance/intersection.cpp index 18bafa9bb..37d207aff 100644 --- a/src/extractor/guidance/intersection.cpp +++ b/src/extractor/guidance/intersection.cpp @@ -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::epsilon()) + if (util::angularDeviation(angle, 0) > std::numeric_limits::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] "; diff --git a/src/extractor/guidance/intersection_generator.cpp b/src/extractor/guidance/intersection_generator.cpp index 5465dcbb8..98cb57318 100644 --- a/src/extractor/guidance/intersection_generator.cpp +++ b/src/extractor/guidance/intersection_generator.cpp @@ -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 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 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> &performed_merges) const + const std::vector &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 = diff --git a/src/extractor/guidance/intersection_handler.cpp b/src/extractor/guidance/intersection_handler.cpp index 15d6832b5..5168a20f8 100644 --- a/src/extractor/guidance/intersection_handler.cpp +++ b/src/extractor/guidance/intersection_handler.cpp @@ -5,10 +5,14 @@ #include "util/guidance/name_announcements.hpp" #include "util/log.hpp" +#include "util/bearing.hpp" +#include "util/coordinate_calculation.hpp" + #include #include 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()) { diff --git a/src/extractor/guidance/intersection_normalizer.cpp b/src/extractor/guidance/intersection_normalizer.cpp index 0aab78fe9..52f58efd9 100644 --- a/src/extractor/guidance/intersection_normalizer.cpp +++ b/src/extractor/guidance/intersection_normalizer.cpp @@ -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 #include @@ -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>> 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 target_id = GetActualTarget(index); - const auto other_target_id = GetActualTarget(other_index); - if (target_id == node_at_intersection || other_target_id == node_at_intersection) - return false; - - const auto coordinate_at_target = node_coordinates[target_id]; - const auto coordinate_at_other_target = node_coordinates[other_target_id]; - - const auto turn_bearing = - util::coordinate_calculation::bearing(coordinate_at_intersection, coordinate_at_target); - const auto other_turn_bearing = util::coordinate_calculation::bearing( - coordinate_at_intersection, coordinate_at_other_target); - - // fuzzy becomes narrower due to minor differences in angle computations, yay floating point - const bool becomes_narrower = - angularDeviation(turn_bearing, other_turn_bearing) < NARROW_TURN_ANGLE && - angularDeviation(turn_bearing, other_turn_bearing) <= - angularDeviation(intersection[index].bearing, intersection[other_index].bearing) + - MAXIMAL_ALLOWED_NO_TURN_DEVIATION; - - return becomes_narrower; + const 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 bool is_y_arm_first = isValidYArm(first_index, second_index); - const bool is_y_arm_second = isValidYArm(second_index, first_index); + // use lazy evaluation to check only if mergable + return can_merge && is_distinct(); +} - // Only merge valid y-arms - if (!is_y_arm_first || !is_y_arm_second) - return false; +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}; +} - if (angle_between < 60) - return true; +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; + }; - // 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>> +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> merging_map; + std::vector merging_map; + const auto merge = [this, &merging_map](const IntersectionShapeData &first, + const IntersectionShapeData &second) { - const auto merge = [this, combineAngles, &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; diff --git a/src/extractor/guidance/mergable_road_detector.cpp b/src/extractor/guidance/mergable_road_detector.cpp new file mode 100644 index 000000000..98332f003 --- /dev/null +++ b/src/extractor/guidance/mergable_road_detector.cpp @@ -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 &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( + 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 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( + 1, node_based_graph.GetEdgeData(lhs.eid).road_classification.GetNumberOfLanes()); + const auto lane_count_rhs = std::max( + 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 diff --git a/src/extractor/guidance/node_based_graph_walker.cpp b/src/extractor/guidance/node_based_graph_walker.cpp index 8f5a235fd..8b3df93ae 100644 --- a/src/extractor/guidance/node_based_graph_walker.cpp +++ b/src/extractor/guidance/node_based_graph_walker.cpp @@ -1,4 +1,7 @@ #include "extractor/guidance/node_based_graph_walker.hpp" +#include "util/bearing.hpp" +#include "util/coordinate_calculation.hpp" + #include 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 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 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 diff --git a/src/extractor/guidance/sliproad_handler.cpp b/src/extractor/guidance/sliproad_handler.cpp index 5d1e111a5..2163a4b2c 100644 --- a/src/extractor/guidance/sliproad_handler.cpp +++ b/src/extractor/guidance/sliproad_handler.cpp @@ -5,7 +5,6 @@ #include "util/guidance/name_announcements.hpp" #include - #include #include #include diff --git a/src/extractor/guidance/turn_analysis.cpp b/src/extractor/guidance/turn_analysis.cpp index 740371aa0..6a9d12650 100644 --- a/src/extractor/guidance/turn_analysis.cpp +++ b/src/extractor/guidance/turn_analysis.cpp @@ -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; } diff --git a/src/extractor/guidance/turn_discovery.cpp b/src/extractor/guidance/turn_discovery.cpp index 170716a65..fc9d31337 100644 --- a/src/extractor/guidance/turn_discovery.cpp +++ b/src/extractor/guidance/turn_discovery.cpp @@ -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,9 +44,12 @@ 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), - &util::coordinate_calculation::haversineDistance); + 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 // at via_edge and the intersection at NODE_W diff --git a/src/extractor/guidance/turn_handler.cpp b/src/extractor/guidance/turn_handler.cpp index e493fcc05..8e48b8169 100644 --- a/src/extractor/guidance/turn_handler.cpp +++ b/src/extractor/guidance/turn_handler.cpp @@ -10,7 +10,6 @@ #include -using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData; using osrm::extractor::guidance::getTurnDirection; using osrm::util::angularDeviation; diff --git a/src/util/coordinate_calculation.cpp b/src/util/coordinate_calculation.cpp index cfc2846ce..540c6d593 100644 --- a/src/util/coordinate_calculation.cpp +++ b/src/util/coordinate_calculation.cpp @@ -5,8 +5,8 @@ #include -#include - +#include +#include #include #include @@ -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() / 180.0); -} - -double radToDeg(const double radian) -{ - using namespace boost::math::constants; - return radian * (180.0 * (1. / pi())); -} - double bearing(const Coordinate first_coordinate, const Coordinate second_coordinate) { const double lon_diff = static_cast(toFloating(second_coordinate.lon - first_coordinate.lon)); - const double lon_delta = degToRad(lon_diff); - const double lat1 = degToRad(static_cast(toFloating(first_coordinate.lat))); - const double lat2 = degToRad(static_cast(toFloating(second_coordinate.lat))); + const double lon_delta = detail::degToRad(lon_diff); + const double lat1 = detail::degToRad(static_cast(toFloating(first_coordinate.lat))); + const double lat2 = detail::degToRad(static_cast(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 -leastSquareRegression(const std::vector &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(toFloating(coordinates.front().lon)); - double max_lon = static_cast(toFloating(coordinates.front().lon)); - for (const auto coord : coordinates) - { - min_lon = std::min(min_lon, static_cast(toFloating(coord.lon))); - max_lon = std::max(max_lon, static_cast(toFloating(coord.lon))); - sum_lon += static_cast(toFloating(coord.lon)); - sum_lon_lon += - static_cast(toFloating(coord.lon)) * static_cast(toFloating(coord.lon)); - sum_lat += static_cast(toFloating(coord.lat)); - sum_lon_lat += - static_cast(toFloating(coord.lon)) * static_cast(toFloating(coord.lat)); - } + 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::epsilon()) - return std::make_pair(coordinates.front(), coordinates.back()); +// find the closes distance between two sets of coordinates +double findClosestDistance(const std::vector &lhs, const std::vector &rhs) +{ + double current_min = std::numeric_limits::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((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 getDeviations(const std::vector &from, + const std::vector &to) +{ + auto find_deviation = [&to](const Coordinate coordinate) { + return findClosestDistance(coordinate, to.begin(), to.end()); + }; + + std::vector 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(toFloating(coordinate.lon)); + const auto lat = static_cast(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(lhs.lon) - static_cast(rhs.lon); + const auto lat_diff_int = static_cast(lhs.lat) - static_cast(rhs.lat); + return {util::FixedLongitude{lon_diff_int}, util::FixedLatitude{lat_diff_int}}; } } // ns coordinate_calculation diff --git a/src/util/geojson_debug_policies.cpp b/src/util/geojson_debug_policies.cpp index ae173dff8..52f063dac 100644 --- a/src/util/geojson_debug_policies.cpp +++ b/src/util/geojson_debug_policies.cpp @@ -36,6 +36,7 @@ NodeIdVectorToMultiPoint::NodeIdVectorToMultiPoint( : node_coordinates(node_coordinates) { } + util::json::Object NodeIdVectorToMultiPoint:: operator()(const std::vector &node_ids, const boost::optional &properties) const diff --git a/unit_tests/util/coordinate_calculation.cpp b/unit_tests/util/coordinate_calculation.cpp index 83fbb9e2c..21f41e763 100644 --- a/unit_tests/util/coordinate_calculation.cpp +++ b/unit_tests/util/coordinate_calculation.cpp @@ -1,6 +1,7 @@ #include #include +#include "util/bearing.hpp" #include "util/coordinate_calculation.hpp" #include @@ -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 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 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 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 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()