diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d6e8e9cf..754a51020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Guidance: - improved handling of sliproads (emit turns instead of 'take the ramp') + - improved collapsing of instructions. Some 'new name' instructions will be suppressed if they are without alternative and the segment is short - BREAKING: modifies the file format with new internal identifiers - API: diff --git a/features/bicycle/oneway.feature b/features/bicycle/oneway.feature index 3794244d7..1d363ebc5 100644 --- a/features/bicycle/oneway.feature +++ b/features/bicycle/oneway.feature @@ -115,7 +115,7 @@ Feature: Bike - Oneway streets Scenario: Bike - Two consecutive oneways Given the node map - | a | b | c | + | a | b | | c | And the ways | nodes | oneway | diff --git a/features/car/oneway.feature b/features/car/oneway.feature index b5bbe7cbe..d048afb2a 100644 --- a/features/car/oneway.feature +++ b/features/car/oneway.feature @@ -68,7 +68,7 @@ Feature: Car - Oneway streets Scenario: Car - Two consecutive oneways Given the node map - | a | b | c | + | a | b | | c | And the ways | nodes | oneway | diff --git a/features/car/restrictions.feature b/features/car/restrictions.feature index d311fb0a9..719ab5fa1 100644 --- a/features/car/restrictions.feature +++ b/features/car/restrictions.feature @@ -5,6 +5,7 @@ Feature: Car - Turn restrictions Background: Use car routing Given the profile "car" + Given a grid size of 200 meters @no_turning Scenario: Car - No left turn diff --git a/features/car/traffic_turn_penalties.feature b/features/car/traffic_turn_penalties.feature index e0c372786..658f780e2 100644 --- a/features/car/traffic_turn_penalties.feature +++ b/features/car/traffic_turn_penalties.feature @@ -12,19 +12,17 @@ Feature: Traffic - turn penalties | nodes | highway | | ad | primary | | cd | primary | - | de | primary | + | def | primary | | dhk | primary | | bf | primary | - | ef | primary | | fg | primary | | fim | primary | | jk | primary | - | kl | primary | + | klm | primary | | ko | primary | - | lm | primary | | mn | primary | | mp | primary | And the profile "car" @@ -32,22 +30,22 @@ Feature: Traffic - turn penalties Scenario: Weighting not based on turn penalty file When I route I should get - | from | to | route | speed | time | - | a | h | ad,dhk,dhk | 63 km/h | 11.5s +-1 | + | from | to | route | speed | time | + | a | h | ad,dhk,dhk | 63 km/h | 11.5s +-1 | # straight - | i | g | fim,fg,fg | 59 km/h | 12s +-1 | + | i | g | fim,fg,fg | 59 km/h | 12s +-1 | # right - | a | e | ad,de,de | 57 km/h | 12.5s +-1 | + | a | e | ad,def,def | 57 km/h | 12.5s +-1 | # left - | c | g | cd,de,ef,fg,fg | 63 km/h | 23s +-1 | + | c | g | cd,def,fg,fg | 63 km/h | 23s +-1 | # double straight - | p | g | mp,fim,fg,fg | 61 km/h | 23.5s +-1 | + | p | g | mp,fim,fg,fg | 61 km/h | 23.5s +-1 | # straight-right - | a | l | ad,dhk,kl,kl | 60 km/h | 24s +-1 | + | a | l | ad,dhk,klm,klm | 60 km/h | 24s +-1 | # straight-left - | l | e | kl,dhk,de,de | 59 km/h | 24.5s +-1 | + | l | e | klm,dhk,def,def | 59 km/h | 24.5s +-1 | # double right - | g | n | fg,fim,mn,mn | 57 km/h | 25s +-1 | + | g | n | fg,fim,mn,mn | 57 km/h | 25s +-1 | # double left Scenario: Weighting based on turn penalty file @@ -62,24 +60,24 @@ Feature: Traffic - turn penalties """ And the contract extra arguments "--turn-penalty-file penalties.csv" When I route I should get - | from | to | route | speed | time | - | a | h | ad,dhk,dhk | 63 km/h | 11.5s +-1 | + | from | to | route | speed | time | + | a | h | ad,dhk,dhk | 63 km/h | 11.5s +-1 | # straight - | i | g | fim,fg,fg | 55 km/h | 13s +-1 | + | i | g | fim,fg,fg | 55 km/h | 13s +-1 | # right - ifg penalty - | a | e | ad,de,de | 64 km/h | 11s +-1 | + | a | e | ad,def,def | 64 km/h | 11s +-1 | # left - faster because of negative ade penalty - | c | g | cd,de,ef,fg,fg | 63 km/h | 23s +-1 | + | c | g | cd,def,fg,fg | 63 km/h | 23s +-1 | # double straight - | p | g | mp,fim,fg,fg | 59 km/h | 24.5s +-1 | + | p | g | mp,fim,fg,fg | 59 km/h | 24.5s +-1 | # straight-right - ifg penalty - | a | l | ad,de,ef,fim,lm,lm | 61 km/h | 35.5s +-1 | + | a | l | ad,def,fim,klm,klm | 61 km/h | 35.5s +-1 | # was straight-left - forced around by hkl penalty - | l | e | lm,fim,ef,ef | 57 km/h | 25s +-1 | + | l | e | klm,fim,def,def | 57 km/h | 25s +-1 | # double right - forced left by lkh penalty - | g | n | fg,fim,mn,mn | 30 km/h | 47.5s +-1 | + | g | n | fg,fim,mn,mn | 30 km/h | 47.5s +-1 | # double left - imn penalty - | j | c | jk,kl,lm,fim,ef,de,cd,cd | 60 km/h | 48s +-1 | + | j | c | jk,klm,fim,def,cd,cd | 60 km/h | 48s +-1 | # double left - hdc penalty ever so slightly higher than imn; forces all the way around Scenario: Too-negative penalty clamps, but does not fail @@ -90,8 +88,8 @@ Feature: Traffic - turn penalties 1,4,5,-10 """ When I route I should get - | from | to | route | time | - | a | d | ad,ad | 10s +-1 | - | a | e | ad,de,de | 10s +-1 | - | b | f | bf,bf | 10s +-1 | - | b | g | bf,fg,fg | 20s +-1 | + | from | to | route | time | + | a | d | ad,ad | 10s +-1 | + | a | e | ad,def,def | 10s +-1 | + | b | f | bf,bf | 10s +-1 | + | b | g | bf,fg,fg | 20s +-1 | diff --git a/features/guidance/collapse.feature b/features/guidance/collapse.feature index ac4a826c9..8665e2bc8 100644 --- a/features/guidance/collapse.feature +++ b/features/guidance/collapse.feature @@ -345,3 +345,143 @@ Feature: Collapse | a,d | first,first,first,first | depart,continue left,continue right,arrive | | a,e | first,second,second | depart,turn left,arrive | | a,f | first,third,third | depart,turn straight,arrive | + + Scenario: Bridge on unnamed road + Given the node map + | a | b | | | | c | d | + + And the ways + | nodes | highway | name | + | ab | primary | | + | bc | primary | Bridge | + | cd | primary | | + + When I route I should get + | waypoints | route | turns | + | a,d | , | depart,arrive | + + Scenario: Crossing Bridge into Segregated Turn + Given the node map + | | | | | | f | + | i | h | | | g | e | + | a | b | | | c | d | + + And the ways + | nodes | highway | oneway | name | + | ab | primary | yes | to_bridge | + | bc | primary | yes | bridge | + | cd | primary | yes | off_bridge | + | de | primary | yes | | + | ef | primary | no | target_road | + | eg | primary | yes | off_bridge | + | gh | primary | yes | bridge | + | hi | primary | yes | to_bridge | + + When I route I should get + | waypoints | route | turns | + | a,f | to_bridge,target_road,target_road | depart,turn left,arrive | + + Scenario: Pankenbruecke + Given the node map + | h | | | | | | i | | | | | | | + | | | b | c | d | e | f | | | | | | g | + | a | | | | | | | | | | | | | + + And the ways + | nodes | highway | name | oneway | + | abh | primary | inroad | yes | + | 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 | + | a,g | inroad,outroad,outroad | depart,new name straight,arrive | + | a,i | inroad,cross,cross | depart,turn left,arrive | + + Scenario: Close Turns - Don't Collapse + Given the node map + | | g | d | | + | | | | | + | e | b | c | f | + | | | | | + | | a | h | | + + And the ways + | nodes | highway | name | + | ab | primary | in | + | ebcf | primary | cross | + | cd | primary | out | + | bg | primary | straight | + | ch | primary | reverse | + + When I route I should get + | waypoints | route | turns | + | a,d | in,cross,out,out | depart,turn right,turn left,arrive | + | a,h | in,cross,reverse,reverse | depart,turn right,turn right,arrive | + | g,d | straight,cross,out,out | depart,turn left,turn left,arrive | + + Scenario: No Name During Turns + Given the node map + | a | b | | + | | c | d | + + And the ways + | nodes | highway | name | + | ab | tertiary | road | + | bc | tertiary | | + | cd | tertiary | road | + + When I route I should get + | waypoints | route | turns | + | a,d | road,road | depart,arrive | + + Scenario: No Name During Turns, Random Oneway + Given the node map + | a | b | | + | | c | d | + + And the ways + | nodes | highway | name | oneway | + | ab | tertiary | road | no | + | bc | tertiary | | yes | + | cd | tertiary | road | no | + + When I route I should get + | waypoints | route | turns | + | a,d | road,road | depart,arrive | + + Scenario: Pulled Back Turn + Given the node map + | | | d | + | a | b | c | + | | e | | + + And the ways + | nodes | highway | name | + | abc | tertiary | road | + | cd | tertiary | left | + | be | tertiary | right | + + When I route I should get + | waypoints | route | turns | + | a,d | road,left,left | depart,turn left,arrive | + | a,e | road,right,right | depart,turn right,arrive | + + Scenario: No Name During Turns, keep important turns + Given the node map + | a | b | e | + | | c | d | + + And the ways + | nodes | highway | name | + | ab | tertiary | road | + | bc | tertiary | | + | cd | tertiary | road | + | be | tertiary | other | + + When I route I should get + | waypoints | route | turns | + | a,d | road,road,road | depart,continue right,arrive | + diff --git a/features/guidance/intersections.feature b/features/guidance/intersections.feature index b12f19c7a..b25667a09 100644 --- a/features/guidance/intersections.feature +++ b/features/guidance/intersections.feature @@ -117,9 +117,9 @@ Feature: Intersections Data | cf | corner | When I route I should get - | waypoints | route | turns | intersections | - | a,d | through,through | depart,arrive | true:90,true:0 true:90 false:270,true:90 true:180 false:270;true:270 | - | f,a | corner,throughbridge,through | depart,end of road left,arrive | true:0;true:90 false:180 true:270,true:0 false:90 true:270;true:90 | + | waypoints | route | turns | intersections | + | a,d | through,through | depart,arrive | true:90,true:0 true:90 false:270,true:90 true:180 false:270;true:270 | + | f,a | corner,through,through | depart,end of road left,arrive | true:0;true:90 false:180 true:270,true:0 false:90 true:270;true:90 | Scenario: Roundabouts Given the node map diff --git a/features/guidance/new-name.feature b/features/guidance/new-name.feature index 984ae7bb2..8c823dd7c 100644 --- a/features/guidance/new-name.feature +++ b/features/guidance/new-name.feature @@ -3,7 +3,7 @@ Feature: New-Name Instructions Background: Given the profile "car" - Given a grid size of 10 meters + Given a grid size of 100 meters Scenario: Undisturbed name Change Given the node map @@ -136,7 +136,7 @@ Feature: New-Name Instructions Scenario: Empty road names - Announce Change From, suppress Change To Given the node map - | a | | b | | c | | d | + | a | | b | 1 | c | | d | And the ways | nodes | name | @@ -147,7 +147,7 @@ Feature: New-Name Instructions When I route I should get | waypoints | route | turns | | a,d | ab,cd,cd | depart,new name straight,arrive | - | a,c | ab, | depart,arrive | + | a,1 | ab, | depart,arrive | Scenario: Empty road names - Loose name shortly Given the node map diff --git a/features/testbot/alternative.feature b/features/testbot/alternative.feature index 4031631a0..8f2edcca8 100644 --- a/features/testbot/alternative.feature +++ b/features/testbot/alternative.feature @@ -3,6 +3,7 @@ Feature: Alternative route Background: Given the profile "testbot" + And a grid size of 200 meters And the node map | | b | c | d | | | @@ -17,11 +18,11 @@ Feature: Alternative route | dz | | ag | | gh | + | ck | + | kh | | hi | | ij | | jz | - | ck | - | kh | Scenario: Enabled alternative Given the query options diff --git a/features/testbot/basic.feature b/features/testbot/basic.feature index c2f969ddb..03b48929d 100644 --- a/features/testbot/basic.feature +++ b/features/testbot/basic.feature @@ -56,7 +56,7 @@ Feature: Basic Routing Scenario: Two ways connected in a straight line Given the node map - | a | b | c | + | a | | b | | c | And the ways | nodes | diff --git a/features/testbot/bearing_param.feature b/features/testbot/bearing_param.feature index 267a39622..b8450dfdc 100644 --- a/features/testbot/bearing_param.feature +++ b/features/testbot/bearing_param.feature @@ -43,8 +43,8 @@ Feature: Bearing parameter Scenario: Testbot - Initial bearing on split way Given the node map - | d | | | | | 1 | | | | | c | - | a | | | | | 0 | | | | | b | + | g | d | | | | | 1 | | | | | c | f | + | h | a | | | | | 0 | | | | | b | e | And the ways | nodes | oneway | @@ -52,6 +52,10 @@ Feature: Bearing parameter | bc | yes | | cd | yes | | da | yes | + | be | yes | + | fc | yes | + | dg | yes | + | ha | yes | When I route I should get | from | to | bearings | route | bearing | diff --git a/features/testbot/continue_straight.feature b/features/testbot/continue_straight.feature index 40fcbb4fd..d761eadb6 100644 --- a/features/testbot/continue_straight.feature +++ b/features/testbot/continue_straight.feature @@ -3,6 +3,7 @@ Feature: U-turns at via points Background: Given the profile "testbot" + Given a grid size of 100 meters Scenario: Continue straight at waypoints enabled by default Given the node map diff --git a/features/testbot/mode.feature b/features/testbot/mode.feature index b98c8bf85..f0675b5f8 100644 --- a/features/testbot/mode.feature +++ b/features/testbot/mode.feature @@ -11,6 +11,7 @@ Feature: Testbot - Travel mode Background: Given the profile "testbot" + Given a grid size of 200 meters Scenario: Testbot - Always announce mode change Given the node map @@ -72,9 +73,9 @@ Feature: Testbot - Travel mode | ab | steps | When I route I should get - | from | to | route | modes | time | - | 0 | 1 | ab,ab | steps down,steps down | 60s +-1 | - | 1 | 0 | ab,ab | steps up,steps up | 60s +-1 | + | from | to | route | modes | time | + | 0 | 1 | ab,ab | steps down,steps down | 120s +-1 | + | 1 | 0 | ab,ab | steps up,steps up | 120s +-1 | @oneway Scenario: Testbot - Modes for oneway, different forward/backward speeds diff --git a/features/testbot/summary.feature b/features/testbot/summary.feature index 91cb83bab..028c863f7 100644 --- a/features/testbot/summary.feature +++ b/features/testbot/summary.feature @@ -3,9 +3,10 @@ Feature: Basic Routing Background: Given the profile "testbot" + Given a grid size of 200 meters @smallest - Scenario: Checking + Scenario: Checking Given the node map | a | b | 1 | c | d | e | diff --git a/include/engine/api/route_api.hpp b/include/engine/api/route_api.hpp index c76ec9fd6..a44a13a7e 100644 --- a/include/engine/api/route_api.hpp +++ b/include/engine/api/route_api.hpp @@ -144,6 +144,7 @@ class RouteAPI : public BaseAPI guidance::trimShortSegments(steps, leg_geometry); leg.steps = guidance::postProcess(std::move(steps)); leg.steps = guidance::collapseTurns(std::move(leg.steps)); + leg.steps = guidance::buildIntersections(std::move(leg.steps)); leg.steps = guidance::assignRelativeLocations(std::move(leg.steps), leg_geometry, phantoms.source_phantom, diff --git a/include/engine/guidance/post_processing.hpp b/include/engine/guidance/post_processing.hpp index 770aa39be..16d383df6 100644 --- a/include/engine/guidance/post_processing.hpp +++ b/include/engine/guidance/post_processing.hpp @@ -37,6 +37,9 @@ std::vector assignRelativeLocations(std::vector steps, const PhantomNode &source_node, const PhantomNode &target_node); +// collapse suppressed instructions remaining into intersections array +std::vector buildIntersections(std::vector steps); + // remove steps invalidated by post-processing std::vector removeNoTurnInstructions(std::vector steps); diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index 564904492..9e1b92ad6 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -1,5 +1,5 @@ -#include "engine/guidance/post_processing.hpp" #include "extractor/guidance/turn_instruction.hpp" +#include "engine/guidance/post_processing.hpp" #include "engine/guidance/assemble_steps.hpp" #include "engine/guidance/toolkit.hpp" @@ -31,35 +31,76 @@ namespace guidance namespace { +const constexpr double MAX_COLLAPSE_DISTANCE = 25; + +// List of types that can be collapsed, if all other restrictions pass +bool isCollapsableInstruction(const TurnInstruction instruction) +{ + return instruction.type == TurnType::NewName || + (instruction.type == TurnType::Suppressed && + instruction.direction_modifier == DirectionModifier::Straight) || + (instruction.type == TurnType::Turn && + instruction.direction_modifier == DirectionModifier::Straight); +} + +// A check whether two instructions can be treated as one. This is only the case for very short +// maneuvers that can, in some form, be seen as one. The additional in_step is to find out about +// a possible u-turn. +bool collapsable(const RouteStep &step) +{ + + return step.distance < MAX_COLLAPSE_DISTANCE && + isCollapsableInstruction(step.maneuver.instruction); +} + +bool compatible(const RouteStep &lhs, const RouteStep &rhs) { return lhs.mode == rhs.mode; } + +double nameSegmentLength(std::size_t at, const std::vector &steps) +{ + double result = steps[at].distance; + while (at + 1 < steps.size() && steps[at + 1].name_id == steps[at].name_id) + { + ++at; + result += steps[at].distance; + } + + return result; +} // invalidate a step and set its content to nothing void invalidateStep(RouteStep &step) { step = getInvalidRouteStep(); } +void print(const RouteStep &step) +{ + std::cout << static_cast(step.maneuver.instruction.type) << " " + << static_cast(step.maneuver.instruction.direction_modifier) << " " + << static_cast(step.maneuver.waypoint_type) << " Duration: " << step.duration + << " Distance: " << step.distance << " Geometry: " << step.geometry_begin << " " + << step.geometry_end << " exit: " << step.maneuver.exit + << " Intersections: " << step.intersections.size() << " ["; + + for (const auto &intersection : step.intersections) + { + std::cout << "(bearings:"; + for (auto bearing : intersection.bearings) + std::cout << " " << bearing; + std::cout << ", entry: "; + for (auto entry : intersection.entry) + std::cout << " " << entry; + std::cout << ")"; + } + std::cout << "] name[" << step.name_id << "]: " << step.name; +} + void print(const std::vector &steps) { std::cout << "Path\n"; int segment = 0; for (const auto &step : steps) { - std::cout << "\t[" << ++segment << "]: " << static_cast(step.maneuver.instruction.type) - << " " << static_cast(step.maneuver.instruction.direction_modifier) << " " - << static_cast(step.maneuver.waypoint_type) << " Duration: " << step.duration - << " Distance: " << step.distance << " Geometry: " << step.geometry_begin << " " - << step.geometry_end << " exit: " << step.maneuver.exit - << " Intersections: " << step.intersections.size() << " ["; - - for (const auto &intersection : step.intersections) - { - std::cout << "(bearings:"; - for (auto bearing : intersection.bearings) - std::cout << " " << bearing; - std::cout << ", entry: "; - for (auto entry : intersection.entry) - std::cout << " " << entry; - std::cout << ")"; - } - - std::cout << "] name[" << step.name_id << "]: " << step.name << std::endl; + std::cout << "\t[" << segment++ << "]: "; + print(step); + std::cout << std::endl; } } @@ -327,15 +368,6 @@ RouteStep elongate(RouteStep step, const RouteStep &by_step) return step; } -// A check whether two instructions can be treated as one. This is only the case for very short -// maneuvers that can, in some form, be seen as one. The additional in_step is to find out about -// a possible u-turn. -bool collapsable(const RouteStep &step) -{ - const constexpr double MAX_COLLAPSE_DISTANCE = 25; - return step.distance < MAX_COLLAPSE_DISTANCE; -} - void collapseTurnAt(std::vector &steps, const std::size_t two_back_index, const std::size_t one_back_index, @@ -353,46 +385,59 @@ void collapseTurnAt(std::vector &steps, }; BOOST_ASSERT(!one_back_step.intersections.empty() && !current_step.intersections.empty()); - const auto isCollapsableInstruction = [](const TurnInstruction instruction) { - return instruction.type == TurnType::NewName || - (instruction.type == TurnType::Turn && - instruction.direction_modifier == DirectionModifier::Straight); - }; // Very Short New Name - if (isCollapsableInstruction(one_back_step.maneuver.instruction)) + if (collapsable(one_back_step)) { BOOST_ASSERT(two_back_index < steps.size()); - if (one_back_step.mode == steps[two_back_index].mode) + if (compatible(one_back_step, steps[two_back_index])) { + BOOST_ASSERT(!one_back_step.intersections.empty()); + if (TurnType::Continue == current_step.maneuver.instruction.type || + TurnType::Suppressed == current_step.maneuver.instruction.type) + steps[step_index].maneuver.instruction.type = TurnType::Turn; + else if (TurnType::NewName == current_step.maneuver.instruction.type && + current_step.maneuver.instruction.direction_modifier != + DirectionModifier::Straight && + one_back_step.intersections.front().bearings.size() > 2) + steps[step_index].maneuver.instruction.type = TurnType::Turn; + steps[two_back_index] = elongate(std::move(steps[two_back_index]), one_back_step); // If the previous instruction asked to continue, the name change will have to // be changed into a turn invalidateStep(steps[one_back_index]); - - if (TurnType::Continue == current_step.maneuver.instruction.type) - steps[step_index].maneuver.instruction.type = TurnType::Turn; } } // very short segment after turn - else if (isCollapsableInstruction(current_step.maneuver.instruction)) + else if (one_back_step.distance <= MAX_COLLAPSE_DISTANCE && + isCollapsableInstruction(current_step.maneuver.instruction)) { - if (one_back_step.mode == current_step.mode) + if (compatible(one_back_step, current_step)) { - steps[step_index] = elongate(std::move(steps[step_index]), steps[one_back_index]); - invalidateStep(steps[one_back_index]); - - if (TurnType::Continue == current_step.maneuver.instruction.type) + steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); + if ((TurnType::Continue == one_back_step.maneuver.instruction.type || + TurnType::Suppressed == one_back_step.maneuver.instruction.type) && + current_step.name_id != steps[two_back_index].name_id) { - steps[step_index].maneuver.instruction.type = TurnType::Turn; + steps[one_back_index].maneuver.instruction.type = TurnType::Turn; } + else if (TurnType::Turn == one_back_step.maneuver.instruction.type && + current_step.name_id == steps[two_back_index].name_id) + { + steps[one_back_index].maneuver.instruction.type = TurnType::Continue; + } + steps[one_back_index].name = current_step.name; + steps[one_back_index].name_id = current_step.name_id; + invalidateStep(steps[step_index]); } } // Potential U-Turn - else if (bearingsAreReversed(util::bearing::reverseBearing( + else if (one_back_step.distance <= MAX_COLLAPSE_DISTANCE && + bearingsAreReversed(util::bearing::reverseBearing( one_back_step.intersections.front() .bearings[one_back_step.intersections.front().in]), current_step.intersections.front() - .bearings[current_step.intersections.front().out])) + .bearings[current_step.intersections.front().out]) && + compatible(one_back_step, current_step)) { BOOST_ASSERT(two_back_index < steps.size()); @@ -405,8 +450,7 @@ void collapseTurnAt(std::vector &steps, (step_index + 1 < steps.size()) && isCollapsableInstruction(steps[step_index + 1].maneuver.instruction); const bool u_turn_with_name_change = - collapsable(current_step) && continues_with_name_change && - steps[step_index + 1].name == steps[two_back_index].name; + continues_with_name_change && steps[step_index + 1].name == steps[two_back_index].name; if (direct_u_turn || u_turn_with_name_change) { @@ -518,13 +562,6 @@ std::vector postProcess(std::vector steps) has_entered_roundabout = false; on_roundabout = false; } - else if (instruction.type == TurnType::Suppressed) - { - // count intersections. We cannot use exit, since intersections can follow directly - // after a roundabout - steps[last_valid_instruction] = elongate(steps[last_valid_instruction], step); - step.maneuver.instruction = TurnInstruction::NO_TURN(); - } else if (!isSilent(instruction)) { // Remember the last non silent instruction @@ -564,58 +601,57 @@ std::vector collapseTurns(std::vector steps) BOOST_ASSERT(index > 0); BOOST_ASSERT(index < steps.size()); --index; - while (index > 0 && steps[index].maneuver.instruction == TurnInstruction::NO_TURN()) + while (index > 0 && steps[index].maneuver.instruction.type == TurnType::NoTurn) --index; return index; }; - // Check for an initial unwanted new-name - { - const auto ¤t_step = steps[1]; - if (TurnType::NewName == current_step.maneuver.instruction.type && - current_step.name == steps[0].name) + const auto getPreviousNameIndex = [&steps](std::size_t index) { + BOOST_ASSERT(index > 0); + BOOST_ASSERT(index < steps.size()); + --index; // make sure to skip the current name + while (index > 0 && steps[index].name_id == EMPTY_NAMEID) { - steps[0] = elongate(std::move(steps[0]), steps[1]); - invalidateStep(steps[1]); + --index; } - } - const auto isCollapsableInstruction = [](const TurnInstruction instruction) { - return instruction.type == TurnType::NewName || - (instruction.type == TurnType::Turn && - instruction.direction_modifier == DirectionModifier::Straight); + return index; }; - // Special case handling: if the phantomnode landed on a sliproad, we - // change this into a 'turn' instruction. Sliproads are small ramps - // between roads, not ramps. - if (steps.size() >= 3 && - steps[steps.size() - 2].maneuver.instruction.type == TurnType::Sliproad) - { - steps[steps.size() - 2].maneuver.instruction.type = TurnType::Turn; - } + // a series of turns is only possible to collapse if its only name changes and suppressed turns. + const auto canCollapseAll = [&steps](std::size_t index, const std::size_t end_index) { + BOOST_ASSERT(end_index <= steps.size()); + for (; index < end_index; ++index) + { + if (steps[index].maneuver.instruction.type != TurnType::Suppressed && + steps[index].maneuver.instruction.type != TurnType::NewName) + return false; + } + return true; + }; // first and last instructions are waypoints that cannot be collapsed - for (std::size_t step_index = 2; step_index < steps.size(); ++step_index) + for (std::size_t step_index = 1; step_index + 1 < steps.size(); ++step_index) { const auto ¤t_step = steps[step_index]; const auto one_back_index = getPreviousIndex(step_index); BOOST_ASSERT(one_back_index < steps.size()); - // cannot collapse the depart instruction - if (one_back_index == 0 || current_step.maneuver.instruction == TurnInstruction::NO_TURN()) - continue; - const auto &one_back_step = steps[one_back_index]; - const auto two_back_index = getPreviousIndex(one_back_index); - BOOST_ASSERT(two_back_index < steps.size()); + // how long has a name change to be so that we announce it, even as a bridge? + const constexpr auto name_segment_cutoff_length = 100; + const auto isBasicNameChange = [](const RouteStep &step) { + return step.intersections.size() == 1 && + step.intersections.front().bearings.size() == 2 && + DirectionModifier::Straight == step.maneuver.instruction.direction_modifier; + }; - // Handle sliproads from motorways in urban areas + // Handle sliproads from motorways in urban areas, save from modifying depart, since + // TurnType::Sliproad != TurnType::NoTurn if (one_back_step.maneuver.instruction.type == TurnType::Sliproad) { - // Handle possible u-turns between highways that look like slip-roads - if (steps[two_back_index].name_id == steps[step_index].name_id && + if (steps[getPreviousIndex(one_back_index)].name_id == steps[step_index].name_id && steps[step_index].name_id != EMPTY_NAMEID) { steps[one_back_index].maneuver.instruction.type = TurnType::Continue; @@ -624,42 +660,57 @@ std::vector collapseTurns(std::vector steps) { steps[one_back_index].maneuver.instruction.type = TurnType::Turn; } - steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); - steps[one_back_index].name_id = steps[step_index].name_id; - steps[one_back_index].name = steps[step_index].name; + if (compatible(one_back_step, current_step)) + { + steps[one_back_index] = + elongate(std::move(steps[one_back_index]), steps[step_index]); + steps[one_back_index].name_id = steps[step_index].name_id; + steps[one_back_index].name = steps[step_index].name; - const auto exit_intersection = steps[step_index].intersections.front(); - const auto exit_bearing = exit_intersection.bearings[exit_intersection.out]; + const auto exit_intersection = steps[step_index].intersections.front(); + const auto exit_bearing = exit_intersection.bearings[exit_intersection.out]; - const auto entry_intersection = steps[one_back_index].intersections.front(); - const auto entry_bearing = entry_intersection.bearings[entry_intersection.in]; + const auto entry_intersection = steps[one_back_index].intersections.front(); + const auto entry_bearing = entry_intersection.bearings[entry_intersection.in]; - const double angle = - turn_angle(util::bearing::reverseBearing(entry_bearing), exit_bearing); - steps[one_back_index].maneuver.instruction.direction_modifier = - ::osrm::util::guidance::getTurnDirection(angle); - invalidateStep(steps[step_index]); + const double angle = + turn_angle(util::bearing::reverseBearing(entry_bearing), exit_bearing); + steps[one_back_index].maneuver.instruction.direction_modifier = + ::osrm::util::guidance::getTurnDirection(angle); + invalidateStep(steps[step_index]); + } } // Due to empty segments, we can get name-changes from A->A // These have to be handled in post-processing else if (isCollapsableInstruction(current_step.maneuver.instruction) && - current_step.name == steps[one_back_index].name) + current_step.maneuver.instruction.type != TurnType::Suppressed && + steps[getPreviousNameIndex(step_index)].name == current_step.name && + canCollapseAll(getPreviousNameIndex(step_index) + 1, step_index + 1)) { - steps[one_back_index] = elongate(std::move(steps[one_back_index]), steps[step_index]); - invalidateStep(steps[step_index]); + BOOST_ASSERT(step_index > 0); + const std::size_t last_available_name_index = getPreviousNameIndex(step_index); + + for (std::size_t index = last_available_name_index + 1; index <= step_index; ++index) + { + steps[last_available_name_index] = + elongate(std::move(steps[last_available_name_index]), steps[index]); + invalidateStep(steps[index]); + } } // If we look at two consecutive name changes, we can check for a name oszillation. // A name oszillation changes from name A shortly to name B and back to A. // In these cases, the name change will be suppressed. - else if (isCollapsableInstruction(current_step.maneuver.instruction) && + else if (one_back_index > 0 && compatible(current_step, one_back_step) && + isCollapsableInstruction(current_step.maneuver.instruction) && isCollapsableInstruction(one_back_step.maneuver.instruction)) { - // valid due to step_index starting at 2 + const auto two_back_index = getPreviousIndex(one_back_index); + BOOST_ASSERT(two_back_index < steps.size()); + // valid, since one_back is collapsable: const auto &coming_from_name = steps[two_back_index].name; if (current_step.name == coming_from_name) { - if (current_step.mode == one_back_step.mode && - one_back_step.mode == steps[two_back_index].mode) + if (compatible(one_back_step, steps[two_back_index])) { steps[two_back_index] = elongate(elongate(std::move(steps[two_back_index]), steps[one_back_index]), @@ -670,14 +721,44 @@ std::vector collapseTurns(std::vector steps) // TODO discuss: we could think about changing the new-name to a pure notification // about mode changes } + else if (nameSegmentLength(one_back_index, steps) < name_segment_cutoff_length && + isBasicNameChange(one_back_step) && isBasicNameChange(current_step)) + { + steps[two_back_index] = + elongate(std::move(steps[two_back_index]), steps[one_back_index]); + invalidateStep(steps[one_back_index]); + if (nameSegmentLength(step_index, steps) < name_segment_cutoff_length) + { + steps[two_back_index] = + elongate(std::move(steps[two_back_index]), steps[step_index]); + invalidateStep(steps[step_index]); + } + } + else if (one_back_step.distance <= MAX_COLLAPSE_DISTANCE) + { + // check for one of the multiple collapse scenarios and, if possible, collapse the + // turn + const auto two_back_index = getPreviousIndex(one_back_index); + BOOST_ASSERT(two_back_index < steps.size()); + collapseTurnAt(steps, two_back_index, one_back_index, step_index); + } } - else if (collapsable(one_back_step)) + else if (one_back_index > 0 && one_back_step.distance <= MAX_COLLAPSE_DISTANCE) { // check for one of the multiple collapse scenarios and, if possible, collapse the turn + const auto two_back_index = getPreviousIndex(one_back_index); + BOOST_ASSERT(two_back_index < steps.size()); collapseTurnAt(steps, two_back_index, one_back_index, step_index); } } + // handle final sliproad + if (steps.size() >= 3 && + steps[steps.size() - 2].maneuver.instruction.type == TurnType::Sliproad) + { + steps[steps.size() - 2].maneuver.instruction.type = TurnType::Turn; + } + BOOST_ASSERT(steps.front().intersections.size() >= 1); BOOST_ASSERT(steps.front().intersections.front().bearings.size() == 1); BOOST_ASSERT(steps.front().intersections.front().entry.size() == 1); @@ -700,7 +781,6 @@ std::vector collapseTurns(std::vector steps) // usually not be as relevant. void trimShortSegments(std::vector &steps, LegGeometry &geometry) { - if (steps.size() < 2 || geometry.locations.size() <= 2) return; @@ -955,6 +1035,29 @@ LegGeometry resyncGeometry(LegGeometry leg_geometry, const std::vector buildIntersections(std::vector steps) +{ + std::size_t last_valid_instruction = 0; + for (std::size_t step_index = 0; step_index < steps.size(); ++step_index) + { + auto &step = steps[step_index]; + const auto instruction = step.maneuver.instruction; + if (instruction.type == TurnType::Suppressed) + { + // count intersections. We cannot use exit, since intersections can follow directly + // after a roundabout + steps[last_valid_instruction] = elongate(steps[last_valid_instruction], step); + step.maneuver.instruction = TurnInstruction::NO_TURN(); + } + else if (!isSilent(instruction)) + { + // Remember the last non silent instruction + last_valid_instruction = step_index; + } + } + return removeNoTurnInstructions(std::move(steps)); +} + } // namespace guidance } // namespace engine } // namespace osrm diff --git a/src/extractor/guidance/turn_handler.cpp b/src/extractor/guidance/turn_handler.cpp index 198c20dcb..683df168a 100644 --- a/src/extractor/guidance/turn_handler.cpp +++ b/src/extractor/guidance/turn_handler.cpp @@ -69,9 +69,6 @@ Intersection TurnHandler::handleTwoWayTurn(const EdgeID via_edge, Intersection i intersection[1].turn.instruction = getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]); - if (intersection[1].turn.instruction.type == TurnType::Suppressed) - intersection[1].turn.instruction.type = TurnType::NoTurn; - return intersection; }