* Added post process logic to collapse segregated turn instructions * format updates * Fixed coordinates to reflect reality Updated left turn road name * fixed coordinates to fix test * Skip last step when processing segregated steps * updated segregated turn test * Updated segregated test * Updated test: Segregated Intersection, Cross Belonging to Correct Street - features/guidance/collapse.feature:79 * Fixed all but one for features/guidance/collapse.feature:124 * Fixed Scenario: Partly Segregated Intersection, Two Segregated Roads, Intersection belongs to Second - features/guidance/collapse.feature:219 * Fixed 7 of th 9 failures for Scenario: Partly Segregated Intersection, Two Segregated Roads, Intersection belongs to Second - features/guidance/collapse.feature:219 * Fixed 7 of the 9 failures for Scenario: Segregated Intersection, Cross Belonging to Mixed Streets - Slight Angles (2) - features/guidance/collapse.feature:318 * Fixed Scenario: Segregated Intersection into Slight Turn - features/guidance/collapse.feature:581 * Updated Scenario: U-turn after a traffic light - features/guidance/turn-lanes.feature:1220 * Updated how we combine segregated steps * Added test to Verify end of road left turn across divided roads * Fixed divided highwat tests * Fixed test failure * fixed Scenario: Partitioned turn, Slight Curve - maxspeed - features/guidance/turn-lanes.feature:936 * Fixed Scenario: Partitioned turn, Slight Curve - features/guidance/turn-lanes.feature:961 * Added strategies to combine segrgated intersections * Added setModifier alias for readability * Added strategies to combine segrgated intersections * Format updates * Fixes segregated indentification to not mark `circular` edge as segregated * Added intersection prior to turn so we still call out end of road * updated expectation to be turn instead of continue * Confirmed with @oxidase that the lane information if correct - updated the expectation * Added logic to handle wider straights Fixed tests * Update CHANGELOG.md Added #4925 * Removed TODO * Process straight step prior to wider straight step
424 lines
20 KiB
C++
424 lines
20 KiB
C++
#include "engine/guidance/collapse_scenario_detection.hpp"
|
|
#include "guidance/constants.hpp"
|
|
#include "util/bearing.hpp"
|
|
|
|
#include <numeric>
|
|
|
|
#include <boost/assert.hpp>
|
|
|
|
namespace osrm
|
|
{
|
|
namespace engine
|
|
{
|
|
namespace guidance
|
|
{
|
|
using namespace osrm::guidance;
|
|
|
|
namespace
|
|
{
|
|
|
|
// to collapse steps, we focus on short segments that don't interact with other roads. To collapse
|
|
// two instructions into one, we need to look at to instrutions immediately after each other.
|
|
bool noIntermediaryIntersections(const RouteStep &step)
|
|
{
|
|
return std::all_of(step.intersections.begin() + 1,
|
|
step.intersections.end(),
|
|
[](const auto &intersection) { return intersection.entry.size() == 2; });
|
|
}
|
|
|
|
// Link roads, as far as we are concerned, are short unnamed segments between to named segments.
|
|
bool isLinkRoad(const RouteStep &pre_link_step,
|
|
const RouteStep &link_step,
|
|
const RouteStep &post_link_step)
|
|
{
|
|
const constexpr double MAX_LINK_ROAD_LENGTH = 2 * MAX_COLLAPSE_DISTANCE;
|
|
const auto is_short = link_step.distance <= MAX_LINK_ROAD_LENGTH;
|
|
const auto unnamed = link_step.name.empty();
|
|
const auto between_named = !pre_link_step.name.empty() && !post_link_step.name.empty();
|
|
|
|
return is_short && unnamed && between_named && noIntermediaryIntersections(link_step);
|
|
}
|
|
|
|
// Just like a link step, but shorter and no name restrictions.
|
|
bool isShortAndUndisturbed(const RouteStep &step)
|
|
{
|
|
const auto is_short = step.distance <= MAX_COLLAPSE_DISTANCE;
|
|
return is_short && noIntermediaryIntersections(step);
|
|
}
|
|
|
|
// On dual carriageways, we might want to use u-turns in combination with new-name instructions.
|
|
// Otherwise a u-turn should never be part of a collapsing instructions.
|
|
bool noBadUTurnCombination(const RouteStepIterator first, const RouteStepIterator second)
|
|
{
|
|
auto has_uturn = hasModifier(*first, DirectionModifier::UTurn) ||
|
|
hasModifier(*second, DirectionModifier::UTurn);
|
|
|
|
auto const from_name_change_into_uturn =
|
|
hasTurnType(*first, TurnType::NewName) && hasModifier(*second, DirectionModifier::UTurn);
|
|
|
|
auto const uturn_into_name_change =
|
|
hasTurnType(*second, TurnType::NewName) && hasModifier(*first, DirectionModifier::UTurn);
|
|
|
|
return !has_uturn || from_name_change_into_uturn || uturn_into_name_change;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool basicCollapsePreconditions(const RouteStepIterator first, const RouteStepIterator second)
|
|
{
|
|
const auto has_roundabout_type = hasRoundaboutType(first->maneuver.instruction) ||
|
|
hasRoundaboutType(second->maneuver.instruction);
|
|
|
|
const auto waypoint_type = hasWaypointType(*first) || hasWaypointType(*second);
|
|
|
|
const auto contains_bad_uturn = !noBadUTurnCombination(first, second);
|
|
|
|
return !has_roundabout_type && !waypoint_type && haveSameMode(*first, *second) &&
|
|
!contains_bad_uturn;
|
|
}
|
|
|
|
bool basicCollapsePreconditions(const RouteStepIterator first,
|
|
const RouteStepIterator second,
|
|
const RouteStepIterator third)
|
|
{
|
|
const auto has_roundabout_type = hasRoundaboutType(first->maneuver.instruction) ||
|
|
hasRoundaboutType(second->maneuver.instruction) ||
|
|
hasRoundaboutType(third->maneuver.instruction);
|
|
|
|
const auto contains_bad_uturn =
|
|
!noBadUTurnCombination(first, second) && !noBadUTurnCombination(second, third);
|
|
|
|
// require modes to match up
|
|
return !has_roundabout_type && haveSameMode(*first, *second, *third) && !contains_bad_uturn;
|
|
}
|
|
|
|
bool isStaggeredIntersection(const RouteStepIterator step_prior_to_intersection,
|
|
const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
BOOST_ASSERT(!hasWaypointType(*step_entering_intersection) &&
|
|
!(hasWaypointType(*step_leaving_intersection)));
|
|
// don't touch roundabouts
|
|
if (entersRoundabout(step_entering_intersection->maneuver.instruction) ||
|
|
entersRoundabout(step_leaving_intersection->maneuver.instruction))
|
|
return false;
|
|
// Base decision on distance since the zig-zag is a visual clue.
|
|
// If adjusted, make sure to check validity of the is_right/is_left classification below
|
|
const constexpr auto MAX_STAGGERED_DISTANCE = 3; // debatable, but keep short to be on safe side
|
|
|
|
const auto angle = [](const RouteStep &step) {
|
|
const auto &intersection = step.intersections.front();
|
|
const auto entry_bearing = util::bearing::reverse(intersection.bearings[intersection.in]);
|
|
const auto exit_bearing = intersection.bearings[intersection.out];
|
|
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.
|
|
// We do not want to trigger e.g. on sharp uturn'ish turns or going straight "turns".
|
|
// Therefore we use the turn angle to derive 90 degree'ish right / left turns.
|
|
// This more closely resembles what we understand as Staggered Intersection.
|
|
// We have to be careful in cases with larger MAX_STAGGERED_DISTANCE values. If the distance
|
|
// gets large, sharper angles might be not obvious enough to consider them a staggered
|
|
// intersection. We might need to consider making the decision here dependent on the actual turn
|
|
// angle taken. To do so, we could scale the angle-limits by a factor depending on the distance
|
|
// between the turns.
|
|
const auto is_right = [](const double angle) { return angle > 45 && angle < 135; };
|
|
const auto is_left = [](const double angle) { return angle > 225 && angle < 315; };
|
|
|
|
const auto left_right =
|
|
is_left(angle(*step_entering_intersection)) && is_right(angle(*step_leaving_intersection));
|
|
const auto right_left =
|
|
is_right(angle(*step_entering_intersection)) && is_left(angle(*step_leaving_intersection));
|
|
|
|
// A RouteStep holds distance/duration from the maneuver to the subsequent step.
|
|
// We are only interested in the distance between the first and the second.
|
|
const auto is_short = step_entering_intersection->distance < MAX_STAGGERED_DISTANCE;
|
|
const auto intermediary_mode_change =
|
|
step_prior_to_intersection->mode == step_leaving_intersection->mode &&
|
|
step_entering_intersection->mode != step_leaving_intersection->mode;
|
|
|
|
const auto mode_change_when_entering =
|
|
step_prior_to_intersection->mode != step_entering_intersection->mode;
|
|
|
|
// previous step maneuver intersections should be length 1 to indicate that
|
|
// there are no intersections between the two potentially collapsible turns
|
|
return is_short && (left_right || right_left) && !intermediary_mode_change &&
|
|
!mode_change_when_entering && noIntermediaryIntersections(*step_entering_intersection);
|
|
}
|
|
|
|
bool isUTurn(const RouteStepIterator step_prior_to_intersection,
|
|
const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(
|
|
step_prior_to_intersection, step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
// uturns only allowed on turns
|
|
if (!hasTurnType(*step_entering_intersection, TurnType::Turn) &&
|
|
!hasTurnType(*step_entering_intersection, TurnType::Continue) &&
|
|
!hasTurnType(*step_entering_intersection, TurnType::EndOfRoad))
|
|
return false;
|
|
|
|
// the most basic condition for a uturn is that we actually turn around
|
|
const bool takes_u_turn = bearingsAreReversed(
|
|
util::bearing::reverse(step_entering_intersection->intersections.front()
|
|
.bearings[step_entering_intersection->intersections.front().in]),
|
|
step_leaving_intersection->intersections.front()
|
|
.bearings[step_leaving_intersection->intersections.front().out]);
|
|
|
|
if (!takes_u_turn)
|
|
return false;
|
|
|
|
// TODO check for name match after additional step
|
|
const auto names_match = haveSameName(*step_prior_to_intersection, *step_leaving_intersection);
|
|
|
|
// names within a u-turn road have to match from entry step to exit step
|
|
if (!names_match)
|
|
return false;
|
|
|
|
const auto collapsable = isShortAndUndisturbed(*step_entering_intersection);
|
|
const auto only_allowed_turn = (numberOfAllowedTurns(*step_leaving_intersection) == 1) &&
|
|
noIntermediaryIntersections(*step_entering_intersection);
|
|
|
|
return collapsable || isLinkRoad(*step_prior_to_intersection,
|
|
*step_entering_intersection,
|
|
*step_leaving_intersection) ||
|
|
only_allowed_turn;
|
|
}
|
|
|
|
bool isNameOszillation(const RouteStepIterator step_prior_to_intersection,
|
|
const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(
|
|
step_prior_to_intersection, step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto are_name_changes =
|
|
(hasTurnType(*step_entering_intersection, TurnType::NewName) ||
|
|
(hasTurnType(*step_entering_intersection, TurnType::Turn) &&
|
|
hasModifier(*step_entering_intersection, DirectionModifier::Straight))) &&
|
|
(hasTurnType(*step_leaving_intersection, TurnType::NewName) ||
|
|
(hasTurnType(*step_leaving_intersection, TurnType::Suppressed) &&
|
|
step_leaving_intersection->name_id == EMPTY_NAMEID) ||
|
|
(hasTurnType(*step_leaving_intersection, TurnType::Turn) &&
|
|
hasModifier(*step_leaving_intersection, DirectionModifier::Straight)));
|
|
|
|
if (!are_name_changes)
|
|
return false;
|
|
|
|
const auto names_match =
|
|
// accept empty names as well as same names
|
|
step_prior_to_intersection->name_id == step_leaving_intersection->name_id ||
|
|
haveSameName(*step_prior_to_intersection, *step_leaving_intersection);
|
|
return names_match;
|
|
}
|
|
|
|
bool maneuverPreceededByNameChange(const RouteStepIterator step_prior_to_intersection,
|
|
const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(
|
|
step_prior_to_intersection, step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection);
|
|
const auto is_name_change =
|
|
hasTurnType(*step_entering_intersection, TurnType::NewName) ||
|
|
((hasTurnType(*step_entering_intersection, TurnType::Turn) ||
|
|
hasTurnType(*step_entering_intersection, TurnType::Continue)) &&
|
|
hasModifier(*step_entering_intersection, DirectionModifier::Straight));
|
|
|
|
const auto followed_by_maneuver =
|
|
hasTurnType(*step_leaving_intersection) &&
|
|
!hasTurnType(*step_leaving_intersection, TurnType::Suppressed);
|
|
|
|
return short_and_undisturbed && is_name_change && followed_by_maneuver;
|
|
}
|
|
|
|
bool maneuverPreceededBySuppressedDirection(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection);
|
|
|
|
const auto is_suppressed_direction =
|
|
hasTurnType(*step_entering_intersection, TurnType::Suppressed) &&
|
|
!hasModifier(*step_entering_intersection, DirectionModifier::Straight);
|
|
|
|
const auto followed_by_maneuver =
|
|
hasTurnType(*step_leaving_intersection) &&
|
|
!hasTurnType(*step_leaving_intersection, TurnType::Suppressed);
|
|
|
|
const auto keeps_direction =
|
|
areSameSide(*step_entering_intersection, *step_leaving_intersection);
|
|
|
|
const auto has_choice = numberOfAllowedTurns(*step_entering_intersection) > 1;
|
|
|
|
return short_and_undisturbed && has_choice && is_suppressed_direction && followed_by_maneuver &&
|
|
keeps_direction;
|
|
}
|
|
|
|
bool suppressedStraightBetweenTurns(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_at_center_of_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(
|
|
step_entering_intersection, step_at_center_of_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto both_short_enough =
|
|
step_entering_intersection->distance < 0.8 * MAX_COLLAPSE_DISTANCE &&
|
|
step_at_center_of_intersection->distance < 0.8 * MAX_COLLAPSE_DISTANCE;
|
|
|
|
const auto similar_length =
|
|
(step_entering_intersection->distance < 5 &&
|
|
step_at_center_of_intersection->distance < 5) ||
|
|
std::min(step_entering_intersection->distance, step_at_center_of_intersection->distance) /
|
|
std::max(step_entering_intersection->distance,
|
|
step_at_center_of_intersection->distance) >
|
|
0.75;
|
|
|
|
const auto correct_types =
|
|
hasTurnType(*step_at_center_of_intersection, TurnType::Suppressed) &&
|
|
hasModifier(*step_at_center_of_intersection, DirectionModifier::Straight) &&
|
|
(hasTurnType(*step_entering_intersection, TurnType::Turn) ||
|
|
hasTurnType(*step_entering_intersection, TurnType::Continue)) &&
|
|
(hasTurnType(*step_leaving_intersection, TurnType::Turn) ||
|
|
hasTurnType(*step_leaving_intersection, TurnType::Continue) ||
|
|
hasTurnType(*step_leaving_intersection, TurnType::OnRamp));
|
|
|
|
const auto total_angle =
|
|
totalTurnAngle(*step_entering_intersection, *step_leaving_intersection);
|
|
const auto total_angle_is_not_uturn =
|
|
(total_angle > NARROW_TURN_ANGLE) && (total_angle < 360 - NARROW_TURN_ANGLE);
|
|
|
|
return both_short_enough && similar_length && correct_types && total_angle_is_not_uturn;
|
|
}
|
|
|
|
bool maneuverSucceededByNameChange(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection);
|
|
const auto followed_by_name_change =
|
|
hasTurnType(*step_leaving_intersection, TurnType::NewName) ||
|
|
((hasTurnType(*step_leaving_intersection, TurnType::Turn) ||
|
|
hasTurnType(*step_leaving_intersection, TurnType::Continue)) &&
|
|
hasModifier(*step_leaving_intersection, DirectionModifier::Straight));
|
|
|
|
const auto is_maneuver = hasTurnType(*step_entering_intersection) &&
|
|
!hasTurnType(*step_entering_intersection, TurnType::Suppressed);
|
|
|
|
// a straight name change can overrule max collapse distance
|
|
const auto is_strong_name_change =
|
|
hasTurnType(*step_leaving_intersection, TurnType::NewName) &&
|
|
hasModifier(*step_leaving_intersection, DirectionModifier::Straight) &&
|
|
step_entering_intersection->distance <= 1.5 * MAX_COLLAPSE_DISTANCE;
|
|
|
|
// also allow a bit more, if the new name is without choice
|
|
const auto is_choiceless_name_change =
|
|
hasTurnType(*step_leaving_intersection, TurnType::NewName) &&
|
|
numberOfAllowedTurns(*step_leaving_intersection) == 1 &&
|
|
step_entering_intersection->distance <= 1.5 * MAX_COLLAPSE_DISTANCE;
|
|
|
|
return (short_and_undisturbed || is_strong_name_change || is_choiceless_name_change) &&
|
|
followed_by_name_change && is_maneuver;
|
|
}
|
|
|
|
bool maneuverSucceededBySuppressedDirection(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection);
|
|
|
|
const auto followed_by_suppressed_direction =
|
|
hasTurnType(*step_leaving_intersection, TurnType::Suppressed) &&
|
|
!hasModifier(*step_leaving_intersection, DirectionModifier::Straight);
|
|
|
|
const auto is_maneuver = hasTurnType(*step_entering_intersection) &&
|
|
!hasTurnType(*step_entering_intersection, TurnType::Suppressed);
|
|
|
|
const auto keeps_direction =
|
|
areSameSide(*step_entering_intersection, *step_leaving_intersection);
|
|
|
|
return short_and_undisturbed && followed_by_suppressed_direction && is_maneuver &&
|
|
keeps_direction;
|
|
}
|
|
|
|
bool nameChangeImmediatelyAfterSuppressed(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto very_short = step_entering_intersection->distance < 0.25 * MAX_COLLAPSE_DISTANCE;
|
|
const auto correct_types = hasTurnType(*step_entering_intersection, TurnType::Suppressed) &&
|
|
hasTurnType(*step_leaving_intersection, TurnType::NewName);
|
|
|
|
return very_short && correct_types;
|
|
}
|
|
|
|
bool closeChoicelessTurnAfterTurn(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto short_and_undisturbed = isShortAndUndisturbed(*step_entering_intersection);
|
|
const auto is_turn = !hasModifier(*step_entering_intersection, DirectionModifier::Straight);
|
|
|
|
const auto followed_by_choiceless = numberOfAllowedTurns(*step_leaving_intersection) == 1;
|
|
const auto followed_by_suppressed =
|
|
hasTurnType(*step_leaving_intersection, TurnType::Suppressed);
|
|
|
|
return short_and_undisturbed && is_turn && followed_by_choiceless && !followed_by_suppressed;
|
|
}
|
|
|
|
bool doubleChoiceless(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto double_choiceless =
|
|
(numberOfAllowedTurns(*step_leaving_intersection) == 1) &&
|
|
(step_entering_intersection->intersections.size() == 2) &&
|
|
(std::count(step_entering_intersection->intersections.back().entry.begin(),
|
|
step_entering_intersection->intersections.back().entry.end(),
|
|
true) == 1);
|
|
|
|
const auto short_enough = step_entering_intersection->distance < 1.5 * MAX_COLLAPSE_DISTANCE;
|
|
|
|
return double_choiceless && short_enough;
|
|
}
|
|
|
|
bool straightTurnFollowedByChoiceless(const RouteStepIterator step_entering_intersection,
|
|
const RouteStepIterator step_leaving_intersection)
|
|
{
|
|
if (!basicCollapsePreconditions(step_entering_intersection, step_leaving_intersection))
|
|
return false;
|
|
|
|
const auto is_short = step_entering_intersection->distance <= 2 * MAX_COLLAPSE_DISTANCE;
|
|
const auto has_correct_type = hasTurnType(*step_entering_intersection, TurnType::Continue) ||
|
|
hasTurnType(*step_entering_intersection, TurnType::Turn);
|
|
|
|
const auto is_straight = hasModifier(*step_entering_intersection, DirectionModifier::Straight);
|
|
|
|
const auto only_choice = numberOfAllowedTurns(*step_leaving_intersection) == 1;
|
|
|
|
return is_short && has_correct_type && is_straight && only_choice &&
|
|
noIntermediaryIntersections(*step_entering_intersection);
|
|
}
|
|
|
|
} /* namespace guidance */
|
|
} /* namespace engine */
|
|
} /* namespace osrm */
|