osrm-backend/src/extractor/guidance/intersection_generator.cpp

605 lines
25 KiB
C++
Raw Normal View History

2016-08-11 03:44:30 -04:00
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/toolkit.hpp"
#include <algorithm>
2016-08-11 08:21:34 -04:00
#include <iomanip>
#include <iterator>
#include <limits>
#include <utility>
2016-05-02 06:00:26 -04:00
#include <boost/range/algorithm/count_if.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
2016-04-11 06:51:06 -04:00
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
{
2016-08-11 08:21:34 -04:00
auto intersection = GetConnectedRoads(from_node, via_eid);
const auto node_at_intersection = node_based_graph.GetTarget(via_eid);
return AdjustForJoiningRoads(
node_at_intersection, MergeSegregatedRoads(node_at_intersection, std::move(intersection)));
}
// 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.
2016-08-11 08:21:34 -04:00
Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node,
const EdgeID via_eid) const
{
Intersection intersection;
const NodeID turn_node = node_based_graph.GetTarget(via_eid);
2016-08-31 09:22:36 -04:00
const NodeID only_restriction_to_node = [&]() {
// If only restrictions refer to invalid ways somewhere far away, we rather ignore the
// restriction than to not route over the intersection at all.
const auto only_restriction_to_node =
restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node);
if (only_restriction_to_node != SPECIAL_NODEID)
{
// check if we can find an edge in the edge-rage
for (const auto onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node))
if (only_restriction_to_node == node_based_graph.GetTarget(onto_edge))
return only_restriction_to_node;
}
// Ignore broken only restrictions.
return SPECIAL_NODEID;
}();
const bool is_barrier_node = barrier_nodes.find(turn_node) != barrier_nodes.end();
bool has_uturn_edge = false;
2016-05-02 06:00:26 -04:00
bool uturn_could_be_valid = 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)
{
2016-05-02 06:00:26 -04:00
uturn_could_be_valid = turn_is_valid;
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;
}
}
2016-05-02 06:00:26 -04:00
// is a dead-end, only possible road is to go back
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);
2016-05-02 06:00:26 -04:00
if (std::abs(angle) < std::numeric_limits<double>::epsilon())
has_uturn_edge = true;
}
2016-06-15 08:38:24 -04:00
intersection.push_back(
ConnectedRoad(TurnOperation{onto_edge,
angle,
{TurnType::Invalid, DirectionModifier::UTurn},
INVALID_LANE_DATAID},
turn_is_valid));
}
// We hit the case of a street leading into nothing-ness. Since the code here assumes that this
2016-05-02 06:00:26 -04:00
// will never happen we add an artificial invalid uturn in this case.
if (!has_uturn_edge)
{
intersection.push_back(
2016-06-15 08:38:24 -04:00
{TurnOperation{
via_eid, 0., {TurnType::Invalid, DirectionModifier::UTurn}, INVALID_LANE_DATAID},
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());
2016-05-02 06:00:26 -04:00
const auto valid_count =
boost::count_if(intersection, [](const ConnectedRoad &road) { return road.entry_allowed; });
if (0 == valid_count && uturn_could_be_valid)
{
// after intersections sorting by angles, find the u-turn with (from_node == to_node)
// that was inserted together with setting uturn_could_be_valid flag
std::size_t self_u_turn = 0;
2016-08-11 03:44:30 -04:00
while (self_u_turn < intersection.size() &&
intersection[self_u_turn].turn.angle < std::numeric_limits<double>::epsilon() &&
from_node != node_based_graph.GetTarget(intersection[self_u_turn].turn.eid))
{
++self_u_turn;
}
BOOST_ASSERT(from_node == node_based_graph.GetTarget(intersection[self_u_turn].turn.eid));
intersection[self_u_turn].entry_allowed = true;
}
2016-05-02 06:00:26 -04:00
2016-08-11 07:32:41 -04:00
return intersection;
}
2016-08-15 10:55:03 -04:00
// Checks for mergability of two ways that represent the same intersection. For further information
// see interface documentation in header.
2016-08-11 08:21:34 -04:00
bool IntersectionGenerator::CanMerge(const NodeID node_at_intersection,
const Intersection &intersection,
2016-08-11 06:44:10 -04:00
std::size_t first_index,
std::size_t second_index) const
{
const auto &first_data = node_based_graph.GetEdgeData(intersection[first_index].turn.eid);
const auto &second_data = node_based_graph.GetEdgeData(intersection[second_index].turn.eid);
// only merge named ids
if (first_data.name_id == EMPTY_NAMEID)
return false;
// need to be same name
if (first_data.name_id != second_data.name_id)
return false;
// compatibility is required
if (first_data.travel_mode != second_data.travel_mode)
return false;
if (first_data.road_classification != second_data.road_classification)
return false;
2016-08-11 07:32:41 -04:00
// may not be on a roundabout
if (first_data.roundabout || second_data.roundabout)
2016-08-11 06:44:10 -04:00
return false;
// exactly one of them has to be reversed
if (first_data.reversed == second_data.reversed)
return false;
2016-08-11 08:21:34 -04:00
// one of them needs to be invalid
if (intersection[first_index].entry_allowed && intersection[second_index].entry_allowed)
return false;
2016-08-11 06:44:10 -04:00
// mergeable if the angle is not too big
2016-08-11 08:21:34 -04:00
const auto angle_between = angularDeviation(intersection[first_index].turn.angle,
intersection[second_index].turn.angle);
const auto coordinate_at_in_edge =
getRepresentativeCoordinate(node_at_intersection,
node_based_graph.GetTarget(intersection[0].turn.eid),
intersection[0].turn.eid,
false,
compressed_edge_container,
node_info_list);
const auto coordinate_at_intersection = node_info_list[node_at_intersection];
const auto isValidYArm = [this,
intersection,
coordinate_at_in_edge,
coordinate_at_intersection,
node_at_intersection](const std::size_t index,
const std::size_t other_index) {
2016-08-15 06:43:26 -04:00
const auto GetActualTarget = [&](const std::size_t index) {
2016-08-11 08:21:34 -04:00
EdgeID last_in_edge_id;
GetActualNextIntersection(
node_at_intersection, intersection[index].turn.eid, nullptr, &last_in_edge_id);
return node_based_graph.GetTarget(last_in_edge_id);
};
const auto target_id = GetActualTarget(index);
const auto other_target_id = GetActualTarget(other_index);
2016-08-15 10:55:03 -04:00
if (target_id == node_at_intersection || other_target_id == node_at_intersection)
return false;
2016-08-11 08:21:34 -04:00
const auto coordinate_at_target = node_info_list[target_id];
const auto coordinate_at_other_target = node_info_list[other_target_id];
2016-08-11 08:21:34 -04:00
const auto turn_angle = util::coordinate_calculation::computeAngle(
coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_target);
const auto other_turn_angle = util::coordinate_calculation::computeAngle(
coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_other_target);
2016-08-11 08:21:34 -04:00
const double distance_to_target = util::coordinate_calculation::haversineDistance(
coordinate_at_intersection, coordinate_at_target);
2016-08-15 06:43:26 -04:00
const constexpr double MAX_COLLAPSE_DISTANCE = 30;
if (distance_to_target < MAX_COLLAPSE_DISTANCE)
return false;
2016-08-11 08:21:34 -04:00
const bool becomes_narrower =
angularDeviation(turn_angle, other_turn_angle) < NARROW_TURN_ANGLE &&
angularDeviation(turn_angle, other_turn_angle) <
angularDeviation(intersection[index].turn.angle,
intersection[other_index].turn.angle);
2016-08-11 08:21:34 -04:00
return becomes_narrower;
};
// Only merge valid y-arms
if (!isValidYArm(first_index, second_index) || !isValidYArm(second_index, first_index))
return false;
if (angle_between < 60)
return true;
// Finally, we also allow merging if all streets offer the same name, it is only three roads and
// the angle is not fully extreme:
if (intersection.size() != 3)
return false;
2016-08-15 10:55:03 -04:00
// since we have an intersection of size three now, there is only one index we are not looking
// at right now. The final index in the intersection is calculated next:
const std::size_t third_index = [first_index, second_index]() {
2016-08-11 08:21:34 -04:00
if (first_index == 0)
return second_index == 2 ? 1 : 2;
else if (first_index == 1)
return second_index == 2 ? 0 : 2;
else
return second_index == 1 ? 0 : 1;
}();
// needs to be same road coming in
2016-08-15 10:55:03 -04:00
if (node_based_graph.GetEdgeData(intersection[third_index].turn.eid).name_id !=
2016-08-11 08:21:34 -04:00
first_data.name_id)
return false;
2016-08-15 10:55:03 -04:00
// we only allow collapsing of a Y like fork. So the angle to the third index has to be
2016-08-11 08:21:34 -04:00
// roughly equal:
const auto y_angle_difference =
2016-08-15 10:55:03 -04:00
angularDeviation(angularDeviation(intersection[third_index].turn.angle,
2016-08-11 08:21:34 -04:00
intersection[first_index].turn.angle),
2016-08-15 10:55:03 -04:00
angularDeviation(intersection[third_index].turn.angle,
2016-08-11 08:21:34 -04:00
intersection[second_index].turn.angle));
// Allow larger angles if its three roads only of the same name
2016-08-15 10:55:03 -04:00
// This is a heuristic and might need to be revised.
const bool assume_y_intersection =
2016-08-11 08:21:34 -04:00
angle_between < 100 && y_angle_difference < FUZZY_ANGLE_DIFFERENCE;
2016-08-15 10:55:03 -04:00
return assume_y_intersection;
2016-08-11 06:44:10 -04:00
}
/*
* 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.
*/
2016-08-11 08:21:34 -04:00
Intersection IntersectionGenerator::MergeSegregatedRoads(const NodeID intersection_node,
Intersection intersection) const
{
const auto getRight = [&](std::size_t index) {
return (index + intersection.size() - 1) % intersection.size();
};
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;
2016-05-02 06:00:26 -04:00
const bool is_connected_to_roundabout = [this, &intersection]() {
2016-04-15 11:18:55 -04:00
for (const auto &road : intersection)
{
if (node_based_graph.GetEdgeData(road.turn.eid).roundabout)
return true;
}
return false;
}();
// check for merges including the basic u-turn
2016-08-11 03:44:30 -04:00
// these result in an adjustment of all other angles. This is due to how these angles are
// perceived. Considering the following example:
//
// c b
// Y
// a
//
// coming from a to b (given a road that splits at the fork into two one-ways), the turn is not
// considered as a turn but rather as going straight.
// Now if we look at the situation merging:
//
// a b
// \ /
// e - + - d
// |
// c
//
// With a,b representing the same road, the intersection itself represents a classif for way
// intersection so we handle it like
//
// (a),b
// |
// e - + - d
// |
// c
//
// To be able to consider this adjusted representation down the line, we merge some roads.
// If the merge occurs at the u-turn edge, we need to adjust all angles, though, since they are
// with respect to the now changed perceived location of a. If we move (a) to the left, we add
// the difference to all angles. Otherwise we subtract it.
bool merged_first = false;
2016-08-11 06:44:10 -04:00
// these result in an adjustment of all other angles
2016-08-11 08:21:34 -04:00
if (CanMerge(intersection_node, intersection, 0, intersection.size() - 1))
{
2016-08-11 03:44:30 -04:00
merged_first = true;
// moving `a` to the left
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;
2016-04-15 11:18:55 -04:00
// FIXME if we have a left-sided country, we need to switch this off and enable it below
intersection[0] = merge(intersection.front(), intersection.back());
intersection[0].turn.angle = 0;
2016-04-15 11:18:55 -04:00
intersection.pop_back();
}
2016-08-11 08:21:34 -04:00
else if (CanMerge(intersection_node, intersection, 0, 1))
{
2016-08-11 03:44:30 -04:00
merged_first = true;
// moving `a` to the right
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);
}
2016-08-11 03:44:30 -04:00
if (merged_first && is_connected_to_roundabout)
{
/*
* We are merging a u-turn against the direction of a roundabout
*
* -----------> roundabout
* / \
* out in
*
* These cases have to be disabled, even if they are not forbidden specifically by a
* relation
*/
intersection[0].entry_allowed = false;
}
// 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)
{
2016-08-11 08:21:34 -04:00
if (CanMerge(intersection_node, intersection, 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;
}
2016-08-11 06:44:10 -04:00
// OSM can have some very steep angles for joining roads. Considering the following intersection:
// x
2016-08-15 10:55:03 -04:00
// |
// v __________c
2016-08-11 06:44:10 -04:00
// /
// a ---d
// \ __________b
//
// with c->d as a oneway
// and d->b as a oneway, the turn von x->d is actually a turn from x->a. So when looking at the
// intersection coming from x, we want to interpret the situation as
// x
2016-08-15 10:55:03 -04:00
// |
// a __ d __ v__________c
2016-08-11 06:44:10 -04:00
// |
// |_______________b
//
// Where we see the turn to `d` as a right turn, rather than going straight.
// We do this by adjusting the local turn angle at `x` to turn onto `d` to be reflective of this
2016-08-15 10:55:03 -04:00
// situation, where `v` would be the node at the intersection.
2016-08-11 08:21:34 -04:00
Intersection IntersectionGenerator::AdjustForJoiningRoads(const NodeID node_at_intersection,
2016-08-11 06:44:10 -04:00
Intersection intersection) const
{
2016-08-11 07:32:41 -04:00
// nothing to do for dead ends
2016-08-11 08:21:34 -04:00
if (intersection.size() <= 1)
2016-08-11 07:32:41 -04:00
return intersection;
2016-08-15 10:55:03 -04:00
const util::Coordinate coordinate_at_intersection = node_info_list[node_at_intersection];
// never adjust u-turns
for (std::size_t index = 1; index < intersection.size(); ++index)
2016-08-11 06:44:10 -04:00
{
2016-08-15 10:55:03 -04:00
auto &road = intersection[index];
2016-08-11 06:44:10 -04:00
// to find out about the above situation, we need to look at the next intersection (at d in
// the example). If the initial road can be merged to the left/right, we are about to adjust
// the angle.
const auto next_intersection_along_road =
2016-08-11 08:21:34 -04:00
GetConnectedRoads(node_at_intersection, road.turn.eid);
2016-08-11 06:44:10 -04:00
if (next_intersection_along_road.size() <= 1)
continue;
2016-08-15 10:55:03 -04:00
const auto node_at_next_intersection = node_based_graph.GetTarget(road.turn.eid);
const util::Coordinate coordinate_at_next_intersection =
node_info_list[node_at_next_intersection];
if (util::coordinate_calculation::haversineDistance(coordinate_at_intersection,
coordinate_at_next_intersection) > 30)
continue;
2016-08-11 07:32:41 -04:00
const auto adjustAngle = [](double angle, double offset) {
angle += offset;
if (angle > 360)
return angle - 360.;
else if (angle < 0)
return angle + 360.;
return angle;
};
2016-08-15 10:55:03 -04:00
// check if the u-turn edge at the next intersection could be merged to the left/right. If
// this is the case and the road is not far away (see previous distance check), if
// influences the perceived angle.
if (CanMerge(node_at_next_intersection, next_intersection_along_road, 0, 1))
2016-08-11 06:44:10 -04:00
{
2016-08-11 07:32:41 -04:00
const auto offset = 0.5 * angularDeviation(next_intersection_along_road[0].turn.angle,
next_intersection_along_road[1].turn.angle);
// at the target intersection, we merge to the right, so we need to shift the current
// angle to the left
road.turn.angle = adjustAngle(road.turn.angle, offset);
2016-08-11 06:44:10 -04:00
}
2016-08-15 10:55:03 -04:00
else if (CanMerge(node_at_next_intersection,
2016-08-11 08:21:34 -04:00
next_intersection_along_road,
0,
next_intersection_along_road.size() - 1))
2016-08-11 06:44:10 -04:00
{
2016-08-11 07:32:41 -04:00
const auto offset =
0.5 * angularDeviation(
next_intersection_along_road[0].turn.angle,
next_intersection_along_road[next_intersection_along_road.size() - 1]
.turn.angle);
// at the target intersection, we merge to the left, so we need to shift the current
// angle to the right
road.turn.angle = adjustAngle(road.turn.angle, -offset);
2016-08-11 06:44:10 -04:00
}
}
return intersection;
}
2016-08-15 06:43:26 -04:00
Intersection
2016-08-11 08:21:34 -04:00
IntersectionGenerator::GetActualNextIntersection(const NodeID starting_node,
const EdgeID via_edge,
NodeID *resulting_from_node = nullptr,
EdgeID *resulting_via_edge = nullptr) const
{
// This function skips over traffic lights/graph compression issues and similar to find the next
// actual intersection
2016-08-15 10:55:03 -04:00
Intersection result = GetConnectedRoads(starting_node, via_edge);
2016-08-11 08:21:34 -04:00
// Skip over stuff that has not been compressed due to barriers/parallel edges
NodeID node_at_intersection = starting_node;
EdgeID incoming_edge = via_edge;
2016-09-05 04:06:50 -04:00
// to prevent endless loops
const auto termination_node = node_based_graph.GetTarget(via_edge);
2016-08-15 10:55:03 -04:00
while (result.size() == 2 &&
node_based_graph.GetEdgeData(via_edge).IsCompatibleTo(
node_based_graph.GetEdgeData(result[1].turn.eid)))
2016-08-11 08:21:34 -04:00
{
node_at_intersection = node_based_graph.GetTarget(incoming_edge);
2016-08-15 10:55:03 -04:00
incoming_edge = result[1].turn.eid;
result = GetConnectedRoads(node_at_intersection, incoming_edge);
2016-09-05 04:06:50 -04:00
// When looping back to the original node, we obviously are in a loop. Stop there.
if (termination_node == node_based_graph.GetTarget(incoming_edge))
break;
2016-08-11 08:21:34 -04:00
}
// return output if requested
if (resulting_from_node)
*resulting_from_node = node_at_intersection;
if (resulting_via_edge)
*resulting_via_edge = incoming_edge;
2016-08-15 10:55:03 -04:00
return result;
2016-08-11 08:21:34 -04:00
}
} // namespace guidance
} // namespace extractor
} // namespace osrm