#include "engine/guidance/collapse_turns.hpp" #include "guidance/constants.hpp" #include "guidance/turn_instruction.hpp" #include "engine/guidance/collapse_scenario_detection.hpp" #include "engine/guidance/collapsing_utility.hpp" #include "util/bearing.hpp" #include "util/guidance/name_announcements.hpp" #include #include namespace osrm { namespace engine { namespace guidance { using osrm::util::angularDeviation; using namespace osrm::guidance; namespace { const constexpr double MAX_COLLAPSE_DISTANCE = 30; // find the combined turn angle for two turns. Not in all scenarios we can easily add both angles // (e.g 90 degree left followed by 90 degree right would be no turn at all). double findTotalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_step) { if (entry_step.geometry_begin > exit_step.geometry_begin) return findTotalTurnAngle(exit_step, entry_step); const auto exit_intersection = exit_step.intersections.front(); const auto exit_step_exit_bearing = exit_intersection.bearings[exit_intersection.out]; const auto exit_step_entry_bearing = util::bearing::reverse(exit_intersection.bearings[exit_intersection.in]); const auto entry_intersection = entry_step.intersections.front(); const auto entry_step_entry_bearing = 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::bearing::angleBetween(exit_step_entry_bearing, exit_step_exit_bearing); const auto entry_angle = util::bearing::angleBetween(entry_step_entry_bearing, entry_step_exit_bearing); const double total_angle = util::bearing::angleBetween(entry_step_entry_bearing, exit_step_exit_bearing); // both angles are in the same direction, the total turn gets increased // // a ---- b // ` // c // | // d // // Will be considered just like // // a -----b // | // c // | // d const auto use_total_angle = [&]() { // only consider actual turns in combination: if (angularDeviation(total_angle, 180) < 0.5 * NARROW_TURN_ANGLE) return false; // entry step is short and the exit and the exit step does not have intersections?? if (entry_step.distance < MAX_COLLAPSE_DISTANCE) return true; // both go roughly in the same direction if ((entry_angle <= 185 && exit_angle <= 185) || (entry_angle >= 175 && exit_angle >= 175)) return true; return false; }(); // We allow for minor deviations from a straight line if (use_total_angle) { return total_angle; } else { // to prevent ignoring angles like // // a -- b // | // c -- d // // We don't combine both turn angles here but keep the very first turn angle. // We choose the first one, since we consider the first maneuver in a merge range the // important one return entry_angle; } } inline void handleSliproad(RouteStepIterator sliproad_step) { // find the next step after the sliproad step itself (this is not necessarily the next step, // since we might have to skip over traffic lights/node penalties) auto next_step = [&sliproad_step]() { auto next_step = findNextTurn(sliproad_step); while (isTrafficLightStep(*next_step)) { // in sliproad checks, we should have made sure not to include invalid modes BOOST_ASSERT(haveSameMode(*sliproad_step, *next_step)); sliproad_step->ElongateBy(*next_step); next_step->Invalidate(); next_step = findNextTurn(next_step); } BOOST_ASSERT(haveSameMode(*sliproad_step, *next_step)); return next_step; }(); // have we reached the end? if (hasWaypointType(*next_step)) { setInstructionType(*sliproad_step, TurnType::Turn); } else { const auto previous_step = findPreviousTurn(sliproad_step); const auto connecting_same_name_roads = haveSameName(*previous_step, *next_step); auto sliproad_turn_type = connecting_same_name_roads ? TurnType::Continue : TurnType::Turn; setInstructionType(*sliproad_step, sliproad_turn_type); combineRouteSteps(*sliproad_step, *next_step, AdjustToCombinedTurnAngleStrategy(), TransferSignageStrategy(), TransferLanesStrategy()); } } } // namespace // STRATEGIES // keep signage/other entries in route step intact void NoModificationStrategy::operator()(RouteStep &, const RouteStep &) const { // actually do nothing. } // transfer turn type from a different turn void TransferTurnTypeStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { step_at_turn_location.maneuver = transfer_from_step.maneuver; } void AdjustToCombinedTurnAngleStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { // Forks point to left/right. By doing a combined angle, we would risk ending up with // unreasonable fork instrucitons. The direction of a fork only depends on the forking location, // not further angles coming up // // d // . c // a - b // // could end up as `fork left` for `a-b-c`, instead of fork-right if (hasTurnType(step_at_turn_location, TurnType::Fork)) return; // TODO assert transfer_from_step == step_at_turn_location + 1 const auto angle = findTotalTurnAngle(step_at_turn_location, transfer_from_step); step_at_turn_location.maneuver.instruction.direction_modifier = getTurnDirection(angle); } AdjustToCombinedTurnStrategy::AdjustToCombinedTurnStrategy( const RouteStep &step_prior_to_intersection) : step_prior_to_intersection(step_prior_to_intersection) { } void AdjustToCombinedTurnStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { const auto angle = findTotalTurnAngle(step_at_turn_location, transfer_from_step); // Forks and merges point to left/right. By doing a combined angle, we would risk ending up with // unreasonable fork instrucitons. The direction of a fork or a merge only depends on the // location, // not further angles coming up // // d // . c // a - b // // could end up as `fork left` for `a-b-c`, instead of fork-right const auto new_modifier = hasTurnType(step_at_turn_location, TurnType::Fork) || hasTurnType(step_at_turn_location, TurnType::Merge) ? step_at_turn_location.maneuver.instruction.direction_modifier : getTurnDirection(angle); // a turn that is a new name or straight (turn/continue) const auto is_non_turn = [](const RouteStep &step) { return hasTurnType(step, TurnType::NewName) || (hasTurnType(step, TurnType::Turn) && hasModifier(step, DirectionModifier::Straight)) || (hasTurnType(step, TurnType::Continue) && hasModifier(step, DirectionModifier::Straight)); }; // check if the first part is the actual turn const auto transferring_from_non_turn = is_non_turn(transfer_from_step); // or if the maneuver location does not perform an actual turn const auto maneuver_at_non_turn = is_non_turn(step_at_turn_location) || hasTurnType(step_at_turn_location, TurnType::Suppressed); // creating turns if the original instrution wouldn't be a maneuver (also for turn straights)` if (transferring_from_non_turn || maneuver_at_non_turn) { if (hasTurnType(step_at_turn_location, TurnType::Suppressed)) { if (new_modifier == DirectionModifier::Straight) { setInstructionType(step_at_turn_location, TurnType::NewName); } else { step_at_turn_location.maneuver.instruction.type = haveSameName(step_prior_to_intersection, transfer_from_step) ? TurnType::Continue : TurnType::Turn; } } else if (hasTurnType(step_at_turn_location, TurnType::NewName) && hasTurnType(transfer_from_step, TurnType::Suppressed) && new_modifier != DirectionModifier::Straight) { setInstructionType(step_at_turn_location, TurnType::Turn); } else if (hasTurnType(step_at_turn_location, TurnType::Continue) && !haveSameName(step_prior_to_intersection, transfer_from_step)) { setInstructionType(step_at_turn_location, TurnType::Turn); } else if (hasTurnType(step_at_turn_location, TurnType::Turn) && !hasTurnType(transfer_from_step, TurnType::Suppressed) && haveSameName(step_prior_to_intersection, transfer_from_step)) { setInstructionType(step_at_turn_location, TurnType::Continue); } } // if we are turning onto a ramp, we carry the ramp (e.g. a turn onto a ramp that is modelled // later only) else if (hasTurnType(transfer_from_step, TurnType::OnRamp)) { setInstructionType(step_at_turn_location, TurnType::OnRamp); } // switch two turns to a single continue, if necessary else if (hasTurnType(step_at_turn_location, TurnType::Turn) && hasTurnType(transfer_from_step, TurnType::Turn) && haveSameName(step_prior_to_intersection, transfer_from_step)) { setInstructionType(step_at_turn_location, TurnType::Continue); } // switch continue to turn, if possible else if (hasTurnType(step_at_turn_location, TurnType::Continue) && hasTurnType(transfer_from_step, TurnType::Turn) && !haveSameName(step_prior_to_intersection, transfer_from_step)) { setInstructionType(step_at_turn_location, TurnType::Turn); } // finally set our new modifier step_at_turn_location.maneuver.instruction.direction_modifier = new_modifier; } StaggeredTurnStrategy::StaggeredTurnStrategy(const RouteStep &step_prior_to_intersection) : step_prior_to_intersection(step_prior_to_intersection) { } void StaggeredTurnStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { step_at_turn_location.maneuver.instruction.direction_modifier = DirectionModifier::Straight; step_at_turn_location.maneuver.instruction.type = haveSameName(step_prior_to_intersection, transfer_from_step) ? TurnType::Suppressed : TurnType::NewName; } void CombineSegregatedStepsStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { // Handle end of road if (hasTurnType(step_at_turn_location, TurnType::EndOfRoad) || hasTurnType(transfer_from_step, TurnType::EndOfRoad)) { setInstructionType(step_at_turn_location, TurnType::EndOfRoad); } } SegregatedTurnStrategy::SegregatedTurnStrategy(const RouteStep &step_prior_to_intersection) : step_prior_to_intersection(step_prior_to_intersection) { } void SegregatedTurnStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { // Used to control updating of the modifier based on turn direction bool update_modifier_for_turn_direction = true; const auto calculate_turn_angle = [](const RouteStep &entry_step, const RouteStep &exit_step) { return util::bearing::angleBetween(entry_step.maneuver.bearing_before, exit_step.maneuver.bearing_after); }; // Calculate turn angle and direction for segregated const auto turn_angle = calculate_turn_angle(step_at_turn_location, transfer_from_step); const auto turn_direction = getTurnDirection(turn_angle); const auto is_straight_step = [](const RouteStep &step) { return ((hasTurnType(step, TurnType::NewName) || hasTurnType(step, TurnType::Continue) || hasTurnType(step, TurnType::Suppressed) || hasTurnType(step, TurnType::Turn)) && (hasModifier(step, DirectionModifier::Straight) || hasModifier(step, DirectionModifier::SlightLeft) || hasModifier(step, DirectionModifier::SlightRight))); }; const auto is_turn_step = [](const RouteStep &step) { return (hasTurnType(step, TurnType::Turn) || hasTurnType(step, TurnType::Continue) || hasTurnType(step, TurnType::NewName) || hasTurnType(step, TurnType::Suppressed)); }; // Process end of road step if (hasTurnType(step_at_turn_location, TurnType::EndOfRoad) || hasTurnType(transfer_from_step, TurnType::EndOfRoad)) { // Keep end of road setInstructionType(step_at_turn_location, TurnType::EndOfRoad); } // Process fork step at turn else if (hasTurnType(step_at_turn_location, TurnType::Fork)) { // Do not update modifier based on turn direction update_modifier_for_turn_direction = false; } // Process straight step else if ((turn_direction == guidance::DirectionModifier::Straight) && is_straight_step(transfer_from_step)) { // Determine if continue or new name setInstructionType(step_at_turn_location, (haveSameName(step_prior_to_intersection, transfer_from_step) ? TurnType::Suppressed : TurnType::NewName)); } // Process wider straight step else if (isWiderStraight(turn_angle) && hasSingleIntersection(step_at_turn_location) && hasStraightestTurn(step_at_turn_location) && hasStraightestTurn(transfer_from_step)) { // Determine if continue or new name setInstructionType(step_at_turn_location, (haveSameName(step_prior_to_intersection, transfer_from_step) ? TurnType::Suppressed : TurnType::NewName)); // Set modifier to straight setModifier(step_at_turn_location, osrm::guidance::DirectionModifier::Straight); // Do not update modifier based on turn direction update_modifier_for_turn_direction = false; } // Process turn step else if ((turn_direction != guidance::DirectionModifier::Straight) && is_turn_step(transfer_from_step)) { // Mark as turn setInstructionType(step_at_turn_location, TurnType::Turn); } // Process the others not covered above by using the transfer step turn type else { // Set type from transfer step setInstructionType(step_at_turn_location, transfer_from_step.maneuver.instruction.type); } // Update modifier based on turn direction, if needed if (update_modifier_for_turn_direction) { setModifier(step_at_turn_location, turn_direction); } } SetFixedInstructionStrategy::SetFixedInstructionStrategy(const TurnInstruction instruction) : instruction(instruction) { } void SetFixedInstructionStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &) const { step_at_turn_location.maneuver.instruction = instruction; } void TransferSignageStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { step_at_turn_location.AdaptStepSignage(transfer_from_step); step_at_turn_location.rotary_name = transfer_from_step.rotary_name; step_at_turn_location.rotary_pronunciation = transfer_from_step.rotary_pronunciation; } void TransferLanesStrategy::operator()(RouteStep &step_at_turn_location, const RouteStep &transfer_from_step) const { step_at_turn_location.intersections.front().lanes = transfer_from_step.intersections.front().lanes; step_at_turn_location.intersections.front().lane_description = transfer_from_step.intersections.front().lane_description; } void suppressStep(RouteStep &step_at_turn_location, RouteStep &step_after_turn_location) { return combineRouteSteps(step_at_turn_location, step_after_turn_location, NoModificationStrategy(), NoModificationStrategy(), NoModificationStrategy()); } // OTHER IMPLEMENTATIONS OSRM_ATTR_WARN_UNUSED RouteSteps collapseTurnInstructions(RouteSteps steps) { // make sure we can safely iterate over all steps (has depart/arrive with TurnType::NoTurn) BOOST_ASSERT(!hasTurnType(steps.front()) && !hasTurnType(steps.back())); BOOST_ASSERT(hasWaypointType(steps.front()) && hasWaypointType(steps.back())); if (steps.size() <= 2) return steps; // start of with no-op for (auto current_step = steps.begin() + 1; current_step + 1 != steps.end(); ++current_step) { if (entersRoundabout(current_step->maneuver.instruction) || staysOnRoundabout(current_step->maneuver.instruction)) { // If postProcess is called before then all corresponding leavesRoundabout steps are // removed and the current roundabout step can be ignored by directly proceeding to // the next step. // If postProcess is not called before then all steps till the next leavesRoundabout // step must be skipped to prevent incorrect roundabouts post-processing. // are we done for good? if (current_step + 1 == steps.end()) break; else continue; } // only operate on actual turns if (!hasTurnType(*current_step)) continue; // handle all situations involving the sliproad turn type if (hasTurnType(*current_step, TurnType::Sliproad)) { handleSliproad(current_step); continue; } // don't collapse next step if it is a waypoint alread const auto next_step = findNextTurn(current_step); if (hasWaypointType(*next_step)) break; const auto previous_step = findPreviousTurn(current_step); // don't collapse anything that does change modes if (current_step->mode != next_step->mode) continue; // handle staggered intersections: // a staggered intersection describes to turns in rapid succession that go in opposite // directions (e.g. right + left) with a very short segment in between if (isStaggeredIntersection(previous_step, current_step, next_step)) { combineRouteSteps(*current_step, *next_step, StaggeredTurnStrategy(*previous_step), TransferSignageStrategy(), NoModificationStrategy()); } else if (isUTurn(previous_step, current_step, next_step)) { combineRouteSteps( *current_step, *next_step, SetFixedInstructionStrategy({TurnType::Continue, DirectionModifier::UTurn}), TransferSignageStrategy(), NoModificationStrategy()); } else if (isNameOszillation(previous_step, current_step, next_step)) { // first deactivate the second name switch suppressStep(*current_step, *next_step); // and then the first (to ensure both iterators to be valid) suppressStep(*previous_step, *current_step); } else if (maneuverPreceededByNameChange(previous_step, current_step, next_step) || maneuverPreceededBySuppressedDirection(current_step, next_step)) { const auto strategy = AdjustToCombinedTurnStrategy(*previous_step); strategy(*next_step, *current_step); // suppress previous step suppressStep(*previous_step, *current_step); } else if (maneuverSucceededByNameChange(current_step, next_step) || nameChangeImmediatelyAfterSuppressed(current_step, next_step) || maneuverSucceededBySuppressedDirection(current_step, next_step) || closeChoicelessTurnAfterTurn(current_step, next_step)) { combineRouteSteps(*current_step, *next_step, AdjustToCombinedTurnStrategy(*previous_step), TransferSignageStrategy(), NoModificationStrategy()); } else if (straightTurnFollowedByChoiceless(current_step, next_step)) { combineRouteSteps(*current_step, *next_step, AdjustToCombinedTurnStrategy(*previous_step), TransferSignageStrategy(), NoModificationStrategy()); } else if (suppressedStraightBetweenTurns(previous_step, current_step, next_step)) { const auto far_back_step = findPreviousTurn(previous_step); previous_step->ElongateBy(*current_step); current_step->Invalidate(); combineRouteSteps(*previous_step, *next_step, AdjustToCombinedTurnStrategy(*far_back_step), TransferSignageStrategy(), NoModificationStrategy()); } // if the current collapsing triggers, we can check for advanced scenarios that only are // possible after an inital collapse step (e.g. name change right after a u-turn) // // f - e - d // | | // a - b - c // // In this scenario, bc and de might belong to a different road than a-b and f-e (since // there are no fix conventions how to label them in segregated intersections). These steps // might only become apparent after some initial collapsing const auto new_next_step = findNextTurn(current_step); if (doubleChoiceless(current_step, new_next_step)) { combineRouteSteps(*current_step, *new_next_step, AdjustToCombinedTurnStrategy(*previous_step), TransferSignageStrategy(), NoModificationStrategy()); } if (!hasWaypointType(*previous_step)) { const auto far_back_step = findPreviousTurn(previous_step); // due to name changes, we can find u-turns a bit late. Thats why we check far back as // well if (isUTurn(far_back_step, previous_step, current_step)) { combineRouteSteps( *previous_step, *current_step, SetFixedInstructionStrategy({TurnType::Continue, DirectionModifier::UTurn}), TransferSignageStrategy(), NoModificationStrategy()); } } } return steps; } // OTHER IMPLEMENTATIONS OSRM_ATTR_WARN_UNUSED RouteSteps collapseSegregatedTurnInstructions(RouteSteps steps) { // make sure we can safely iterate over all steps (has depart/arrive with TurnType::NoTurn) BOOST_ASSERT(!hasTurnType(steps.front()) && !hasTurnType(steps.back())); BOOST_ASSERT(hasWaypointType(steps.front()) && hasWaypointType(steps.back())); if (steps.size() <= 2) return steps; auto curr_step = steps.begin() + 1; auto next_step = curr_step + 1; const auto last_step = steps.end() - 1; // Loop over steps to collapse the segregated intersections; ignore first and last step while (next_step != last_step) { const auto prev_step = findPreviousTurn(curr_step); // if current step and next step are both segregated then combine the steps with no turn // adjustment if (curr_step->is_segregated && next_step->is_segregated) { // Combine segregated steps combineRouteSteps(*curr_step, *next_step, CombineSegregatedStepsStrategy(), TransferSignageStrategy(), TransferLanesStrategy()); ++next_step; } // else if the current step is segregated and the next step is not segregated // and the next step is not a roundabout then combine with turn adjustment else if (curr_step->is_segregated && !next_step->is_segregated && !hasRoundaboutType(curr_step->maneuver.instruction) && !hasRoundaboutType(next_step->maneuver.instruction)) { // Determine if u-turn if (bearingsAreReversed( util::bearing::reverse(curr_step->intersections.front() .bearings[curr_step->intersections.front().in]), next_step->intersections.front() .bearings[next_step->intersections.front().out])) { // Collapse segregated u-turn combineRouteSteps( *curr_step, *next_step, SetFixedInstructionStrategy({TurnType::Continue, DirectionModifier::UTurn}), TransferSignageStrategy(), NoModificationStrategy()); } else { // Collapse segregated turn combineRouteSteps(*curr_step, *next_step, SegregatedTurnStrategy(*prev_step), TransferSignageStrategy(), NoModificationStrategy()); } // Segregated step has been removed curr_step->is_segregated = false; ++next_step; } // else next step else { curr_step = next_step; ++next_step; } } // Clean up steps steps = removeNoTurnInstructions(std::move(steps)); return steps; } } // namespace guidance } // namespace engine } // namespace osrm