osrm-backend/src/guidance/segregated_intersection_classification.cpp
2018-03-26 11:02:04 +00:00

295 lines
11 KiB
C++

#include "guidance/segregated_intersection_classification.hpp"
#include "extractor/intersection/coordinate_extractor.hpp"
#include "extractor/node_based_graph_factory.hpp"
#include "guidance/turn_instruction.hpp"
#include "util/coordinate_calculation.hpp"
#include <set>
using osrm::guidance::getTurnDirection;
namespace osrm
{
namespace guidance
{
// Maximum length in meters of an internal intersection edge
constexpr auto INTERNAL_LENGTH_MAX = 32.0f;
// The lower and upper bound internal straight values
constexpr auto INTERNAL_STRAIGHT_LOWER_BOUND = 150.0;
constexpr auto INTERNAL_STRAIGHT_UPPER_BOUND = 210.0;
struct EdgeInfo
{
EdgeID edge;
NodeID node;
util::StringView name;
bool reversed;
extractor::ClassData road_class;
extractor::NodeBasedEdgeClassification flags;
struct LessName
{
bool operator()(EdgeInfo const &e1, EdgeInfo const &e2) const { return e1.name < e2.name; }
};
};
std::unordered_set<EdgeID> findSegregatedNodes(const extractor::NodeBasedGraphFactory &factory,
const extractor::NameTable &names)
{
auto const &graph = factory.GetGraph();
auto const &annotation = factory.GetAnnotationData();
auto const &coordinates = factory.GetCoordinates();
extractor::intersection::CoordinateExtractor coordExtractor(
graph, factory.GetCompressedEdges(), coordinates);
auto const get_edge_length = [&](NodeID from_node, EdgeID edge_id, NodeID to_node) {
auto const geom =
coordExtractor.GetCoordinatesAlongRoad(from_node, edge_id, false, to_node);
double length = 0.0;
for (size_t i = 1; i < geom.size(); ++i)
{
length += util::coordinate_calculation::haversineDistance(geom[i - 1], geom[i]);
}
return length;
};
// Returns an angle between edges from from_edge_id to to_edge_id
auto const get_angle = [&](NodeID from_node, EdgeID from_edge_id, EdgeID to_edge_id) {
auto intersection_node = graph.GetTarget(from_edge_id);
auto from_edge_id_outgoing = graph.FindEdge(intersection_node, from_node);
auto to_node = graph.GetTarget(to_edge_id);
auto const node_to =
coordExtractor.GetCoordinateCloseToTurn(intersection_node, to_edge_id, false, to_node);
auto const node_from = coordExtractor.GetCoordinateCloseToTurn(
intersection_node, from_edge_id_outgoing, false, from_node);
return util::coordinate_calculation::computeAngle(
node_from, coordinates[intersection_node], node_to);
};
auto const get_edge_info = [&](EdgeID edge_id, NodeID node, auto const &edge_data) -> EdgeInfo {
/// @todo Make string normalization/lowercase/trim for comparison ...
auto const id = annotation[edge_data.annotation_data].name_id;
BOOST_ASSERT(id != INVALID_NAMEID);
auto const name = names.GetNameForID(id);
return {edge_id,
node,
name,
edge_data.reversed,
annotation[edge_data.annotation_data].classes,
edge_data.flags};
};
auto is_bidirectional = [](auto flags) {
return flags.is_split || (!flags.is_split && flags.forward && flags.backward);
};
auto is_internal_straight = [](auto const turn_degree) {
return (turn_degree > INTERNAL_STRAIGHT_LOWER_BOUND &&
turn_degree < INTERNAL_STRAIGHT_UPPER_BOUND);
};
// Lambda to check if the turn set includes a right turn type
const auto has_turn_right = [](std::set<guidance::DirectionModifier::Enum> &turn_types) {
return turn_types.find(guidance::DirectionModifier::Right) != turn_types.end() ||
turn_types.find(guidance::DirectionModifier::SharpRight) != turn_types.end();
};
// Lambda to check if the turn set includes a left turn type
const auto has_turn_left = [](std::set<guidance::DirectionModifier::Enum> &turn_types) {
return turn_types.find(guidance::DirectionModifier::Left) != turn_types.end() ||
turn_types.find(guidance::DirectionModifier::SharpLeft) != turn_types.end();
};
auto isSegregated = [&](NodeID node1,
std::vector<EdgeInfo> v1,
std::vector<EdgeInfo> v2,
EdgeInfo const &current,
double edge_length) {
// Internal intersection edges must be short and cannot be a roundabout.
// Also they must be a road use (not footway, cycleway, etc.)
// TODO - consider whether alleys, cul-de-sacs, and other road uses
// are candidates to be marked as internal intersection edges.
// TODO adjust length as needed with lambda
if (edge_length > INTERNAL_LENGTH_MAX || current.flags.roundabout || current.flags.circular)
{
return false;
}
// Iterate through inbound edges and get turn degrees from driveable inbound
// edges onto the candidate edge.
bool oneway_inbound = false;
std::set<guidance::DirectionModifier::Enum> incoming_turn_type;
for (auto const &edge_from : v1)
{
// Get the inbound edge and edge data
auto edge_inbound = graph.FindEdge(edge_from.node, node1);
auto const &edge_inbound_data = graph.GetEdgeData(edge_inbound);
if (!edge_inbound_data.reversed)
{
// Store the turn type of incoming driveable edges.
incoming_turn_type.insert(guidance::getTurnDirection(
get_angle(edge_from.node, edge_inbound, current.edge)));
// Skip any inbound edges not oneway (i.e. skip bidirectional)
// and link edge
// and not a road
if (is_bidirectional(edge_inbound_data.flags) ||
edge_inbound_data.flags.road_classification.IsLinkClass() ||
(edge_inbound_data.flags.road_classification.GetClass() >
extractor::RoadPriorityClass::SIDE_RESIDENTIAL))
{
continue;
}
// Get the turn degree from the inbound edge to the current edge
// Skip if the inbound edge is not somewhat perpendicular to the current edge
if (is_internal_straight(get_angle(edge_from.node, edge_inbound, current.edge)))
{
continue;
}
// If we are here the edge is a candidate oneway inbound
oneway_inbound = true;
}
}
// Must have an inbound oneway, excluding edges that are nearly straight
// turn type onto the directed edge.
if (!oneway_inbound)
{
return false;
}
// Iterate through outbound edges and get turn degrees from the candidate
// edge onto outbound driveable edges.
bool oneway_outbound = false;
std::set<guidance::DirectionModifier::Enum> outgoing_turn_type;
for (auto const &edge_to : v2)
{
if (!edge_to.reversed)
{
// Store outgoing turn type for any driveable edges
outgoing_turn_type.insert(
guidance::getTurnDirection(get_angle(node1, current.edge, edge_to.edge)));
// Skip any outbound edges not oneway (i.e. skip bidirectional)
// and link edge
// and not a road
if (is_bidirectional(edge_to.flags) ||
edge_to.flags.road_classification.IsLinkClass() ||
(edge_to.flags.road_classification.GetClass() >
extractor::RoadPriorityClass::SIDE_RESIDENTIAL))
{
continue;
}
// Get the turn degree from the current edge to the outbound edge
// Skip if the outbound edge is not somewhat perpendicular to the current edge
if (is_internal_straight(get_angle(node1, current.edge, edge_to.edge)))
{
continue;
}
// If we are here the edge is a candidate oneway outbound
oneway_outbound = true;
}
}
// Must have outbound oneway at end node (exclude edges that are nearly
// straight turn from directed edge
if (!oneway_outbound)
{
return false;
}
// A further rejection case is if there are incoming edges that
// have "opposite" turn degrees than outgoing edges or if the outgoing
// edges have opposing turn degrees.
if ((has_turn_left(incoming_turn_type) && has_turn_right(outgoing_turn_type)) ||
(has_turn_right(incoming_turn_type) && has_turn_left(outgoing_turn_type)) ||
(has_turn_left(outgoing_turn_type) && has_turn_right(outgoing_turn_type)))
{
return false;
}
// TODO - determine if we need to add name checks or need to check headings
// of the inbound and outbound oneway edges
// Assume this is an intersection internal edge
return true;
};
auto const collect_edge_info_fn = [&](auto const &edges1, NodeID node2) {
std::vector<EdgeInfo> info;
for (auto const &e : edges1)
{
NodeID const target = graph.GetTarget(e);
if (target == node2)
continue;
info.push_back(get_edge_info(e, target, graph.GetEdgeData(e)));
}
if (info.empty())
return info;
std::sort(info.begin(), info.end(), [](EdgeInfo const &e1, EdgeInfo const &e2) {
return e1.node < e2.node;
});
info.erase(
std::unique(info.begin(),
info.end(),
[](EdgeInfo const &e1, EdgeInfo const &e2) { return e1.node == e2.node; }),
info.end());
return info;
};
auto const isSegregatedFn = [&](EdgeID edge_id,
auto const &edge_data,
auto const &edges1,
NodeID node1,
auto const &edges2,
NodeID node2,
double edge_length) {
return isSegregated(node1,
collect_edge_info_fn(edges1, node2),
collect_edge_info_fn(edges2, node1),
get_edge_info(edge_id, node1, edge_data),
edge_length);
};
std::unordered_set<EdgeID> segregated_edges;
for (NodeID source_id = 0; source_id < graph.GetNumberOfNodes(); ++source_id)
{
auto const source_edges = graph.GetAdjacentEdgeRange(source_id);
for (EdgeID edge_id : source_edges)
{
auto const &edgeData = graph.GetEdgeData(edge_id);
if (edgeData.reversed)
continue;
NodeID const target_id = graph.GetTarget(edge_id);
auto const targetEdges = graph.GetAdjacentEdgeRange(target_id);
double const length = get_edge_length(source_id, edge_id, target_id);
if (isSegregatedFn(
edge_id, edgeData, source_edges, source_id, targetEdges, target_id, length))
segregated_edges.insert(edge_id);
}
}
return segregated_edges;
}
} // namespace guidance
} // namespace osrm