diff --git a/CHANGELOG.md b/CHANGELOG.md index b05d0f34d57..dd255e6f00e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - `osrm-routed` accepts a new property `--memory_file` to store memory in a file on disk. - NodeJS: - `OSRM` object accepts a new option `memory_file` that stores the memory in a file on disk. + - Internals + - CHANGED #4845: Updated segregated intersection identification # 5.16.0 diff --git a/features/guidance/internal-intersections.feature b/features/guidance/internal-intersections.feature new file mode 100644 index 00000000000..887e11c50bf --- /dev/null +++ b/features/guidance/internal-intersections.feature @@ -0,0 +1,46 @@ +@guidance +Feature: Internal Intersection Model + + Background: + Given the profile "car" + Given a grid size of 10 meters + + Scenario: Dual-carriage way intersection + Given the node map + """ + a b + | | + c--d--e--f + | | + g--h--i--j + | | + k l + """ + + And the ways + | nodes | oneway | name | + | adhk | yes | Broken Land Parkway | + | lieb | yes | Broken Land Parkway | + | fed | yes | Snowden River Parkway | + | dc | yes | Patuxent Woods Drive | + | gh | yes | Patuxent Woods Drive | + | hij | yes | Snowden River Parkway | + + When I route I should get + | waypoints | route | turns | # | + | a,k | Broken Land Parkway,Broken Land Parkway | depart,arrive || + | l,b | Broken Land Parkway,Broken Land Parkway | depart,arrive || +# | g,j | Patuxent Woods Drive,Snowden River Parkway,Snowden River Parkway | depart,continue,arrive | did not work as expected - might be another issue to handle in post process? | +# | f,c | Snowden River Parkway,Patuxent Woods Drive,Patuxent Woods Drive | depart,continue,arrive | did not work as expected - might be another issue to handle in post process? | + | a,c | Broken Land Parkway,Patuxent Woods Drive,Patuxent Woods Drive | depart,turn right,arrive || + | g,k | Patuxent Woods Drive,Broken Land Parkway,Broken Land Parkway | depart,turn right,arrive || + | l,j | Broken Land Parkway,Snowden River Parkway,Snowden River Parkway | depart,turn right,arrive || + | f,b | Snowden River Parkway,Broken Land Parkway,Broken Land Parkway | depart,turn right,arrive || + | a,j | Broken Land Parkway,Snowden River Parkway,Snowden River Parkway | depart,turn left,arrive || + | g,b | Patuxent Woods Drive,Broken Land Parkway,Broken Land Parkway | depart,turn left,arrive || + | l,c | Broken Land Parkway,Patuxent Woods Drive,Patuxent Woods Drive | depart,turn left,arrive || + | f,k | Snowden River Parkway,Broken Land Parkway,Broken Land Parkway | depart,turn left,arrive || + | a,b | Broken Land Parkway,Broken Land Parkway,Broken Land Parkway | depart,continue uturn,arrive || + | g,c | Patuxent Woods Drive,Patuxent Woods Drive,Patuxent Woods Drive | depart,continue uturn,arrive || + | l,k | Broken Land Parkway,Broken Land Parkway,Broken Land Parkway | depart,continue uturn,arrive || + | f,j | Snowden River Parkway,Snowden River Parkway,Snowden River Parkway | depart,continue uturn,arrive || diff --git a/src/guidance/segregated_intersection_classification.cpp b/src/guidance/segregated_intersection_classification.cpp index b7f891a7ba5..50df992884c 100644 --- a/src/guidance/segregated_intersection_classification.cpp +++ b/src/guidance/segregated_intersection_classification.cpp @@ -1,29 +1,39 @@ #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 "util/name_table.hpp" +#include + +using osrm::guidance::getTurnDirection; namespace osrm { namespace guidance { -namespace RoadPriorityClass = extractor::RoadPriorityClass; +// 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; - // 0 - outgoing (forward), 1 - incoming (reverse), 2 - both outgoing and incoming - int direction; + bool reversed; extractor::ClassData road_class; - RoadPriorityClass::Enum road_priority_class; + extractor::NodeBasedEdgeClassification flags; struct LessName { @@ -31,112 +41,19 @@ struct EdgeInfo }; }; -bool IsSegregated(std::vector v1, - std::vector v2, - EdgeInfo const ¤t, - double edgeLength) -{ - if (v1.size() < 2 || v2.size() < 2) - return false; - - auto const sort_by_name_fn = [](std::vector &v) { - std::sort(v.begin(), v.end(), EdgeInfo::LessName()); - }; - - sort_by_name_fn(v1); - sort_by_name_fn(v2); - - // Internal edge with the name should be connected with any other neibour edge with the same - // name, e.g. isolated edge with unique name is not segregated. - // b - 'b' road continues here - // | - // - - a - | - // b - segregated edge - // - - a - | - if (!current.name.empty()) - { - auto const findNameFn = [¤t](std::vector const &v) { - return std::binary_search(v.begin(), v.end(), current, EdgeInfo::LessName()); - }; - - if (!findNameFn(v1) && !findNameFn(v2)) - return false; - } - - // set_intersection like routine to get equal result pairs - std::vector> commons; - - auto i1 = v1.begin(); - auto i2 = v2.begin(); - - while (i1 != v1.end() && i2 != v2.end()) - { - if (i1->name == i2->name) - { - if (!i1->name.empty()) - commons.push_back(std::make_pair(&(*i1), &(*i2))); - - ++i1; - ++i2; - } - else if (i1->name < i2->name) - ++i1; - else - ++i2; - } - - if (commons.size() < 2) - return false; - - auto const check_equal_class = [](std::pair const &e) { - // Or (e.first->road_class & e.second->road_class != 0) - return e.first->road_class == e.second->road_class; - }; - - size_t equal_class_count = 0; - for (auto const &e : commons) - if (check_equal_class(e)) - ++equal_class_count; - - if (equal_class_count < 2) - return false; - - auto const get_length_threshold = [](EdgeInfo const *e) { - switch (e->road_priority_class) - { - case RoadPriorityClass::MOTORWAY: - case RoadPriorityClass::TRUNK: - return 30.0; - case RoadPriorityClass::PRIMARY: - return 20.0; - case RoadPriorityClass::SECONDARY: - case RoadPriorityClass::TERTIARY: - return 10.0; - default: - return 5.0; - } - }; - - double threshold = std::numeric_limits::max(); - for (auto const &e : commons) - threshold = - std::min(threshold, get_length_threshold(e.first) + get_length_threshold(e.second)); - - return edgeLength <= threshold; -} - std::unordered_set findSegregatedNodes(const extractor::NodeBasedGraphFactory &factory, const util::NameTable &names) { - auto const &graph = factory.GetGraph(); auto const &annotation = factory.GetAnnotationData(); + auto const &coordinates = factory.GetCoordinates(); extractor::intersection::CoordinateExtractor coordExtractor( - graph, factory.GetCompressedEdges(), factory.GetCoordinates()); + graph, factory.GetCompressedEdges(), coordinates); - auto const get_edge_length = [&](NodeID from_node, EdgeID edgeID, NodeID to_node) { - auto const geom = coordExtractor.GetCoordinatesAlongRoad(from_node, edgeID, false, to_node); + 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) { @@ -145,18 +62,166 @@ std::unordered_set findSegregatedNodes(const extractor::NodeBasedGraphFa return length; }; - auto const get_edge_info = [&](NodeID node, auto const &edgeData) -> EdgeInfo { + // 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[edgeData.annotation_data].name_id; + auto const id = annotation[edge_data.annotation_data].name_id; BOOST_ASSERT(id != INVALID_NAMEID); auto const name = names.GetNameForID(id); - - return {node, + return {edge_id, + node, name, - edgeData.reversed ? 1 : 0, - annotation[edgeData.annotation_data].classes, - edgeData.flags.road_classification.GetClass()}; + 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 &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 &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 v1, + std::vector v2, + EdgeInfo const ¤t, + 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 lamda + if (edge_length > INTERNAL_LENGTH_MAX || current.flags.roundabout) + { + return false; + } + // Iterate through inbound edges and get turn degrees from driveable inbound + // edges onto the candidate edge. + bool oneway_inbound = false; + std::set 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 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) { @@ -168,7 +233,7 @@ std::unordered_set findSegregatedNodes(const extractor::NodeBasedGraphFa if (target == node2) continue; - info.push_back(get_edge_info(target, graph.GetEdgeData(e))); + info.push_back(get_edge_info(e, target, graph.GetEdgeData(e))); } if (info.empty()) @@ -178,22 +243,6 @@ std::unordered_set findSegregatedNodes(const extractor::NodeBasedGraphFa return e1.node < e2.node; }); - // Merge equal infos with correct direction. - auto curr = info.begin(); - auto next = curr; - while (++next != info.end()) - { - if (curr->node == next->node) - { - BOOST_ASSERT(curr->name == next->name); - BOOST_ASSERT(curr->road_class == next->road_class); - BOOST_ASSERT(curr->direction != next->direction); - curr->direction = 2; - } - else - curr = next; - } - info.erase( std::unique(info.begin(), info.end(), @@ -203,36 +252,39 @@ std::unordered_set findSegregatedNodes(const extractor::NodeBasedGraphFa return info; }; - auto const isSegregatedFn = [&](auto const &edgeData, + auto const isSegregatedFn = [&](EdgeID edge_id, + auto const &edge_data, auto const &edges1, NodeID node1, auto const &edges2, NodeID node2, - double edgeLength) { - return IsSegregated(collect_edge_info_fn(edges1, node2), + double edge_length) { + return isSegregated(node1, + collect_edge_info_fn(edges1, node2), collect_edge_info_fn(edges2, node1), - get_edge_info(node1, edgeData), - edgeLength); + get_edge_info(edge_id, node1, edge_data), + edge_length); }; std::unordered_set segregated_edges; - for (NodeID sourceID = 0; sourceID < graph.GetNumberOfNodes(); ++sourceID) + for (NodeID source_id = 0; source_id < graph.GetNumberOfNodes(); ++source_id) { - auto const sourceEdges = graph.GetAdjacentEdgeRange(sourceID); - for (EdgeID edgeID : sourceEdges) + auto const source_edges = graph.GetAdjacentEdgeRange(source_id); + for (EdgeID edge_id : source_edges) { - auto const &edgeData = graph.GetEdgeData(edgeID); + auto const &edgeData = graph.GetEdgeData(edge_id); if (edgeData.reversed) continue; - NodeID const targetID = graph.GetTarget(edgeID); - auto const targetEdges = graph.GetAdjacentEdgeRange(targetID); + NodeID const target_id = graph.GetTarget(edge_id); + auto const targetEdges = graph.GetAdjacentEdgeRange(target_id); - double const length = get_edge_length(sourceID, edgeID, targetID); - if (isSegregatedFn(edgeData, sourceEdges, sourceID, targetEdges, targetID, length)) - segregated_edges.insert(edgeID); + 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); } } diff --git a/test/nodejs/constants.js b/test/nodejs/constants.js index 370b7f6d3cc..b14bd367417 100644 --- a/test/nodejs/constants.js +++ b/test/nodejs/constants.js @@ -10,7 +10,7 @@ exports.three_test_coordinates = [[7.41337, 43.72956], exports.two_test_coordinates = exports.three_test_coordinates.slice(0, 2) -exports.test_tile = {'at': [17059, 11948, 15], 'size': 169387}; +exports.test_tile = {'at': [17059, 11948, 15], 'size': 168612}; // Test files generated by the routing engine; check test/data if (process.env.OSRM_DATA_PATH !== undefined) {