use enter + exit for roundabout instructions (#4358)

* Expose roundabout/rotary exit instructions as a new instruction type.
This commit is contained in:
Moritz Kobitzsch
2017-09-05 21:30:34 +02:00
committed by Daniel Patterson
parent 0fc1aa2711
commit c2dc7e9cd0
24 changed files with 564 additions and 1187 deletions
+10 -10
View File
@@ -66,20 +66,20 @@ const constexpr TurnTypeName turn_type_names[] = {
{"end of road", "end of road"},
{"notification", "notification"},
{"roundabout", "enter roundabout"},
{"roundabout", "enter and exit roundabout"},
{"exit roundabout", "enter and exit roundabout"},
{"rotary", "enter rotary"},
{"rotary", "enter and exit rotary"},
{"exit rotary", "enter and exit rotary"},
{"roundabout turn", "enter roundabout turn"},
{"roundabout turn", "enter and exit roundabout turn"},
{"use lane", "use lane"},
{"invalid", "(noturn)"},
{"invalid", "(suppressed)"},
{"invalid", "(enter roundabout at exit)"},
{"invalid", "(exit roundabout)"},
{"invalid", "(enter rotary at exit)"},
{"invalid", "(exit rotary)"},
{"invalid", "(enter roundabout intersection at exit)"},
{"invalid", "(exit roundabout intersection)"},
{"roundabout", "roundabout"},
{"exit roundabout", "exit roundabout"},
{"rotary", "rotary"},
{"exit rotary", "exit rotary"},
{"roundabout turn", "roundabout turn"},
{"exit roundabout", "exit roundabout turn"},
{"invalid", "(stay on roundabout)"},
{"invalid", "(sliproad)"}};
@@ -100,14 +100,14 @@ inline bool hasValidLanes(const guidance::IntermediateIntersection &intersection
std::string instructionTypeToString(const TurnType::Enum type)
{
static_assert(sizeof(turn_type_names) / sizeof(turn_type_names[0]) >= TurnType::MaxTurnType,
"Some turn types has not string representation.");
"Some turn types have no string representation.");
return turn_type_names[static_cast<std::size_t>(type)].external_name;
}
std::string internalInstructionTypeToString(const TurnType::Enum type)
{
static_assert(sizeof(turn_type_names) / sizeof(turn_type_names[0]) >= TurnType::MaxTurnType,
"Some turn types has not string representation.");
"Some turn types have no string representation.");
return turn_type_names[static_cast<std::size_t>(type)].internal_name;
}
+145 -227
View File
@@ -7,6 +7,7 @@
#include "engine/guidance/collapsing_utility.hpp"
#include "util/bearing.hpp"
#include "util/group_by.hpp"
#include "util/guidance/name_announcements.hpp"
#include "util/guidance/turn_lanes.hpp"
@@ -14,6 +15,8 @@
#include <boost/numeric/conversion/cast.hpp>
#include <boost/range/iterator_range.hpp>
#include "engine/guidance/collapsing_utility.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
@@ -26,6 +29,8 @@ using osrm::extractor::guidance::hasRampType;
using osrm::extractor::guidance::mirrorDirectionModifier;
using osrm::extractor::guidance::bearingToDirectionModifier;
using RouteStepIterator = std::vector<osrm::engine::guidance::RouteStep>::iterator;
namespace osrm
{
namespace engine
@@ -36,208 +41,137 @@ namespace guidance
namespace
{
void fixFinalRoundabout(std::vector<RouteStep> &steps)
// Ensure that after we are done with the roundabout, only the roundabout instructions themselves
// remain
void compressRange(const RouteStepIterator begin, const RouteStepIterator end)
{
for (std::size_t propagation_index = steps.size() - 1; propagation_index > 0;
--propagation_index)
{
auto &propagation_step = steps[propagation_index];
propagation_step.maneuver.exit = 0;
if (entersRoundabout(propagation_step.maneuver.instruction))
{
// remember the current name as rotary name in tha case we end in a rotary
if (propagation_step.maneuver.instruction.type == TurnType::EnterRotary ||
propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit)
{
propagation_step.rotary_name = propagation_step.name;
propagation_step.rotary_pronunciation = propagation_step.pronunciation;
}
else if (propagation_step.maneuver.instruction.type ==
TurnType::EnterRoundaboutIntersection ||
propagation_step.maneuver.instruction.type ==
TurnType::EnterRoundaboutIntersectionAtExit)
{
propagation_step.maneuver.instruction.type = TurnType::EnterRoundabout;
}
if (begin == end)
return;
return;
}
// accumulate turn data into the enter instructions
else if (propagation_step.maneuver.instruction.type == TurnType::StayOnRoundabout)
for (auto itr = begin + 1; itr != end; ++itr)
{
// ensure not to invalidate the final arrive
if (!hasWaypointType(*itr))
{
// TODO this operates on the data that is in the instructions.
// We are missing out on the final segment after the last stay-on-roundabout
// instruction though. it is not contained somewhere until now
steps[propagation_index - 1].ElongateBy(propagation_step);
steps[propagation_index - 1].maneuver.exit = propagation_step.maneuver.exit;
propagation_step.Invalidate();
begin->ElongateBy(*itr);
itr->Invalidate();
}
}
}
bool setUpRoundabout(RouteStep &step)
// this function handles a single roundabout between enter (which might be missing) to exit (which
// might be missing as well)
void processRoundaboutExits(const RouteStepIterator begin, const RouteStepIterator end)
{
// basic entry into a roundabout
// Special case handling, if an entry is directly tied to an exit
const auto instruction = step.maneuver.instruction;
if (instruction.type == TurnType::EnterRotaryAtExit ||
instruction.type == TurnType::EnterRoundaboutAtExit ||
instruction.type == TurnType::EnterRoundaboutIntersectionAtExit)
auto const last = end - 1;
// If we do not exit the roundabout, there is no exit to report. All good here
if (!leavesRoundabout(last->maneuver.instruction))
{
// Here we consider an actual entry, not an exit. We simply have to count the additional
// exit
step.maneuver.exit = 1;
// prevent futher special case handling of these two.
if (instruction.type == TurnType::EnterRotaryAtExit)
step.maneuver.instruction.type = TurnType::EnterRotary;
else if (instruction.type == TurnType::EnterRoundaboutAtExit)
step.maneuver.instruction.type = TurnType::EnterRoundabout;
else
step.maneuver.instruction.type = TurnType::EnterRoundaboutIntersection;
// first we do some clean-up
if (begin->maneuver.instruction.type == TurnType::EnterRotary ||
begin->maneuver.instruction.type == TurnType::EnterRotaryAtExit)
{
begin->rotary_name = begin->name;
begin->rotary_pronunciation = begin->pronunciation;
}
// roundabout turns don't make sense without an exit, update the type
else if (entersRoundabout(begin->maneuver.instruction) &&
(begin->maneuver.instruction.type == TurnType::EnterRoundaboutIntersection ||
begin->maneuver.instruction.type == TurnType::EnterRoundaboutIntersectionAtExit))
{
begin->maneuver.instruction.type = TurnType::EnterRoundabout;
}
// We are doing a roundtrip on the roundabout, Nothing to do here but to remove the
// instructions
compressRange(begin, end);
return;
}
if (leavesRoundabout(instruction))
{
// This set-up, even though it looks the same, is actually looking at entering AND exiting
step.maneuver.exit = 1; // count the otherwise missing exit
const auto passes_exit_or_leaves_roundabout = [](auto const &step) {
return staysOnRoundabout(step.maneuver.instruction) ||
leavesRoundabout(step.maneuver.instruction);
};
// prevent futher special case handling of these two.
if (instruction.type == TurnType::EnterAndExitRotary)
step.maneuver.instruction.type = TurnType::EnterRotary;
else if (instruction.type == TurnType::EnterAndExitRoundabout)
step.maneuver.instruction.type = TurnType::EnterRoundabout;
else
step.maneuver.instruction.type = TurnType::EnterRoundaboutIntersection;
return false;
// exit count
const auto exit = std::count_if(begin, end, passes_exit_or_leaves_roundabout);
// removes all intermediate instructions, assigns names and exit numbers
BOOST_ASSERT(leavesRoundabout(last->maneuver.instruction));
BOOST_ASSERT(std::distance(begin, end) >= 1);
last->maneuver.exit = exit;
// when we actually have an enter instruction, we can store all the information on it that we
// need, otherwise we only provide the exit instruciton. In case of re-routing on the
// roundabout, this might result in strange behaviour, but this way we are more resiliant and we
// do provide exit after all
if (entersRoundabout(begin->maneuver.instruction))
{
begin->maneuver.exit = exit;
// special handling for rotaries: remember the name (legacy feature, due to
// adapt-step-signage)
if (begin->maneuver.instruction.type == TurnType::EnterRotary ||
begin->maneuver.instruction.type == TurnType::EnterRotaryAtExit)
{
begin->rotary_name = begin->name;
begin->rotary_pronunciation = begin->pronunciation;
}
// compute the total direction modifier for roundabout turns
else if (begin->maneuver.instruction.type == TurnType::EnterRoundaboutIntersection ||
begin->maneuver.instruction.type == TurnType::EnterRoundaboutIntersectionAtExit)
{
const auto entry_intersection = begin->intersections.front();
const auto exit_intersection = last->intersections.front();
const auto exit_bearing = exit_intersection.bearings[exit_intersection.out];
BOOST_ASSERT(!begin->intersections.empty());
const double angle = util::bearing::angleBetween(
util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]),
exit_bearing);
begin->maneuver.instruction.direction_modifier = getTurnDirection(angle);
}
begin->AdaptStepSignage(*last);
}
// in case of a roundabout turn, we do not emit an exit as long as the mode remains the same
if ((begin->maneuver.instruction.type == TurnType::EnterRoundaboutIntersection ||
begin->maneuver.instruction.type == TurnType::EnterRoundaboutIntersectionAtExit) &&
begin->mode == last->mode)
{
compressRange(begin, end);
}
else
{
return true;
// do not remove last (the exit instruction)
compressRange(begin, last);
}
}
void closeOffRoundabout(const bool on_roundabout,
std::vector<RouteStep> &steps,
std::size_t step_index)
// roundabout groups are a sequence of roundabout instructions. This can contain enter/exit
// instructions in between
void processRoundaboutGroups(const std::pair<RouteStepIterator, RouteStepIterator> &range)
{
auto &step = steps[step_index];
step.maneuver.exit += 1;
if (!on_roundabout)
const auto leaves_roundabout = [](auto const &step) {
return leavesRoundabout(step.maneuver.instruction);
};
auto itr = range.first;
while (itr != range.second)
{
BOOST_ASSERT(steps.size() >= 2);
// We reached a special case that requires the addition of a special route step in the
// beginning. We started in a roundabout, so to announce the exit, we move use the exit
// instruction and move it right to the beginning to make sure to immediately announce the
// exit.
BOOST_ASSERT(leavesRoundabout(steps[1].maneuver.instruction) ||
steps[1].maneuver.instruction.type == TurnType::StayOnRoundabout ||
steps[1].maneuver.instruction.type == TurnType::Suppressed ||
steps[1].maneuver.instruction.type == TurnType::NoTurn);
steps[0].geometry_end = 1;
steps[1].geometry_begin = 0;
steps[1].AddInFront(steps[0]);
steps[1].intersections.erase(steps[1].intersections.begin()); // otherwise we copy the
// source
if (leavesRoundabout(steps[1].maneuver.instruction))
steps[1].maneuver.exit = 1;
steps[0].duration = 0;
steps[0].distance = 0;
const auto exitToEnter = [](const TurnType::Enum type) {
if (TurnType::ExitRotary == type)
return TurnType::EnterRotary;
// if we do not enter the roundabout Intersection, we cannot treat the full traversal as
// a turn. So we switch it up to the roundabout type
else if (type == TurnType::ExitRoundaboutIntersection)
return TurnType::EnterRoundabout;
else
return TurnType::EnterRoundabout;
};
steps[1].maneuver.instruction.type = exitToEnter(step.maneuver.instruction.type);
if (steps[1].maneuver.instruction.type == TurnType::EnterRotary)
auto exit = std::find_if(itr, range.second, leaves_roundabout);
if (exit == range.second)
{
steps[1].rotary_name = steps[0].name;
steps[1].rotary_pronunciation = steps[0].pronunciation;
processRoundaboutExits(itr, exit);
itr = exit;
}
}
if (step_index > 1)
{
auto &exit_step = steps[step_index];
auto &prev_step = steps[step_index - 1];
// In case the step with the roundabout exit instruction cannot be merged with the
// previous step we change the instruction to a normal turn
if (!guidance::haveSameMode(exit_step, prev_step))
else
{
BOOST_ASSERT(leavesRoundabout(exit_step.maneuver.instruction));
if (!entersRoundabout(prev_step.maneuver.instruction))
{
prev_step.maneuver.instruction = exit_step.maneuver.instruction;
}
prev_step.maneuver.exit = exit_step.maneuver.exit;
exit_step.maneuver.instruction.type = TurnType::Notification;
step_index--;
processRoundaboutExits(itr, exit + 1);
itr = exit + 1;
}
}
// Normal exit from the roundabout, or exit from a previously fixed roundabout. Propagate the
// index back to the entering location and prepare the current silent set of instructions for
// removal.
std::vector<std::size_t> intermediate_steps;
BOOST_ASSERT(!steps[step_index].intersections.empty());
// the very first intersection in the steps represents the location of the turn. Following
// intersections are locations passed along the way
const auto exit_intersection = steps[step_index].intersections.front();
const auto exit_bearing = exit_intersection.bearings[exit_intersection.out];
const auto destination_copy = step;
if (step_index > 1)
{
// The very first route-step is head, so we cannot iterate past that one
for (std::size_t propagation_index = step_index - 1; propagation_index > 0;
--propagation_index)
{
auto &propagation_step = steps[propagation_index];
auto &next_step = steps[propagation_index + 1];
if (guidance::haveSameMode(propagation_step, next_step))
{
propagation_step.ElongateBy(next_step);
propagation_step.maneuver.exit = next_step.maneuver.exit;
next_step.Invalidate();
}
if (entersRoundabout(propagation_step.maneuver.instruction))
{
const auto entry_intersection = propagation_step.intersections.front();
// remember rotary name
if (propagation_step.maneuver.instruction.type == TurnType::EnterRotary ||
propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit)
{
propagation_step.rotary_name = propagation_step.name;
propagation_step.rotary_pronunciation = propagation_step.pronunciation;
}
else if (propagation_step.maneuver.instruction.type ==
TurnType::EnterRoundaboutIntersection ||
propagation_step.maneuver.instruction.type ==
TurnType::EnterRoundaboutIntersectionAtExit)
{
BOOST_ASSERT(!propagation_step.intersections.empty());
const double angle = util::bearing::angleBetween(
util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]),
exit_bearing);
auto bearings = propagation_step.intersections.front().bearings;
propagation_step.maneuver.instruction.direction_modifier =
getTurnDirection(angle);
}
propagation_step.AdaptStepSignage(destination_copy);
break;
}
}
// remove exit
}
}
} // namespace
@@ -248,64 +182,49 @@ void closeOffRoundabout(const bool on_roundabout,
// They are required for maintenance purposes. We can calculate the number
// of exits to pass in a roundabout and the number of intersections
// that we come across.
std::vector<RouteStep> postProcess(std::vector<RouteStep> steps)
std::vector<RouteStep> handleRoundabouts(std::vector<RouteStep> steps)
{
// the steps should always include the first/last step in form of a location
BOOST_ASSERT(steps.size() >= 2);
if (steps.size() == 2)
// check if a step has roundabout type
const auto has_roundabout_type = [](auto const &step) {
return hasRoundaboutType(step.maneuver.instruction);
};
const auto first_roundabout_type =
std::find_if(steps.begin(), steps.end(), has_roundabout_type);
// no roundabout to process?
if (first_roundabout_type == steps.end())
return steps;
// Count Street Exits forward
bool on_roundabout = false;
bool has_entered_roundabout = false;
// unless the first instruction enters the roundabout, we are currently on a roundabout. This is
// a special case that happens if the route starts on a roundabout. It is a border case, but
// could happen during re-routing. In the case of re-routing, exit counting might be confusing,
// but it is the best we can do
bool currently_on_roundabout = !entersRoundabout(first_roundabout_type->maneuver.instruction);
// count the exits forward. if enter/exit roundabout happen both, no further treatment is
// required. We might end up with only one of them (e.g. starting within a roundabout)
// or having a via-point in the roundabout.
// In this case, exits are numbered from the start of the leg.
for (std::size_t step_index = 0; step_index < steps.size(); ++step_index)
{
const auto next_step_index = step_index + 1;
auto &step = steps[step_index];
const auto instruction = step.maneuver.instruction;
if (entersRoundabout(instruction))
// this group by paradigm does might contain intermediate roundabout instructions, when they are
// directly connected. Otherwise it will be a sequence containing everything from enter to exit.
// If we already start on the roundabout, the first valid place will be steps.begin().
const auto is_on_roundabout = [&currently_on_roundabout](const auto &step) {
if (currently_on_roundabout)
{
has_entered_roundabout = setUpRoundabout(step);
if (leavesRoundabout(step.maneuver.instruction))
currently_on_roundabout = false;
if (has_entered_roundabout && next_step_index < steps.size())
steps[next_step_index].maneuver.exit = step.maneuver.exit;
return true;
}
else if (instruction.type == TurnType::StayOnRoundabout)
else
{
on_roundabout = true;
// increase the exit number we require passing the exit
step.maneuver.exit += 1;
if (next_step_index < steps.size())
steps[next_step_index].maneuver.exit = step.maneuver.exit;
currently_on_roundabout = entersRoundabout(step.maneuver.instruction);
auto result = currently_on_roundabout;
// cases that immediately exit the roundabout
if (currently_on_roundabout)
currently_on_roundabout = !leavesRoundabout(step.maneuver.instruction);
return result;
}
else if (leavesRoundabout(instruction))
{
// if (!has_entered_roundabout)
// in case the we are not on a roundabout, the very first instruction
// after the depart will be transformed into a roundabout and become
// the first valid instruction
closeOffRoundabout(has_entered_roundabout, steps, step_index);
has_entered_roundabout = false;
on_roundabout = false;
}
else if (on_roundabout && next_step_index < steps.size())
{
steps[next_step_index].maneuver.exit = step.maneuver.exit;
}
}
};
// unterminated roundabout
// Move backwards through the instructions until the start and remove the exit number
// A roundabout without exit translates to enter-roundabout
if (has_entered_roundabout || on_roundabout)
{
fixFinalRoundabout(steps);
}
// for each range of instructions between begin/end of a roundabout assign
util::group_by(steps.begin(), steps.end(), is_on_roundabout, processRoundaboutGroups);
BOOST_ASSERT(steps.front().intersections.size() >= 1);
BOOST_ASSERT(steps.front().intersections.front().bearings.size() == 1);
@@ -333,8 +252,7 @@ void trimShortSegments(std::vector<RouteStep> &steps, LegGeometry &geometry)
return;
// if phantom node is located at the connection of two segments, either one can be selected
// as
// turn
// as turn
//
// a --- b
// |