Improves lane handling for subsequent going straight, resolves #2625

Before we only worked on subsequent quick turns, as in:
`right, right` keeps the user on the rightmost lanes.

This changeset modifies the logic to work on any subsequent steps
that are "quick" and have lane information we can constrain later.

Because we do not have a from-lane => to-lanes mapping we take the
lanes left and right of the turn lanes into account when heuristically
assigning the leftmost / rightmost lanes.

There are some edge cases where this still does not give us the optimal
solution but it gets close to what is actually possible at the moment
without having a lane mapping in post-processing.

References:
- https://github.com/Project-OSRM/osrm-backend/issues/2625

fix rebase
This commit is contained in:
Daniel J. Hofmann
2016-07-08 12:39:29 +02:00
committed by Moritz Kobitzsch
parent b6dbf81206
commit 86fd04e556
7 changed files with 391 additions and 112 deletions
+117 -37
View File
@@ -4,8 +4,11 @@
#include "extractor/guidance/turn_instruction.hpp"
#include "engine/guidance/toolkit.hpp"
#include "engine/guidance/post_processing.hpp"
#include <iterator>
#include <unordered_set>
#include <utility>
using TurnInstruction = osrm::extractor::guidance::TurnInstruction;
namespace TurnType = osrm::extractor::guidance::TurnType;
@@ -24,47 +27,45 @@ namespace guidance
std::vector<RouteStep> anticipateLaneChange(std::vector<RouteStep> steps,
const double min_duration_needed_for_lane_change)
{
// Postprocessing does not strictly guarantee for only turns
const auto is_turn = [](const RouteStep &step) {
return step.maneuver.instruction.type != TurnType::NewName &&
step.maneuver.instruction.type != TurnType::Notification;
// Lane anticipation works on contiguous ranges of quick steps that have lane information
const auto is_quick_has_lanes = [&](const RouteStep &step) {
const auto is_quick = step.duration < min_duration_needed_for_lane_change;
const auto has_lanes = step.intersections.front().lanes.lanes_in_turn > 0;
return has_lanes && is_quick;
};
const auto is_quick = [min_duration_needed_for_lane_change](const RouteStep &step) {
return step.duration < min_duration_needed_for_lane_change;
};
const auto is_quick_turn = [&](const RouteStep &step) {
return is_turn(step) && is_quick(step);
};
// Determine range of subsequent quick turns, candidates for possible lane anticipation
using StepIter = decltype(steps)::iterator;
using StepIterRange = std::pair<StepIter, StepIter>;
std::vector<StepIterRange> subsequent_quick_turns;
std::vector<StepIterRange> quick_lanes_ranges;
const auto keep_turn_range = [&](StepIterRange range) {
const auto range_back_inserter = [&](StepIterRange range) {
if (std::distance(range.first, range.second) > 1)
subsequent_quick_turns.push_back(std::move(range));
quick_lanes_ranges.push_back(std::move(range));
};
util::group_by(begin(steps), end(steps), is_quick_turn, keep_turn_range);
util::group_by(begin(steps), end(steps), is_quick_has_lanes, range_back_inserter);
// The lanes for a keep straight depend on the next left/right turn. Tag them in advance.
std::unordered_set<const RouteStep *> is_straight_left;
std::unordered_set<const RouteStep *> is_straight_right;
// Walk backwards over all turns, constraining possible turn lanes.
// Later turn lanes constrain earlier ones: we have to anticipate lane changes.
const auto constrain_lanes = [](const StepIterRange &turns) {
const auto constrain_lanes = [&](const StepIterRange &turns) {
const std::reverse_iterator<StepIter> rev_first{turns.second};
const std::reverse_iterator<StepIter> rev_last{turns.first};
// We're walking backwards over all adjacent turns:
// the current turn lanes constrain the lanes we have to take in the previous turn.
util::for_each_pair(rev_first, rev_last, [](RouteStep &current, RouteStep &previous) {
util::for_each_pair(rev_first, rev_last, [&](RouteStep &current, RouteStep &previous) {
const auto current_inst = current.maneuver.instruction;
const auto current_lanes = current.intersections.front().lanes;
// Constrain the previous turn's lanes
auto &previous_lanes = previous.intersections.front().lanes;
const auto previous_inst = previous.maneuver.instruction;
// Lane mapping (N:M) from previous lanes (N) to current lanes (M), with:
// N > M, N > 1 fan-in situation, constrain N lanes to min(N,M) shared lanes
// otherwise nothing to constrain
@@ -74,34 +75,113 @@ std::vector<RouteStep> anticipateLaneChange(std::vector<RouteStep> steps,
if (!lanes_to_constrain || !lanes_fan_in)
return;
// In case there is no lane information we work with one artificial lane
const auto current_adjusted_lanes = std::max(current_lanes.lanes_in_turn, LaneID{1});
// We do not have a mapping from lanes to lanes. All we have is the lanes in the turn
// and all the lanes at that situation. To perfectly handle lane anticipation in cases
// where lanes in the turn fan in but for example the overall lanes at that location
// fan out, we would have to know the asymmetric mapping of lanes. This is currently
// not possible at the moment. In the following we implement a heuristic instead.
const LaneID current_num_all_lanes = current.intersections.front().lane_description.size();
const LaneID current_num_lanes_right_of_turn = current_lanes.first_lane_from_the_right;
const LaneID current_num_lanes_left_of_turn =
current_num_all_lanes -
(current_lanes.lanes_in_turn + current_num_lanes_right_of_turn);
const auto num_shared_lanes = std::min(current_adjusted_lanes, //
previous_lanes.lanes_in_turn);
const LaneID num_shared_lanes = std::min(current_lanes.lanes_in_turn, //
previous_lanes.lanes_in_turn); //
if (isRightTurn(current_inst))
// 0/ Tag keep straight with the next turn's direction if available
const auto previous_is_straight =
!isLeftTurn(previous_inst) && !isRightTurn(previous_inst);
if (previous_is_straight)
{
if (isLeftTurn(current_inst) || is_straight_left.count(&current) > 0)
is_straight_left.insert(&previous);
else if (isRightTurn(current_inst) || is_straight_right.count(&current) > 0)
is_straight_right.insert(&previous);
}
// 1/ How to anticipate left, right:
const auto anticipate_for_left_turn = [&] {
// Current turn is left turn, already keep left during previous turn.
// This implies constraining the rightmost lanes in previous step.
LaneID new_first_lane_from_the_right =
previous_lanes.first_lane_from_the_right // start from rightmost lane
+ previous_lanes.lanes_in_turn // one past leftmost lane
- num_shared_lanes; // back number of new lanes
// The leftmost target lanes might not be involved in the turn. Figure out
// how many lanes are to the left and not in the turn.
new_first_lane_from_the_right -=
std::min(current_num_lanes_left_of_turn, num_shared_lanes);
previous_lanes = {num_shared_lanes, new_first_lane_from_the_right};
};
const auto anticipate_for_right_turn = [&] {
// Current turn is right turn, already keep right during the previous turn.
// This implies constraining the leftmost lanes in the previous turn step.
previous_lanes = {num_shared_lanes, previous_lanes.first_lane_from_the_right};
}
else if (isLeftTurn(current_inst))
{
// Current turn is left turn, already keep left during previous turn.
// This implies constraining the rightmost lanes in the previous turn step.
const LaneID shared_lane_delta = previous_lanes.lanes_in_turn - num_shared_lanes;
const LaneID previous_adjusted_lanes =
std::min(current_adjusted_lanes, shared_lane_delta);
const LaneID constraint_first_lane_from_the_right =
previous_lanes.first_lane_from_the_right + previous_adjusted_lanes;
LaneID new_first_lane_from_the_right = previous_lanes.first_lane_from_the_right;
previous_lanes = {num_shared_lanes, constraint_first_lane_from_the_right};
// The rightmost target lanes might not be involved in the turn. Figure out
// how many lanes are to the right and not in the turn.
new_first_lane_from_the_right +=
std::min(current_num_lanes_right_of_turn, num_shared_lanes);
previous_lanes = {num_shared_lanes, new_first_lane_from_the_right};
};
// 2/ When to anticipate a left, right turn
if (isLeftTurn(current_inst))
anticipate_for_left_turn();
else if (isRightTurn(current_inst))
anticipate_for_right_turn();
else // keepStraight
{
// Heuristic: we do not have a from-lanes -> to-lanes mapping. What we use
// here instead in addition is the number of all lanes (not only the lanes
// in a turn):
//
// -v-v v-v- straight follows
// | | | |
// <- v v -> keep straight here
// | |
// <-| |->
//
// A route from the top left to the bottom right here goes over a keep
// straight. If we handle all keep straights as right turns (in right-sided
// driving), we wrongly guide the user to the rightmost lanes in the first turn.
// Not only is this wrong but the opposite of what we expect.
//
// The following implements a heuristic to determine a keep straight's
// direction in relation to the next step. In the above example we would get:
//
// coming from right, going to left (in direction of way) -> handle as left turn
if (is_straight_left.count(&current) > 0)
anticipate_for_left_turn();
else if (is_straight_right.count(&current) > 0)
anticipate_for_right_turn();
else // FIXME: right-sided driving
anticipate_for_right_turn();
}
// We might have constrained the previous step in a way that makes it compatible
// with the current step. If we did so we collapse it here and mark the current
// step as invalid, scheduled for later removal.
if (collapsable(previous, current))
{
previous = elongate(previous, current);
current.maneuver.instruction = TurnInstruction::NO_TURN();
}
});
};
std::for_each(begin(subsequent_quick_turns), end(subsequent_quick_turns), constrain_lanes);
std::for_each(begin(quick_lanes_ranges), end(quick_lanes_ranges), constrain_lanes);
// Lane Anticipation might have collapsed steps after constraining lanes. Remove invalid steps.
steps = removeNoTurnInstructions(std::move(steps));
return steps;
}
+60 -57
View File
@@ -69,27 +69,6 @@ bool isCollapsableInstruction(const TurnInstruction instruction)
(instruction.type == TurnType::Merge);
}
// 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. Lookahead of one step.
bool collapsable(const RouteStep &step, const RouteStep &next)
{
const auto is_short_step = step.distance < MAX_COLLAPSE_DISTANCE;
const auto instruction_can_be_collapsed = isCollapsableInstruction(step.maneuver.instruction);
const auto is_use_lane = step.maneuver.instruction.type == TurnType::UseLane;
const auto lanes_dont_change =
step.intersections.front().lanes == next.intersections.front().lanes;
if (is_short_step && instruction_can_be_collapsed)
return true;
// Prevent collapsing away important lane change steps
if (is_short_step && is_use_lane && lanes_dont_change)
return true;
return false;
}
bool compatible(const RouteStep &lhs, const RouteStep &rhs) { return lhs.mode == rhs.mode; }
double nameSegmentLength(std::size_t at, const std::vector<RouteStep> &steps)
@@ -336,40 +315,6 @@ void closeOffRoundabout(const bool on_roundabout,
}
}
// elongate a step by another. the data is added either at the front, or the back
OSRM_ATTR_WARN_UNUSED
RouteStep elongate(RouteStep step, const RouteStep &by_step)
{
step.duration += by_step.duration;
step.distance += by_step.distance;
// by_step comes after step -> we append at the end
if (step.geometry_end == by_step.geometry_begin + 1)
{
step.geometry_end = by_step.geometry_end;
// if we elongate in the back, we only need to copy the intersections to the beginning.
// the bearings remain the same, as the location of the turn doesn't change
step.intersections.insert(
step.intersections.end(), by_step.intersections.begin(), by_step.intersections.end());
}
// by_step comes before step -> we append at the front
else
{
BOOST_ASSERT(step.maneuver.waypoint_type == WaypointType::None &&
by_step.maneuver.waypoint_type == WaypointType::None);
BOOST_ASSERT(by_step.geometry_end == step.geometry_begin + 1);
step.geometry_begin = by_step.geometry_begin;
// elongating in the front changes the location of the maneuver
step.maneuver = by_step.maneuver;
step.intersections.insert(
step.intersections.begin(), by_step.intersections.begin(), by_step.intersections.end());
}
return step;
}
void collapseTurnAt(std::vector<RouteStep> &steps,
const std::size_t two_back_index,
const std::size_t one_back_index,
@@ -571,8 +516,66 @@ bool isStaggeredIntersection(const RouteStep &previous, const RouteStep &current
} // namespace
// elongate a step by another. the data is added either at the front, or the back
OSRM_ATTR_WARN_UNUSED
RouteStep elongate(RouteStep step, const RouteStep &by_step)
{
step.duration += by_step.duration;
step.distance += by_step.distance;
// by_step comes after step -> we append at the end
if (step.geometry_end == by_step.geometry_begin + 1)
{
step.geometry_end = by_step.geometry_end;
// if we elongate in the back, we only need to copy the intersections to the beginning.
// the bearings remain the same, as the location of the turn doesn't change
step.intersections.insert(
step.intersections.end(), by_step.intersections.begin(), by_step.intersections.end());
}
// by_step comes before step -> we append at the front
else
{
BOOST_ASSERT(step.maneuver.waypoint_type == WaypointType::None &&
by_step.maneuver.waypoint_type == WaypointType::None);
BOOST_ASSERT(by_step.geometry_end == step.geometry_begin + 1);
step.geometry_begin = by_step.geometry_begin;
// elongating in the front changes the location of the maneuver
step.maneuver = by_step.maneuver;
step.intersections.insert(
step.intersections.begin(), by_step.intersections.begin(), by_step.intersections.end());
}
return step;
}
// Post processing can invalidate some instructions. For example StayOnRoundabout
// is turned into exit counts. These instructions are removed by the following function
// 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. Lookahead of one step.
bool collapsable(const RouteStep &step, const RouteStep &next)
{
const auto is_short_step = step.distance < MAX_COLLAPSE_DISTANCE;
const auto instruction_can_be_collapsed = isCollapsableInstruction(step.maneuver.instruction);
const auto is_use_lane = step.maneuver.instruction.type == TurnType::UseLane;
const auto lanes_dont_change =
step.intersections.front().lanes == next.intersections.front().lanes;
if (is_short_step && instruction_can_be_collapsed)
return true;
// Prevent collapsing away important lane change steps
if (is_short_step && is_use_lane && lanes_dont_change)
return true;
return false;
}
std::vector<RouteStep> removeNoTurnInstructions(std::vector<RouteStep> steps)
{
// finally clean up the post-processed instructions.
@@ -1199,7 +1202,7 @@ std::vector<RouteStep> buildIntersections(std::vector<RouteStep> steps)
{
// count intersections. We cannot use exit, since intersections can follow directly
// after a roundabout
steps[last_valid_instruction] = elongate(steps[last_valid_instruction], step);
steps[last_valid_instruction] = elongate(std::move(steps[last_valid_instruction]), step);
step.maneuver.instruction = TurnInstruction::NO_TURN();
}
else if (!isSilent(instruction))
@@ -1273,7 +1276,7 @@ std::vector<RouteStep> collapseUseLane(std::vector<RouteStep> steps)
step.intersections.front().lane_description))
{
const auto previous = getPreviousIndex(step_index);
steps[previous] = elongate(steps[previous], steps[step_index]);
steps[previous] = elongate(std::move(steps[previous]), steps[step_index]);
// elongate(steps[step_index-1], steps[step_index]);
invalidateStep(steps[step_index]);
}