refactor of turn analysis into turn handlers

This commit is contained in:
Moritz Kobitzsch
2016-04-08 12:49:14 +02:00
committed by Patrick Niklaus
parent dfd180a292
commit fcbf527ba5
14 changed files with 2985 additions and 2612 deletions
+31
View File
@@ -0,0 +1,31 @@
#include "extractor/guidance/intersection.hpp"
namespace osrm
{
namespace extractor
{
namespace guidance
{
ConnectedRoad::ConnectedRoad(const TurnOperation turn, const bool entry_allowed)
: entry_allowed(entry_allowed), turn(turn)
{
}
std::string toString(const ConnectedRoad &road)
{
std::string result = "[connection] ";
result += std::to_string(road.turn.eid);
result += " allows entry: ";
result += std::to_string(road.entry_allowed);
result += " angle: ";
result += std::to_string(road.turn.angle);
result += " instruction: ";
result += std::to_string(static_cast<std::int32_t>(road.turn.instruction.type)) + " " +
std::to_string(static_cast<std::int32_t>(road.turn.instruction.direction_modifier));
return result;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -0,0 +1,255 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/toolkit.hpp"
#include <algorithm>
#include <iterator>
#include <limits>
#include <utility>
namespace osrm
{
namespace extractor
{
namespace guidance
{
IntersectionGenerator::IntersectionGenerator(const util::NodeBasedDynamicGraph &node_based_graph,
const RestrictionMap &restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const std::vector<QueryNode> &node_info_list,
const CompressedEdgeContainer &compressed_edge_container)
: node_based_graph(node_based_graph), restriction_map(restriction_map),
barrier_nodes(barrier_nodes), node_info_list(node_info_list),
compressed_edge_container(compressed_edge_container)
{
}
Intersection IntersectionGenerator::operator()(const NodeID from_node, const EdgeID via_eid) const
{
return getConnectedRoads(from_node, via_eid);
}
// a
// |
// |
// v
// For an intersection from_node --via_edi--> turn_node ----> c
// ^
// |
// |
// b
// This functions returns _all_ turns as if the graph was undirected.
// That means we not only get (from_node, turn_node, c) in the above example
// but also (from_node, turn_node, a), (from_node, turn_node, b). These turns are
// marked as invalid and only needed for intersection classification.
Intersection IntersectionGenerator::getConnectedRoads(const NodeID from_node,
const EdgeID via_eid) const
{
Intersection intersection;
const NodeID turn_node = node_based_graph.GetTarget(via_eid);
const NodeID only_restriction_to_node =
restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node);
const bool is_barrier_node = barrier_nodes.find(turn_node) != barrier_nodes.end();
bool has_uturn_edge = false;
for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node))
{
BOOST_ASSERT(onto_edge != SPECIAL_EDGEID);
const NodeID to_node = node_based_graph.GetTarget(onto_edge);
bool turn_is_valid =
// reverse edges are never valid turns because the resulting turn would look like this:
// from_node --via_edge--> turn_node <--onto_edge-- to_node
// however we need this for capture intersection shape for incoming one-ways
!node_based_graph.GetEdgeData(onto_edge).reversed &&
// we are not turning over a barrier
(!is_barrier_node || from_node == to_node) &&
// We are at an only_-restriction but not at the right turn.
(only_restriction_to_node == SPECIAL_NODEID || to_node == only_restriction_to_node) &&
// the turn is not restricted
!restriction_map.CheckIfTurnIsRestricted(from_node, turn_node, to_node);
auto angle = 0.;
if (from_node == to_node)
{
if (turn_is_valid && !is_barrier_node)
{
// we only add u-turns for dead-end streets.
if (node_based_graph.GetOutDegree(turn_node) > 1)
{
auto number_of_emmiting_bidirectional_edges = 0;
for (auto edge : node_based_graph.GetAdjacentEdgeRange(turn_node))
{
auto target = node_based_graph.GetTarget(edge);
auto reverse_edge = node_based_graph.FindEdge(target, turn_node);
BOOST_ASSERT(reverse_edge != SPECIAL_EDGEID);
if (!node_based_graph.GetEdgeData(reverse_edge).reversed)
{
++number_of_emmiting_bidirectional_edges;
}
}
// is a dead-end
turn_is_valid = number_of_emmiting_bidirectional_edges <= 1;
}
}
has_uturn_edge = true;
BOOST_ASSERT(angle >= 0. && angle < std::numeric_limits<double>::epsilon());
}
else
{
// unpack first node of second segment if packed
const auto first_coordinate = getRepresentativeCoordinate(
from_node, turn_node, via_eid, INVERT, compressed_edge_container, node_info_list);
const auto third_coordinate = getRepresentativeCoordinate(
turn_node, to_node, onto_edge, !INVERT, compressed_edge_container, node_info_list);
angle = util::coordinate_calculation::computeAngle(
first_coordinate, node_info_list[turn_node], third_coordinate);
if (angle < std::numeric_limits<double>::epsilon())
has_uturn_edge = true;
}
intersection.push_back(ConnectedRoad(
TurnOperation{onto_edge, angle, {TurnType::Invalid, DirectionModifier::UTurn}},
turn_is_valid));
}
// We hit the case of a street leading into nothing-ness. Since the code here assumes that this
// will
// never happen we add an artificial invalid uturn in this case.
if (!has_uturn_edge)
{
intersection.push_back(
{TurnOperation{via_eid, 0., {TurnType::Invalid, DirectionModifier::UTurn}}, false});
}
const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) {
return first.turn.angle < second.turn.angle;
};
std::sort(std::begin(intersection), std::end(intersection), ByAngle);
BOOST_ASSERT(intersection[0].turn.angle >= 0. &&
intersection[0].turn.angle < std::numeric_limits<double>::epsilon());
return mergeSegregatedRoads(std::move(intersection));
}
/*
* Segregated Roads often merge onto a single intersection.
* While technically representing different roads, they are
* often looked at as a single road.
* Due to the merging, turn Angles seem off, wenn we compute them from the
* initial positions.
*
* b<b<b<b(1)<b<b<b
* aaaaa-b
* b>b>b>b(2)>b>b>b
*
* Would be seen as a slight turn going fro a to (2). A Sharp turn going from
* (1) to (2).
*
* In cases like these, we megre this segregated roads into a single road to
* end up with a case like:
*
* aaaaa-bbbbbb
*
* for the turn representation.
* Anything containing the first u-turn in a merge affects all other angles
* and is handled separately from all others.
*/
Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersection) const
{
const auto getRight = [&](std::size_t index) {
return (index + intersection.size() - 1) % intersection.size();
};
const auto mergable = [&](std::size_t first, std::size_t second) -> bool {
const auto &first_data = node_based_graph.GetEdgeData(intersection[first].turn.eid);
const auto &second_data = node_based_graph.GetEdgeData(intersection[second].turn.eid);
return first_data.name_id != INVALID_NAME_ID && first_data.name_id == second_data.name_id &&
!first_data.roundabout && !second_data.roundabout &&
first_data.travel_mode == second_data.travel_mode &&
first_data.road_classification == second_data.road_classification &&
// compatible threshold
angularDeviation(intersection[first].turn.angle, intersection[second].turn.angle) <
60 &&
first_data.reversed != second_data.reversed;
};
const auto merge = [](const ConnectedRoad &first,
const ConnectedRoad &second) -> ConnectedRoad {
if (!first.entry_allowed)
{
ConnectedRoad result = second;
result.turn.angle = (first.turn.angle + second.turn.angle) / 2;
if (first.turn.angle - second.turn.angle > 180)
result.turn.angle += 180;
if (result.turn.angle > 360)
result.turn.angle -= 360;
return result;
}
else
{
BOOST_ASSERT(!second.entry_allowed);
ConnectedRoad result = first;
result.turn.angle = (first.turn.angle + second.turn.angle) / 2;
if (first.turn.angle - second.turn.angle > 180)
result.turn.angle += 180;
if (result.turn.angle > 360)
result.turn.angle -= 360;
return result;
}
};
if (intersection.size() == 1)
return intersection;
// check for merges including the basic u-turn
// these result in an adjustment of all other angles
if (mergable(0, intersection.size() - 1))
{
const double correction_factor =
(360 - intersection[intersection.size() - 1].turn.angle) / 2;
for (std::size_t i = 1; i + 1 < intersection.size(); ++i)
intersection[i].turn.angle += correction_factor;
intersection[0] = merge(intersection.front(), intersection.back());
intersection[0].turn.angle = 0;
intersection.pop_back();
}
else if (mergable(0, 1))
{
const double correction_factor = (intersection[1].turn.angle) / 2;
for (std::size_t i = 2; i < intersection.size(); ++i)
intersection[i].turn.angle += correction_factor;
intersection[0] = merge(intersection[0], intersection[1]);
intersection[0].turn.angle = 0;
intersection.erase(intersection.begin() + 1);
}
// a merge including the first u-turn requres an adjustment of the turn angles
// therefore these are handled prior to this step
for (std::size_t index = 2; index < intersection.size(); ++index)
{
if (mergable(index, getRight(index)))
{
intersection[getRight(index)] =
merge(intersection[getRight(index)], intersection[index]);
intersection.erase(intersection.begin() + index);
--index;
}
}
const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) {
return first.turn.angle < second.turn.angle;
};
std::sort(std::begin(intersection), std::end(intersection), ByAngle);
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -0,0 +1,307 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/intersection_handler.hpp"
#include "extractor/guidance/toolkit.hpp"
#include <algorithm>
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace detail
{
inline bool requiresAnnouncement(const EdgeData &from, const EdgeData &to)
{
return !from.IsCompatibleTo(to);
}
}
IntersectionHandler::IntersectionHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table)
: node_based_graph(node_based_graph), node_info_list(node_info_list), name_table(name_table)
{
}
IntersectionHandler::~IntersectionHandler() {}
std::size_t IntersectionHandler::countValid(const Intersection &intersection) const
{
return std::count_if(intersection.begin(), intersection.end(),
[](const ConnectedRoad &road) { return road.entry_allowed; });
}
TurnType IntersectionHandler::findBasicTurnType(const EdgeID via_edge,
const ConnectedRoad &road) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
bool on_ramp = isRampClass(in_data.road_classification.road_class);
bool onto_ramp = isRampClass(out_data.road_classification.road_class);
if (!on_ramp && onto_ramp)
return TurnType::Ramp;
if (in_data.name_id == out_data.name_id && in_data.name_id != INVALID_NAME_ID)
{
return TurnType::Continue;
}
return TurnType::Turn;
}
TurnInstruction IntersectionHandler::getInstructionForObvious(const std::size_t num_roads,
const EdgeID via_edge,
const bool through_street,
const ConnectedRoad &road) const
{
const auto type = findBasicTurnType(via_edge, road);
// handle travel modes:
const auto in_mode = node_based_graph.GetEdgeData(via_edge).travel_mode;
const auto out_mode = node_based_graph.GetEdgeData(road.turn.eid).travel_mode;
if (type == TurnType::Ramp)
{
return {TurnType::Ramp, getTurnDirection(road.turn.angle)};
}
if (angularDeviation(road.turn.angle, 0) < 0.01)
{
return {TurnType::Turn, DirectionModifier::UTurn};
}
if (type == TurnType::Turn)
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (in_data.name_id != out_data.name_id &&
requiresNameAnnounced(name_table.GetNameForID(in_data.name_id),
name_table.GetNameForID(out_data.name_id)))
{
// obvious turn onto a through street is a merge
if (through_street)
{
return {TurnType::Merge, road.turn.angle > STRAIGHT_ANGLE
? DirectionModifier::SlightRight
: DirectionModifier::SlightLeft};
}
else
{
return {TurnType::NewName, getTurnDirection(road.turn.angle)};
}
}
else
{
if (in_mode == out_mode)
return {TurnType::Suppressed, getTurnDirection(road.turn.angle)};
else
return {TurnType::Notification, getTurnDirection(road.turn.angle)};
}
}
BOOST_ASSERT(type == TurnType::Continue);
if (in_mode != out_mode)
{
return {TurnType::Notification, getTurnDirection(road.turn.angle)};
}
if (num_roads > 2)
{
return {TurnType::Suppressed, getTurnDirection(road.turn.angle)};
}
else
{
return {TurnType::NoTurn, getTurnDirection(road.turn.angle)};
}
}
void IntersectionHandler::assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &right) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const bool low_priority_left = isLowPriorityRoadClass(
node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class);
const bool low_priority_right = isLowPriorityRoadClass(
node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class);
if ((angularDeviation(left.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(right.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE))
{
// left side is actually straight
const auto &out_data = node_based_graph.GetEdgeData(left.turn.eid);
if (detail::requiresAnnouncement(in_data, out_data))
{
if (low_priority_right && !low_priority_left)
{
left.turn.instruction = getInstructionForObvious(3, via_edge, false, left);
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
if (low_priority_left && !low_priority_right)
{
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
}
}
else
{
left.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight};
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
}
else if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) <
MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)
{
// right side is actually straight
const auto &out_data = node_based_graph.GetEdgeData(right.turn.eid);
if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) <
MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)
{
if (detail::requiresAnnouncement(in_data, out_data))
{
if (low_priority_left && !low_priority_right)
{
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.turn.instruction = getInstructionForObvious(3, via_edge, false, right);
}
else
{
if (low_priority_right && !low_priority_left)
{
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
}
}
}
else
{
right.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight};
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
}
}
}
// left side of fork
if (low_priority_right && !low_priority_left)
left.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft};
else
{
if (low_priority_left && !low_priority_right)
left.turn.instruction = {TurnType::Turn, DirectionModifier::SlightLeft};
else
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
}
// right side of fork
if (low_priority_left && !low_priority_right)
right.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft};
else
{
if (low_priority_right && !low_priority_left)
right.turn.instruction = {TurnType::Turn, DirectionModifier::SlightRight};
else
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
}
void IntersectionHandler::assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &center,
ConnectedRoad &right) const
{
// TODO handle low priority road classes in a reasonable way
if (left.entry_allowed && center.entry_allowed && right.entry_allowed)
{
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
if (angularDeviation(center.turn.angle, 180) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION)
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const auto &out_data = node_based_graph.GetEdgeData(center.turn.eid);
if (detail::requiresAnnouncement(in_data, out_data))
{
center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight};
}
else
{
center.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight};
}
}
else
{
center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight};
}
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
else if (left.entry_allowed)
{
if (right.entry_allowed)
assignFork(via_edge, left, right);
else if (center.entry_allowed)
assignFork(via_edge, left, center);
else
left.turn.instruction = {findBasicTurnType(via_edge, left),
getTurnDirection(left.turn.angle)};
}
else if (right.entry_allowed)
{
if (center.entry_allowed)
assignFork(via_edge, center, right);
else
right.turn.instruction = {findBasicTurnType(via_edge, right),
getTurnDirection(right.turn.angle)};
}
else
{
if (center.entry_allowed)
center.turn.instruction = {findBasicTurnType(via_edge, center),
getTurnDirection(center.turn.angle)};
}
}
bool IntersectionHandler::isThroughStreet(const std::size_t index,
const Intersection &intersection) const
{
if (node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id == INVALID_NAME_ID)
return false;
for (const auto &road : intersection)
{
// a through street cannot start at our own position
if (road.turn.angle < std::numeric_limits<double>::epsilon())
continue;
if (angularDeviation(road.turn.angle, intersection[index].turn.angle) >
(STRAIGHT_ANGLE - NARROW_TURN_ANGLE) &&
node_based_graph.GetEdgeData(road.turn.eid).name_id ==
node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id)
return true;
}
return false;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
+524
View File
@@ -0,0 +1,524 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/motorway_handler.hpp"
#include "extractor/guidance/toolkit.hpp"
#include "util/simple_logger.hpp"
#include <limits>
#include <utility>
#include <boost/assert.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace detail
{
inline bool isMotorwayClass(const FunctionalRoadClass road_class)
{
return road_class == FunctionalRoadClass::MOTORWAY || road_class == FunctionalRoadClass::TRUNK;
}
inline bool isMotorwayClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
{
return isMotorwayClass(node_based_graph.GetEdgeData(eid).road_classification.road_class);
}
inline FunctionalRoadClass roadClass(const ConnectedRoad &road,
const util::NodeBasedDynamicGraph &graph)
{
return graph.GetEdgeData(road.turn.eid).road_classification.road_class;
}
inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
{
return isRampClass(node_based_graph.GetEdgeData(eid).road_classification.road_class);
}
}
MotorwayHandler::MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table)
: IntersectionHandler(node_based_graph, node_info_list, name_table)
{
}
MotorwayHandler::~MotorwayHandler() {}
bool MotorwayHandler::canProcess(const NodeID,
const EdgeID via_eid,
const Intersection &intersection) const
{
bool has_motorway = false;
bool has_normal_roads = false;
for (const auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
// not merging or forking?
if (road.entry_allowed && angularDeviation(road.turn.angle, STRAIGHT_ANGLE) > 60)
return false;
else if (out_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY ||
out_data.road_classification.road_class == FunctionalRoadClass::TRUNK)
{
if (road.entry_allowed)
has_motorway = true;
}
else if (!isRampClass(out_data.road_classification.road_class))
has_normal_roads = true;
}
if (has_normal_roads)
return false;
const auto &in_data = node_based_graph.GetEdgeData(via_eid);
return has_motorway ||
in_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY ||
in_data.road_classification.road_class == FunctionalRoadClass::TRUNK;
}
Intersection MotorwayHandler::
operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_eid);
// coming from motorway
if (detail::isMotorwayClass(in_data.road_classification.road_class))
{
return fromMotorway(via_eid, std::move(intersection));
}
else // coming from a ramp
{
return fromRamp(via_eid, std::move(intersection));
// ramp merging straight onto motorway
}
}
Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection intersection) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_eid);
BOOST_ASSERT(detail::isMotorwayClass(in_data.road_classification.road_class));
const auto countExitingMotorways = [this](const Intersection &intersection) {
unsigned count = 0;
for (const auto &road : intersection)
{
if (road.entry_allowed && detail::isMotorwayClass(road.turn.eid, node_based_graph))
++count;
}
return count;
};
// find the angle that continues on our current highway
const auto getContinueAngle = [this, in_data](const Intersection &intersection) {
for (const auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (road.turn.angle != 0 && in_data.name_id == out_data.name_id &&
in_data.name_id != INVALID_NAME_ID &&
detail::isMotorwayClass(out_data.road_classification.road_class))
return road.turn.angle;
}
return intersection[0].turn.angle;
};
const auto getMostLikelyContinue = [this, in_data](const Intersection &intersection) {
double angle = intersection[0].turn.angle;
double best = 180;
for (const auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (detail::isMotorwayClass(out_data.road_classification.road_class) &&
angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < best)
{
best = angularDeviation(road.turn.angle, STRAIGHT_ANGLE);
angle = road.turn.angle;
}
}
return angle;
};
const auto findBestContinue = [&]() {
const double continue_angle = getContinueAngle(intersection);
if (continue_angle != intersection[0].turn.angle)
return continue_angle;
else
return getMostLikelyContinue(intersection);
};
// find continue angle
const double continue_angle = findBestContinue();
// highway does not continue and has no obvious choice
if (continue_angle == intersection[0].turn.angle)
{
if (intersection.size() == 2)
{
// do not announce ramps at the end of a highway
intersection[1].turn.instruction = {TurnType::NoTurn,
getTurnDirection(intersection[1].turn.angle)};
}
else if (intersection.size() == 3)
{
// splitting ramp at the end of a highway
if (intersection[1].entry_allowed && intersection[2].entry_allowed)
{
assignFork(via_eid, intersection[2], intersection[1]);
}
else
{
// ending in a passing ramp
if (intersection[1].entry_allowed)
intersection[1].turn.instruction = {
TurnType::NoTurn, getTurnDirection(intersection[1].turn.angle)};
else
intersection[2].turn.instruction = {
TurnType::NoTurn, getTurnDirection(intersection[2].turn.angle)};
}
}
else if (intersection.size() == 4 &&
detail::roadClass(intersection[1], node_based_graph) ==
detail::roadClass(intersection[2], node_based_graph) &&
detail::roadClass(intersection[2], node_based_graph) ==
detail::roadClass(intersection[3], node_based_graph))
{
// tripple fork at the end
assignFork(via_eid, intersection[3], intersection[2], intersection[1]);
}
else if (countValid(intersection) > 0) // check whether turns exist at all
{
// FALLBACK, this should hopefully never be reached
util::SimpleLogger().Write(logDEBUG)
<< "Fallback reached from motorway, no continue angle, " << intersection.size()
<< " roads, " << countValid(intersection) << " valid ones.";
return fallback(std::move(intersection));
}
}
else
{
const unsigned exiting_motorways = countExitingMotorways(intersection);
if (exiting_motorways == 0)
{
// Ending in Ramp
for (auto &road : intersection)
{
if (road.entry_allowed)
{
BOOST_ASSERT(detail::isRampClass(road.turn.eid, node_based_graph));
road.turn.instruction =
TurnInstruction::SUPPRESSED(getTurnDirection(road.turn.angle));
}
}
}
else if (exiting_motorways == 1)
{
// normal motorway passing some ramps or mering onto another motorway
if (intersection.size() == 2)
{
BOOST_ASSERT(!detail::isRampClass(intersection[1].turn.eid, node_based_graph));
intersection[1].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(1,intersection), intersection[1]);
}
else
{
// Normal Highway exit or merge
for (auto &road : intersection)
{
// ignore invalid uturns/other
if (!road.entry_allowed)
continue;
if (road.turn.angle == continue_angle)
{
road.turn.instruction = getInstructionForObvious(
intersection.size(), via_eid, isThroughStreet(1,intersection), road);
}
else if (road.turn.angle < continue_angle)
{
road.turn.instruction = {
detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp
: TurnType::Turn,
(road.turn.angle < 145) ? DirectionModifier::Right
: DirectionModifier::SlightRight};
}
else if (road.turn.angle > continue_angle)
{
road.turn.instruction = {
detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp
: TurnType::Turn,
(road.turn.angle > 215) ? DirectionModifier::Left
: DirectionModifier::SlightLeft};
}
}
}
}
// handle motorway forks
else if (exiting_motorways > 1)
{
if (exiting_motorways == 2 && intersection.size() == 2)
{
intersection[1].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(1,intersection), intersection[1]);
util::SimpleLogger().Write(logDEBUG) << "Disabled U-Turn on a freeway";
intersection[0].entry_allowed = false; // UTURN on the freeway
}
else if (exiting_motorways == 2)
{
// standard fork
std::size_t first_valid = std::numeric_limits<std::size_t>::max(),
second_valid = std::numeric_limits<std::size_t>::max();
for (std::size_t i = 0; i < intersection.size(); ++i)
{
if (intersection[i].entry_allowed &&
detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph))
{
if (first_valid < intersection.size())
{
second_valid = i;
break;
}
else
{
first_valid = i;
}
}
}
assignFork(via_eid, intersection[second_valid], intersection[first_valid]);
}
else if (exiting_motorways == 3)
{
// triple fork
std::size_t first_valid = std::numeric_limits<std::size_t>::max(),
second_valid = std::numeric_limits<std::size_t>::max(),
third_valid = std::numeric_limits<std::size_t>::max();
for (std::size_t i = 0; i < intersection.size(); ++i)
{
if (intersection[i].entry_allowed &&
detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph))
{
if (second_valid < intersection.size())
{
third_valid = i;
break;
}
else if (first_valid < intersection.size())
{
second_valid = i;
}
else
{
first_valid = i;
}
}
}
assignFork(via_eid, intersection[third_valid], intersection[second_valid],
intersection[first_valid]);
}
else
{
util::SimpleLogger().Write(logDEBUG) << "Found motorway junction with more than "
"2 exiting motorways or additional ramps";
return fallback(std::move(intersection));
}
} // done for more than one highway exit
}
return intersection;
}
Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection intersection) const
{
auto num_valid_turns = countValid(intersection);
// ramp straight into a motorway/ramp
if (intersection.size() == 2 && num_valid_turns == 1)
{
BOOST_ASSERT(!intersection[0].entry_allowed);
BOOST_ASSERT(detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph));
intersection[1].turn.instruction = getInstructionForObvious(
intersection.size(), via_eid, isThroughStreet(1,intersection), intersection[1]);
}
else if (intersection.size() == 3)
{
// merging onto a passing highway / or two ramps merging onto the same highway
if (num_valid_turns == 1)
{
BOOST_ASSERT(!intersection[0].entry_allowed);
// check order of highways
// 4
// 5 3
//
// 6 2
//
// 7 1
// 0
if (intersection[1].entry_allowed)
{
if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id !=
INVALID_NAME_ID &&
node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id ==
node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id)
{
// circular order indicates a merge to the left (0-3 onto 4
if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) <
NARROW_TURN_ANGLE)
intersection[1].turn.instruction = {TurnType::Merge,
DirectionModifier::SlightLeft};
else // fallback
intersection[1].turn.instruction = {
TurnType::Merge, getTurnDirection(intersection[1].turn.angle)};
}
else // passing by the end of a motorway
{
intersection[1].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(1,intersection), intersection[1]);
}
}
else
{
BOOST_ASSERT(intersection[2].entry_allowed);
if (detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph) &&
node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id !=
INVALID_NAME_ID &&
node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id ==
node_based_graph.GetEdgeData(intersection[0].turn.eid).name_id)
{
// circular order (5-0) onto 4
if (angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) <
NARROW_TURN_ANGLE)
intersection[2].turn.instruction = {TurnType::Merge,
DirectionModifier::SlightRight};
else // fallback
intersection[2].turn.instruction = {
TurnType::Merge, getTurnDirection(intersection[2].turn.angle)};
}
else // passing the end of a highway
{
intersection[2].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(2,intersection), intersection[2]);
}
}
}
else
{
BOOST_ASSERT(num_valid_turns == 2);
// UTurn on ramps is not possible
BOOST_ASSERT(!intersection[0].entry_allowed);
BOOST_ASSERT(intersection[1].entry_allowed);
BOOST_ASSERT(intersection[2].entry_allowed);
// two motorways starting at end of ramp (fork)
// M M
// \ /
// |
// R
if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph))
{
assignFork(via_eid, intersection[2], intersection[1]);
}
else
{
// continued ramp passing motorway entry
// M R
// M R
// | /
// R
if (detail::isMotorwayClass(node_based_graph.GetEdgeData(intersection[1].turn.eid)
.road_classification.road_class))
{
intersection[1].turn.instruction = {TurnType::Turn,
DirectionModifier::SlightRight};
intersection[2].turn.instruction = {TurnType::Continue,
DirectionModifier::SlightLeft};
}
else
{
assignFork(via_eid, intersection[2], intersection[1]);
}
}
}
}
// On - Off Ramp on passing Motorway, Ramp onto Fork(?)
else if (intersection.size() == 4)
{
bool passed_highway_entry = false;
for (auto &road : intersection)
{
const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid);
if (!road.entry_allowed &&
detail::isMotorwayClass(edge_data.road_classification.road_class))
{
passed_highway_entry = true;
}
else if (detail::isMotorwayClass(edge_data.road_classification.road_class))
{
road.turn.instruction = {TurnType::Merge, passed_highway_entry
? DirectionModifier::SlightRight
: DirectionModifier::SlightLeft};
}
else
{
BOOST_ASSERT(isRampClass(edge_data.road_classification.road_class));
road.turn.instruction = {TurnType::Ramp, getTurnDirection(road.turn.angle)};
}
}
}
else
{ // FALLBACK, hopefully this should never been reached
util::SimpleLogger().Write(logDEBUG) << "Reached fallback on motorway ramp with "
<< intersection.size() << " roads and "
<< countValid(intersection) << " valid turns.";
return fallback(std::move(intersection));
}
return intersection;
}
Intersection MotorwayHandler::fallback(Intersection intersection) const
{
for (auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
util::SimpleLogger().Write(logDEBUG)
<< "road: " << toString(road) << " Name: " << out_data.name_id
<< " Road Class: " << (int)out_data.road_classification.road_class;
if (!road.entry_allowed)
continue;
const auto type = detail::isMotorwayClass(out_data.road_classification.road_class)
? TurnType::Merge
: TurnType::Turn;
if (type == TurnType::Turn)
{
if (angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE)
road.turn.instruction = {type, DirectionModifier::Straight};
else
{
road.turn.instruction = {type, road.turn.angle > STRAIGHT_ANGLE
? DirectionModifier::SlightLeft
: DirectionModifier::SlightRight};
}
}
else
{
road.turn.instruction = {type, road.turn.angle < STRAIGHT_ANGLE
? DirectionModifier::SlightLeft
: DirectionModifier::SlightRight};
}
}
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -0,0 +1,257 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/roundabout_handler.hpp"
#include "extractor/guidance/toolkit.hpp"
#include "util/simple_logger.hpp"
#include <cmath>
#include <set>
#include <unordered_set>
#include <boost/assert.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
RoundaboutHandler::RoundaboutHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table)
: IntersectionHandler(node_based_graph, node_info_list, name_table)
{
}
RoundaboutHandler::~RoundaboutHandler() {}
bool RoundaboutHandler::canProcess(const NodeID from_nid,
const EdgeID via_eid,
const Intersection &intersection) const
{
const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection);
return flags.on_roundabout || flags.can_enter;
}
Intersection RoundaboutHandler::
operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersection) const
{
const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection);
const bool is_rotary = isRotary(node_based_graph.GetTarget(via_eid));
// find the radius of the roundabout
return handleRoundabouts(is_rotary, via_eid, flags.on_roundabout, flags.can_exit_separately,
std::move(intersection));
}
detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags(
const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const
{
const auto &in_edge_data = node_based_graph.GetEdgeData(via_eid);
bool on_roundabout = in_edge_data.roundabout;
bool can_enter_roundabout = false;
bool can_exit_roundabout_separately = false;
for (const auto &road : intersection)
{
const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid);
// only check actual outgoing edges
if (edge_data.reversed)
continue;
if (edge_data.roundabout)
{
can_enter_roundabout = true;
}
// Exiting roundabouts at an entry point is technically a data-modelling issue.
// This workaround handles cases in which an exit follows the entry.
// To correctly represent perceived exits, we only count exits leading to a
// separate vertex than the one we are coming from that are in the direction of
// the roundabout.
// The sorting of the angles represents a problem for left-sided driving, though.
// FIXME in case of left-sided driving, we have to check whether we can enter the
// roundabout later in the cycle, rather than prior.
// FIXME requires consideration of crossing the roundabout
else if (node_based_graph.GetTarget(road.turn.eid) != from_nid && !can_enter_roundabout)
{
can_exit_roundabout_separately = true;
}
}
return {on_roundabout, can_enter_roundabout, can_exit_roundabout_separately};
}
bool RoundaboutHandler::isRotary(const NodeID nid) const
{
// translate a node ID into its respective coordinate stored in the node_info_list
const auto getCoordinate = [this](const NodeID node) {
return util::Coordinate(node_info_list[node].lon, node_info_list[node].lat);
};
unsigned roundabout_name_id = 0;
std::unordered_set<unsigned> connected_names;
const auto getNextOnRoundabout = [this, &roundabout_name_id,
&connected_names](const NodeID node) {
EdgeID continue_edge = SPECIAL_EDGEID;
for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node))
{
const auto &edge_data = node_based_graph.GetEdgeData(edge);
if (!edge_data.reversed && edge_data.roundabout)
{
if (SPECIAL_EDGEID != continue_edge)
{
// fork in roundabout
return SPECIAL_EDGEID;
}
// roundabout does not keep its name
if (roundabout_name_id != 0 && roundabout_name_id != edge_data.name_id &&
requiresNameAnnounced(name_table.GetNameForID(roundabout_name_id),
name_table.GetNameForID(edge_data.name_id)))
{
return SPECIAL_EDGEID;
}
roundabout_name_id = edge_data.name_id;
continue_edge = edge;
}
else if (!edge_data.roundabout)
{
// remember all connected road names
connected_names.insert(edge_data.name_id);
}
}
return continue_edge;
};
// the roundabout radius has to be the same for all locations we look at it from
// to guarantee this, we search the full roundabout for its vertices
// and select the three smalles ids
std::set<NodeID> roundabout_nodes; // needs to be sorted
// this value is a hard abort to deal with potential self-loops
NodeID last_node = nid;
while (0 == roundabout_nodes.count(last_node))
{
roundabout_nodes.insert(last_node);
const auto eid = getNextOnRoundabout(last_node);
if (eid == SPECIAL_EDGEID)
{
util::SimpleLogger().Write(logDEBUG) << "Non-Loop Roundabout found.";
return false;
}
last_node = node_based_graph.GetTarget(eid);
}
// do we have a dedicated name for the rotary, if not its a roundabout
// This function can theoretically fail if the roundabout name is partly
// used with a reference and without. This will be fixed automatically
// when we handle references separately or if the useage is more consistent
if (roundabout_name_id == 0 || connected_names.count(roundabout_name_id))
{
return false;
}
if (roundabout_nodes.size() <= 1)
{
return false;
}
// calculate the radius of the roundabout/rotary. For two coordinates, we assume a minimal
// circle
// with both vertices right at the other side (so half their distance in meters).
// Otherwise, we construct a circle through the first tree vertices.
const auto getRadius = [&roundabout_nodes, &getCoordinate]() {
auto node_itr = roundabout_nodes.begin();
if (roundabout_nodes.size() == 2)
{
const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++);
return 0.5 * util::coordinate_calculation::haversineDistance(first, second);
}
else
{
const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++),
third = getCoordinate(*node_itr++);
return util::coordinate_calculation::circleRadius(first, second, third);
}
};
const double radius = getRadius();
// check whether the circle computation has gone wrong
// The radius computation can result in infinity, if the three coordinates are non-distinct.
// To stay on the safe side, we say its not a rotary
if (std::isinf(radius))
return false;
return radius > MAX_ROUNDABOUT_RADIUS;
}
Intersection RoundaboutHandler::handleRoundabouts(const bool is_rotary,
const EdgeID via_eid,
const bool on_roundabout,
const bool can_exit_roundabout_separately,
Intersection intersection) const
{
// TODO requires differentiation between roundabouts and rotaries
// detect via radius (get via circle through three vertices)
NodeID node_v = node_based_graph.GetTarget(via_eid);
if (on_roundabout)
{
// Shoule hopefully have only a single exit and continue
// at least for cars. How about bikes?
for (auto &road : intersection)
{
auto &turn = road.turn;
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (out_data.roundabout)
{
// TODO can forks happen in roundabouts? E.g. required lane changes
if (1 == node_based_graph.GetDirectedOutDegree(node_v))
{
// No turn possible.
turn.instruction = TurnInstruction::NO_TURN();
}
else
{
turn.instruction =
TurnInstruction::REMAIN_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
}
}
else
{
turn.instruction =
TurnInstruction::EXIT_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
}
}
return intersection;
}
else
for (auto &road : intersection)
{
if (!road.entry_allowed)
continue;
auto &turn = road.turn;
const auto &out_data = node_based_graph.GetEdgeData(turn.eid);
if (out_data.roundabout)
{
turn.instruction =
TurnInstruction::ENTER_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
if (can_exit_roundabout_separately)
{
if (turn.instruction.type == TurnType::EnterRotary)
turn.instruction.type = TurnType::EnterRotaryAtExit;
if (turn.instruction.type == TurnType::EnterRoundabout)
turn.instruction.type = TurnType::EnterRoundaboutAtExit;
}
}
else
{
turn.instruction = TurnInstruction::ENTER_AND_EXIT_ROUNDABOUT(
is_rotary, getTurnDirection(turn.angle));
}
}
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff