diff --git a/features/car/conditional_restrictions.feature b/features/car/conditional_restrictions.feature index 1eb0c237154..bbc6fde88fe 100644 --- a/features/car/conditional_restrictions.feature +++ b/features/car/conditional_restrictions.feature @@ -884,7 +884,7 @@ Feature: Car - Turn restrictions | a | c | albic,dobe,dobe,albic,albic | depart,turn left,continue uturn,turn left,arrive | | a | e | albic,dobe,dobe | depart,turn left,arrive | - @no_turning @conditionals + @no_turning @conditionals @restriction-way Scenario: Car - Conditional restriction with multiple time windows Given the extract extra arguments "--parse-conditional-restrictions" # 5pm Wed 02 May, 2017 GMT @@ -1054,4 +1054,3 @@ Feature: Car - Turn restrictions | a | f | ab,be,ef,ef | depart,turn right,turn left,arrive | a,b,e,f | | c | d | bc,be,de,de | depart,turn left,turn right,arrive | c,b,e,d | | c | f | bc,be,ef,ef | depart,turn left,turn left,arrive | c,b,e,f | - diff --git a/features/car/restrictions.feature b/features/car/restrictions.feature index ff6a4365027..d9fb76adff1 100644 --- a/features/car/restrictions.feature +++ b/features/car/restrictions.feature @@ -575,7 +575,7 @@ Feature: Car - Turn restrictions | c | d | bc,be,de,de | depart,turn left,turn right,arrive | c,b,e,d | | c | f | bc,be,ef,ef | depart,turn left,turn left,arrive | c,b,e,f | - @restriction @overlap + @restriction-way @overlap Scenario: Car - prohibit turn Given the node map """ @@ -710,7 +710,7 @@ Feature: Car - Turn restrictions | a | j | left,first,right,right | | f | e | right,third,left,left | - @restriction + @restriction-way Scenario: Car - allow only turn Given the node map """ @@ -742,7 +742,7 @@ Feature: Car - Turn restrictions | c | d | bc,be,de,de | depart,turn left,turn right,arrive | c,b,e,d | | c | f | bc,be,ef,ef | depart,turn left,turn left,arrive | c,b,e,f | - @restriction + @restriction-way Scenario: Car - allow only turn Given the node map """ @@ -771,7 +771,7 @@ Feature: Car - Turn restrictions | from | to | route | | a | d | ab,be,de,de | - @restriction + @restriction-way Scenario: Multi Way restriction Given the node map """ @@ -808,7 +808,7 @@ Feature: Car - Turn restrictions | from | to | route | | a | h | horiz,vert,horiz,horiz | - @restriction + @restriction-way Scenario: Multi-Way overlapping single-way Given the node map """ @@ -847,7 +847,7 @@ Feature: Car - Turn restrictions | h | d | hfb,abcd,abcd | depart,end of road right,arrive | h,b,d | - @restriction + @restriction-way Scenario: Car - prohibit turn, traffic lights Given the node map """ @@ -890,7 +890,7 @@ Feature: Car - Turn restrictions | c | f | bc,be,ef,ef | depart,turn left,turn left,arrive | c,b,e,f | - @restriction @overlap @geometry + @restriction-way @overlap @geometry Scenario: Geometry Given the node map """ @@ -925,7 +925,7 @@ Feature: Car - Turn restrictions | c | d | bc,bge,de,de | | c | f | bc,bge,de,de,ef,ef | - @restriction @overlap @geometry @traffic-signals + @restriction-way @overlap @geometry @traffic-signals Scenario: Geometry Given the node map """ @@ -967,7 +967,7 @@ Feature: Car - Turn restrictions | c | f | bc,bge,de,de,ef,ef | # don't crash hard on invalid restrictions - @restriction @invalid + @restriction-way @invalid Scenario: Geometry Given the node map """ @@ -999,7 +999,7 @@ Feature: Car - Turn restrictions | a | f | ab,be,ef,ef | - @restriction @overlap @geometry + @restriction @restriction-way @overlap @geometry Scenario: Duplicated restriction Given the node map """ diff --git a/features/guidance/merge-segregated-roads.feature b/features/guidance/merge-segregated-roads.feature index a27862dc5c6..c7f97d55e24 100644 --- a/features/guidance/merge-segregated-roads.feature +++ b/features/guidance/merge-segregated-roads.feature @@ -394,12 +394,16 @@ Feature: Merge Segregated Roads a | b + / \ c h | | | | | | | | + | | + | | d g + \ / e | f diff --git a/features/guidance/turn-angles.feature b/features/guidance/turn-angles.feature index 54c202560cc..80973946450 100644 --- a/features/guidance/turn-angles.feature +++ b/features/guidance/turn-angles.feature @@ -961,12 +961,12 @@ Feature: Simple Turns g . . - . - . - f - h . - . . - . j + . + . + h f + . + . . + . j . . c . . . diff --git a/include/contractor/contracted_edge_container.hpp b/include/contractor/contracted_edge_container.hpp index 5cad19ae3fe..42764f7eba0 100644 --- a/include/contractor/contracted_edge_container.hpp +++ b/include/contractor/contracted_edge_container.hpp @@ -108,7 +108,7 @@ struct ContractedEdgeContainer edges.insert(edges.end(), new_edges.begin(), new_end); auto edges_size = edges.size(); auto new_edges_size = std::distance(new_edges.begin(), new_end); - BOOST_ASSERT(edges_size >= new_edges_size); + BOOST_ASSERT(edges_size >= static_cast(new_edges_size)); flags.resize(edges_size); std::fill(flags.begin() + edges_size - new_edges_size, flags.end(), flag); diff --git a/include/extractor/guidance/intersection_normalizer.hpp b/include/extractor/guidance/intersection_normalizer.hpp deleted file mode 100644 index 95c07f3936c..00000000000 --- a/include/extractor/guidance/intersection_normalizer.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZER_HPP_ -#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZER_HPP_ - -#include "util/attributes.hpp" -#include "util/name_table.hpp" -#include "util/typedefs.hpp" - -#include "extractor/guidance/coordinate_extractor.hpp" -#include "extractor/guidance/intersection.hpp" -#include "extractor/guidance/intersection_generator.hpp" -#include "extractor/guidance/intersection_normalization_operation.hpp" -#include "extractor/guidance/mergable_road_detector.hpp" -#include "extractor/query_node.hpp" -#include "extractor/suffix_table.hpp" - -#include -#include - -namespace osrm -{ -namespace extractor -{ -namespace guidance -{ - -/* - * An intersection is a central part in computing guidance decisions. However the model in OSM and - * the view we want to use in guidance are not necessarily the same thing. We have to account for - * some models that are chosen explicitly in OSM and that don't actually describe how a human would - * experience an intersection. - * - * For example, if a small pedestrian island is located at a traffic light right in the middle of a - * road, OSM tends to model the road as two separate ways. A human would consider these two ways a - * single road, though. In this normalizer, we try to account for these subtle differences between - * OSM data and human perception to improve our decision base for guidance later on. - */ -class IntersectionNormalizer -{ - public: - struct NormalizationResult - { - IntersectionShape normalized_shape; - std::vector performed_merges; - }; - IntersectionNormalizer(const util::NodeBasedDynamicGraph &node_based_graph, - const EdgeBasedNodeDataContainer &node_data_container, - const std::vector &node_coordinates, - const util::NameTable &name_table, - const SuffixTable &street_name_suffix_table, - const IntersectionGenerator &intersection_generator); - - // The function takes an intersection an converts it to a `perceived` intersection which closer - // represents how a human might experience the intersection - OSRM_ATTR_WARN_UNUSED - NormalizationResult operator()(const NodeID node_at_intersection, - IntersectionShape intersection) const; - - private: - const util::NodeBasedDynamicGraph &node_based_graph; - const IntersectionGenerator &intersection_generator; - const MergableRoadDetector mergable_road_detector; - - /* check if two indices in an intersection can be seen as a single road in the perceived - * intersection representation. See below for an example. Utility function for - * MergeSegregatedRoads. It also checks for neighboring merges. - * This is due possible segments where multiple roads could end up being merged into one. - * We only support merging two roads, not three or more, though. - * c c - * / / - * a - b -> a - b - (c,d) but not a - b d -> a,b,(cde) - * \ \ - * d e - */ - bool CanMerge(const NodeID intersection_node, - const IntersectionShape &intersection, - std::size_t first_index, - std::size_t second_index) const; - - // Perform an Actual Merge - IntersectionNormalizationOperation - DetermineMergeDirection(const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs) const; - IntersectionShapeData MergeRoads(const IntersectionShapeData &destination, - const IntersectionShapeData &source) const; - IntersectionShapeData MergeRoads(const IntersectionNormalizationOperation direction, - const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs, - const double opposite_bearing) const; - - // Merge segregated roads to omit invalid turns in favor of treating segregated roads as - // one. - // This function combines roads the following way: - // - // * * - // * is converted to * - // v ^ + - // v ^ + - // - // The treatment results in a straight turn angle of 180º rather than a turn angle of approx - // 160 - OSRM_ATTR_WARN_UNUSED - NormalizationResult MergeSegregatedRoads(const NodeID intersection_node, - IntersectionShape intersection) const; - - // The counterpiece to mergeSegregatedRoads. While we can adjust roads that split up at the - // intersection itself, it can also happen that intersections are connected to joining roads. - // - // * * - // * is converted to * - // v a --- a --- - // v ^ + - // v ^ + - // b - // - // for the local view of b at a. - OSRM_ATTR_WARN_UNUSED - IntersectionShape AdjustBearingsForMergeAtDestination(const NodeID node_at_intersection, - IntersectionShape intersection) const; -}; - -} // namespace guidance -} // namespace extractor -} // namespace osrm - -#endif /* OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZER_HPP_ */ diff --git a/include/extractor/guidance/roundabout_handler.hpp b/include/extractor/guidance/roundabout_handler.hpp index 72779b12ef0..3387348cd64 100644 --- a/include/extractor/guidance/roundabout_handler.hpp +++ b/include/extractor/guidance/roundabout_handler.hpp @@ -64,10 +64,6 @@ class RoundaboutHandler : public IntersectionHandler const EdgeID via_eid, const Intersection &intersection) const; - void invalidateExitAgainstDirection(const NodeID from_nid, - const EdgeID via_eid, - Intersection &intersection) const; - // decide whether we lookk at a roundabout or a rotary RoundaboutType getRoundaboutType(const NodeID nid) const; diff --git a/include/extractor/guidance/turn_analysis.hpp b/include/extractor/guidance/turn_analysis.hpp index 9f405adfb4b..4308c9ebdd7 100644 --- a/include/extractor/guidance/turn_analysis.hpp +++ b/include/extractor/guidance/turn_analysis.hpp @@ -6,7 +6,6 @@ #include "extractor/guidance/intersection.hpp" #include "extractor/guidance/intersection_generator.hpp" #include "extractor/guidance/intersection_normalization_operation.hpp" -#include "extractor/guidance/intersection_normalizer.hpp" #include "extractor/guidance/motorway_handler.hpp" #include "extractor/guidance/roundabout_handler.hpp" #include "extractor/guidance/sliproad_handler.hpp" @@ -56,22 +55,6 @@ class TurnAnalysis Intersection operator()(const NodeID node_prior_to_intersection, const EdgeID entering_via_edge) const; - /* - * Returns a normalized intersection without any assigned turn types. - * This intersection can be used as input for intersection classification, turn lane assignment - * and similar. - */ - struct ShapeResult - { - // the basic shape, containing all turns - IntersectionShape intersection_shape; - // normalized shape, merged some roads into others, adjusted bearings - // see intersection_normalizer for further explanations - IntersectionNormalizer::NormalizationResult annotated_normalized_shape; - }; - OSRM_ATTR_WARN_UNUSED - ShapeResult ComputeIntersectionShapes(const NodeID node_at_center_of_intersection) const; - // Select turn types based on the intersection shape OSRM_ATTR_WARN_UNUSED Intersection AssignTurnTypes(const NodeID from_node, @@ -83,7 +66,6 @@ class TurnAnalysis private: const util::NodeBasedDynamicGraph &node_based_graph; const IntersectionGenerator intersection_generator; - const IntersectionNormalizer intersection_normalizer; const RoundaboutHandler roundabout_handler; const MotorwayHandler motorway_handler; const TurnHandler turn_handler; diff --git a/include/extractor/intersection/intersection_analysis.hpp b/include/extractor/intersection/intersection_analysis.hpp index 529e6ba3263..3711bd02ec3 100644 --- a/include/extractor/intersection/intersection_analysis.hpp +++ b/include/extractor/intersection/intersection_analysis.hpp @@ -2,6 +2,7 @@ #define OSRM_EXTRACTOR_INTERSECTION_INTERSECTION_ANALYSIS_HPP #include "extractor/compressed_edge_container.hpp" +#include "extractor/guidance/mergable_road_detector.hpp" #include "extractor/guidance/turn_lane_types.hpp" #include "extractor/intersection/intersection_edge.hpp" #include "extractor/restriction_index.hpp" @@ -25,24 +26,36 @@ IntersectionEdges getIncomingEdges(const util::NodeBasedDynamicGraph &graph, IntersectionEdges getOutgoingEdges(const util::NodeBasedDynamicGraph &graph, const NodeID intersection); -IntersectionEdgeBearings -getIntersectionBearings(const util::NodeBasedDynamicGraph &graph, - const extractor::CompressedEdgeContainer &compressed_geometries, - const std::vector &node_coordinates, - const NodeID intersection); +std::pair> +getIntersectionGeometries(const util::NodeBasedDynamicGraph &graph, + const extractor::CompressedEdgeContainer &compressed_geometries, + const std::vector &node_coordinates, + const guidance::MergableRoadDetector &detector, + const NodeID intersection); + +guidance::IntersectionView +convertToIntersectionView(const util::NodeBasedDynamicGraph &graph, + const EdgeBasedNodeDataContainer &node_data_container, + const RestrictionMap &restriction_map, + const std::unordered_set &barrier_nodes, + const IntersectionEdgeGeometries &edge_geometries, + const guidance::TurnLanesIndexedArray &turn_lanes_data, + const IntersectionEdge &incoming_edge, + const IntersectionEdges &outgoing_edges, + const std::unordered_set &merged_edges); bool isTurnAllowed(const util::NodeBasedDynamicGraph &graph, const EdgeBasedNodeDataContainer &node_data_container, const RestrictionMap &restriction_map, const std::unordered_set &barrier_nodes, - const IntersectionEdgeBearings &bearings, + const IntersectionEdgeGeometries &geometries, const guidance::TurnLanesIndexedArray &turn_lanes_data, const IntersectionEdge &from, const IntersectionEdge &to); -double computeTurnAngle(const IntersectionEdgeBearings &bearings, - const IntersectionEdge &from, - const IntersectionEdge &to); +double findEdgeBearing(const IntersectionEdgeGeometries &geometries, const EdgeID &edge); + +double findEdgeLength(const IntersectionEdgeGeometries &geometries, const EdgeID &edge); } } } diff --git a/include/extractor/intersection/intersection_edge.hpp b/include/extractor/intersection/intersection_edge.hpp index 4529494e9d9..55620607baf 100644 --- a/include/extractor/intersection/intersection_edge.hpp +++ b/include/extractor/intersection/intersection_edge.hpp @@ -26,15 +26,17 @@ struct IntersectionEdge using IntersectionEdges = std::vector; -struct IntersectionEdgeBearing +struct IntersectionEdgeGeometry { EdgeID edge; - float bearing; + double initial_bearing; + double perceived_bearing; + double length; - bool operator<(const IntersectionEdgeBearing &other) const { return edge < other.edge; } + bool operator<(const IntersectionEdgeGeometry &other) const { return edge < other.edge; } }; -using IntersectionEdgeBearings = std::vector; +using IntersectionEdgeGeometries = std::vector; } } } diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index 3e9a57b676f..e6840d10bb2 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -441,6 +441,17 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( turn_analysis, lane_data_map); + // TODO: add a MergableRoadDetector instance, to be deleted later + guidance::CoordinateExtractor coordinate_extractor( + m_node_based_graph, m_compressed_edge_container, m_coordinates); + guidance::MergableRoadDetector mergable_road_detector(m_node_based_graph, + m_edge_based_node_container, + m_coordinates, + turn_analysis.GetIntersectionGenerator(), + coordinate_extractor, + name_table, + street_name_suffix_table); + bearing_class_by_node_based_node.resize(m_node_based_graph.GetNumberOfNodes(), std::numeric_limits::max()); @@ -543,7 +554,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( const auto node_based_edge_from, const auto node_at_center_of_intersection, const auto node_based_edge_to, - const auto &intersection, + const auto incoming_bearing, const auto &turn, const auto entry_class_id) { @@ -576,7 +587,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( TurnData turn_data = {turn.instruction, turn.lane_data_id, entry_class_id, - util::guidance::TurnBearing(intersection[0].bearing), + util::guidance::TurnBearing(incoming_bearing), util::guidance::TurnBearing(turn.bearing)}; // compute weight and duration penalties @@ -662,59 +673,20 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( node_at_center_of_intersection < end; ++node_at_center_of_intersection) { - - int new_turns = 0, old_turns = 0; - - std::cout << "=== node_at_center_of_intersection " - << node_at_center_of_intersection << "\n"; + // We capture the thread-local work in these objects, then flush + // them in a controlled manner at the end of the parallel range const auto &incoming_edges = intersection::getIncomingEdges( m_node_based_graph, node_at_center_of_intersection); const auto &outgoing_edges = intersection::getOutgoingEdges( m_node_based_graph, node_at_center_of_intersection); - const auto &edge_bearings = - intersection::getIntersectionBearings(m_node_based_graph, - m_compressed_edge_container, - m_coordinates, - node_at_center_of_intersection); - - std::cout << "=== new turns \n"; - for (const auto &incoming_edge : incoming_edges) - { - for (const auto &outgoing_edge : outgoing_edges) - { - const auto turn_angle = intersection::computeTurnAngle( - edge_bearings, incoming_edge, outgoing_edge); - - std::cout << incoming_edge.node << "," << incoming_edge.edge << " -> " - << outgoing_edge.node << "," << outgoing_edge.edge << " -> " - << m_node_based_graph.GetTarget(outgoing_edge.edge) - << " is allowed " - << intersection::isTurnAllowed(m_node_based_graph, - m_edge_based_node_container, - node_restriction_map, - m_barrier_nodes, - edge_bearings, - turn_lanes_data, - incoming_edge, - outgoing_edge) - << " angle " << turn_angle << "\n"; - - new_turns += intersection::isTurnAllowed(m_node_based_graph, - m_edge_based_node_container, - node_restriction_map, - m_barrier_nodes, - edge_bearings, - turn_lanes_data, - incoming_edge, - outgoing_edge); - } - } - - // We capture the thread-local work in these objects, then flush - // them in a controlled manner at the end of the parallel range - - const auto shape_result = - turn_analysis.ComputeIntersectionShapes(node_at_center_of_intersection); + const auto &edge_geometries_and_merged_edges = + intersection::getIntersectionGeometries(m_node_based_graph, + m_compressed_edge_container, + m_coordinates, + mergable_road_detector, + node_at_center_of_intersection); + const auto &edge_geometries = edge_geometries_and_merged_edges.first; + const auto &merged_edge_ids = edge_geometries_and_merged_edges.second; // all nodes in the graph are connected in both directions. We check all // outgoing nodes to find the incoming edge. This is a larger search overhead, @@ -734,45 +706,32 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // From the flags alone, we cannot determine which nodes are connected to // `b` by an outgoing edge. Therefore, we have to search all connected edges for // edges entering `b` - std::cout << "=== old turns \n"; - for (const EdgeID outgoing_edge : - m_node_based_graph.GetAdjacentEdgeRange(node_at_center_of_intersection)) + for (const auto &incoming_edge : incoming_edges) { - const NodeID node_along_road_entering = - m_node_based_graph.GetTarget(outgoing_edge); - - const auto incoming_edge = m_node_based_graph.FindEdge( - node_along_road_entering, node_at_center_of_intersection); - - if (m_node_based_graph.GetEdgeData(incoming_edge).reversed) - continue; - ++node_based_edge_counter; - auto intersection_with_flags_and_angles = - turn_analysis.GetIntersectionGenerator() - .TransformIntersectionShapeIntoView( - node_along_road_entering, - incoming_edge, - shape_result.annotated_normalized_shape.normalized_shape, - shape_result.intersection_shape, - shape_result.annotated_normalized_shape.performed_merges); + const auto intersection_view = + convertToIntersectionView(m_node_based_graph, + m_edge_based_node_container, + node_restriction_map, + m_barrier_nodes, + edge_geometries, + turn_lanes_data, + incoming_edge, + outgoing_edges, + merged_edge_ids); - auto intersection = - turn_analysis.AssignTurnTypes(node_along_road_entering, - incoming_edge, - intersection_with_flags_and_angles); + auto intersection = turn_analysis.AssignTurnTypes( + incoming_edge.node, incoming_edge.edge, intersection_view); OSRM_ASSERT(intersection.valid(), m_coordinates[node_at_center_of_intersection]); - intersection = turn_lane_handler.assignTurnLanes( - node_along_road_entering, incoming_edge, std::move(intersection)); + incoming_edge.node, incoming_edge.edge, std::move(intersection)); // the entry class depends on the turn, so we have to classify the - // interesction for - // every edge + // interesction for every edge const auto turn_classification = classifyIntersection( intersection, m_coordinates[node_at_center_of_intersection]); @@ -790,18 +749,31 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // check if we are turning off a via way const auto turning_off_via_way = way_restriction_map.IsViaWay( - node_along_road_entering, node_at_center_of_intersection); + incoming_edge.node, node_at_center_of_intersection); - for (const auto &turn : intersection) + // Save reversed incoming bearing to compute turn angles + const auto reversed_incoming_bearing = util::bearing::reverse( + findEdgeBearing(edge_geometries, incoming_edge.edge)); + + for (const auto &outgoing_edge : outgoing_edges) { - // only keep valid turns - if (!turn.entry_allowed) + if (!intersection::isTurnAllowed(m_node_based_graph, + m_edge_based_node_container, + node_restriction_map, + m_barrier_nodes, + edge_geometries, + turn_lanes_data, + incoming_edge, + outgoing_edge)) continue; - old_turns += 1; - std::cout << node_along_road_entering << " -> " - << node_at_center_of_intersection << " -> " - << m_node_based_graph.GetTarget(turn.eid) << "\n"; + const auto turn = + std::find_if(intersection.begin(), + intersection.end(), + [edge = outgoing_edge.edge](const auto &road) { + return road.eid == edge; + }); + BOOST_ASSERT(turn != intersection.end()); // In case a way restriction starts at a given location, add a turn onto // every artificial node eminating here. @@ -824,22 +796,22 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // duplicated node associated with the turn. (e.g. ab via bc switches bc // to bc_dup) auto const target_id = way_restriction_map.RemapIfRestricted( - nbe_to_ebn_mapping[turn.eid], - node_along_road_entering, - node_at_center_of_intersection, - m_node_based_graph.GetTarget(turn.eid), + nbe_to_ebn_mapping[outgoing_edge.edge], + incoming_edge.node, + outgoing_edge.node, + m_node_based_graph.GetTarget(outgoing_edge.edge), m_number_of_edge_based_nodes); { // scope to forget edge_with_data after const auto edge_with_data_and_condition = - generate_edge(nbe_to_ebn_mapping[incoming_edge], + generate_edge(nbe_to_ebn_mapping[incoming_edge.edge], target_id, - node_along_road_entering, - incoming_edge, - node_at_center_of_intersection, - turn.eid, - intersection, - turn, + incoming_edge.node, + incoming_edge.edge, + outgoing_edge.node, + outgoing_edge.edge, + reversed_incoming_bearing, + *turn, entry_class_id); buffer->continuous_data.edges_list.push_back( @@ -866,7 +838,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( if (turning_off_via_way) { const auto duplicated_nodes = way_restriction_map.DuplicatedNodeIDs( - node_along_road_entering, node_at_center_of_intersection); + incoming_edge.node, node_at_center_of_intersection); // next to the normal restrictions tracked in `entry_allowed`, via // ways might introduce additional restrictions. These are handled @@ -874,12 +846,12 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( for (auto duplicated_node_id : duplicated_nodes) { const auto from_id = - m_number_of_edge_based_nodes - - way_restriction_map.NumberOfDuplicatedNodes() + - duplicated_node_id; + NodeID(m_number_of_edge_based_nodes - + way_restriction_map.NumberOfDuplicatedNodes() + + duplicated_node_id); auto const node_at_end_of_turn = - m_node_based_graph.GetTarget(turn.eid); + m_node_based_graph.GetTarget(outgoing_edge.edge); const auto is_way_restricted = way_restriction_map.IsRestricted( duplicated_node_id, node_at_end_of_turn); @@ -894,14 +866,14 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // add into delayed data auto edge_with_data_and_condition = - generate_edge(NodeID(from_id), - nbe_to_ebn_mapping[turn.eid], - node_along_road_entering, - incoming_edge, - node_at_center_of_intersection, - turn.eid, - intersection, - turn, + generate_edge(from_id, + nbe_to_ebn_mapping[outgoing_edge.edge], + incoming_edge.node, + incoming_edge.edge, + outgoing_edge.node, + outgoing_edge.edge, + reversed_incoming_bearing, + *turn, entry_class_id); buffer->delayed_data.push_back( @@ -918,8 +890,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( { // add a new conditional for the edge we just created buffer->conditionals.push_back( - {NodeID(from_id), - nbe_to_ebn_mapping[turn.eid], + {from_id, + nbe_to_ebn_mapping[outgoing_edge.edge], {static_cast(-1), m_coordinates[node_at_center_of_intersection], restriction.condition}}); @@ -928,14 +900,14 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( else { auto edge_with_data_and_condition = - generate_edge(NodeID(from_id), - nbe_to_ebn_mapping[turn.eid], - node_along_road_entering, - incoming_edge, - node_at_center_of_intersection, - turn.eid, - intersection, - turn, + generate_edge(from_id, + nbe_to_ebn_mapping[outgoing_edge.edge], + incoming_edge.node, + incoming_edge.edge, + outgoing_edge.node, + outgoing_edge.edge, + reversed_incoming_bearing, + *turn, entry_class_id); buffer->delayed_data.push_back( @@ -951,10 +923,6 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( } } } - - std::cout << "new_turns " << new_turns << " old_turns " << old_turns << "\n"; - OSRM_ASSERT(new_turns == old_turns, - m_coordinates[node_at_center_of_intersection]); } return buffer; diff --git a/src/extractor/guidance/intersection_normalizer.cpp b/src/extractor/guidance/intersection_normalizer.cpp deleted file mode 100644 index ab7ef07476a..00000000000 --- a/src/extractor/guidance/intersection_normalizer.cpp +++ /dev/null @@ -1,430 +0,0 @@ -#include "extractor/guidance/intersection_normalizer.hpp" -#include "util/bearing.hpp" -#include "util/coordinate_calculation.hpp" - -#include -#include - -using osrm::util::angularDeviation; - -namespace osrm -{ -namespace extractor -{ -namespace guidance -{ - -IntersectionNormalizer::IntersectionNormalizer( - const util::NodeBasedDynamicGraph &node_based_graph, - const EdgeBasedNodeDataContainer &node_data_container, - const std::vector &coordinates, - const util::NameTable &name_table, - const SuffixTable &street_name_suffix_table, - const IntersectionGenerator &intersection_generator) - : node_based_graph(node_based_graph), intersection_generator(intersection_generator), - mergable_road_detector(node_based_graph, - node_data_container, - coordinates, - intersection_generator, - intersection_generator.GetCoordinateExtractor(), - name_table, - street_name_suffix_table) -{ -} - -IntersectionNormalizer::NormalizationResult IntersectionNormalizer:: -operator()(const NodeID node_at_intersection, IntersectionShape intersection) const -{ - const auto intersection_copy = intersection; - auto merged_shape_and_merges = - MergeSegregatedRoads(node_at_intersection, std::move(intersection)); - merged_shape_and_merges.normalized_shape = AdjustBearingsForMergeAtDestination( - node_at_intersection, std::move(merged_shape_and_merges.normalized_shape)); - return merged_shape_and_merges; -} - -bool IntersectionNormalizer::CanMerge(const NodeID intersection_node, - const IntersectionShape &intersection, - std::size_t fist_index_in_ccw, - std::size_t second_index_in_ccw) const -{ - BOOST_ASSERT(((fist_index_in_ccw + 1) % intersection.size()) == second_index_in_ccw); - - // don't merge on degree two, since it's most likely a bollard/traffic light or a round way - if (intersection.size() <= 2) - return false; - - const auto can_merge = mergable_road_detector.CanMergeRoad( - intersection_node, intersection[fist_index_in_ccw], intersection[second_index_in_ccw]); - - /* - * Merging should never depend on order/never merge more than two roads. To ensure that we don't - * merge anything that is impacted by neighboring roads (e.g. three roads of the same name as in - * parking lots/border checkpoints), we check if the neigboring roads would be merged as well. - * In that case, we cannot merge, since we would end up merging multiple items together - */ - const auto is_distinct = [&]() { - const auto next_index_in_ccw = (second_index_in_ccw + 1) % intersection.size(); - const auto distinct_to_next_in_ccw = mergable_road_detector.IsDistinctFrom( - intersection[second_index_in_ccw], intersection[next_index_in_ccw]); - const auto prev_index_in_ccw = - (fist_index_in_ccw + intersection.size() - 1) % intersection.size(); - const auto distinct_to_prev_in_ccw = mergable_road_detector.IsDistinctFrom( - intersection[prev_index_in_ccw], intersection[fist_index_in_ccw]); - return distinct_to_next_in_ccw && distinct_to_prev_in_ccw; - }; - - // use lazy evaluation to check only if mergable - return can_merge && is_distinct(); -} - -IntersectionNormalizationOperation -IntersectionNormalizer::DetermineMergeDirection(const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs) const -{ - if (node_based_graph.GetEdgeData(lhs.eid).reversed) - return {lhs.eid, rhs.eid}; - else - return {rhs.eid, lhs.eid}; -} - -IntersectionShapeData IntersectionNormalizer::MergeRoads(const IntersectionShapeData &into, - const IntersectionShapeData &from) const -{ - // we only merge small angles. If the difference between both is large, we are looking at a - // bearing leading north. Such a bearing cannot be handled via the basic average. In this - // case we actually need to shift the bearing by half the difference. - const auto aroundZero = [](const double first, const double second) { - return (std::max(first, second) - std::min(first, second)) >= 180; - }; - - // find the angle between two other angles - const auto combineAngles = [aroundZero](const double first, const double second) { - if (!aroundZero(first, second)) - return .5 * (first + second); - else - { - const auto offset = angularDeviation(first, second); - auto new_angle = std::max(first, second) + .5 * offset; - if (new_angle >= 360) - return new_angle - 360; - return new_angle; - } - }; - - auto result = into; - BOOST_ASSERT(!node_based_graph.GetEdgeData(into.eid).reversed); - result.bearing = combineAngles(into.bearing, from.bearing); - BOOST_ASSERT(0 <= result.bearing && result.bearing < 360.0); - return result; -} - -IntersectionShapeData -IntersectionNormalizer::MergeRoads(const IntersectionNormalizationOperation direction, - const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs, - const double opposite_bearing) const -{ - // In some intersections, turning roads can introduce artificial turns if we merge here. - // Consider a scenario like: - //  - // a . g - f - // | . - // | . - // |. - // d-b--------e - // | - // c - //  - // Merging `bgf` and `be` would introduce an angle, even though d-b-e is perfectly straight - // We don't change the angle, if such an opposite road exists - if (direction.merged_eid == lhs.eid) - { - // change the angle only if the opposite direction is not nearly straight - if (angularDeviation(opposite_bearing, rhs.bearing) > - (STRAIGHT_ANGLE - MAXIMAL_ALLOWED_NO_TURN_DEVIATION)) - return rhs; - else - return MergeRoads(rhs, lhs); - } - else - { - if (angularDeviation(opposite_bearing, lhs.bearing) > - (STRAIGHT_ANGLE - MAXIMAL_ALLOWED_NO_TURN_DEVIATION)) - return lhs; - else - return MergeRoads(lhs, rhs); - } -} - -/* - * 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. - * - * bb>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. - */ -IntersectionNormalizer::NormalizationResult -IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node, - IntersectionShape intersection) const -{ - const auto getRight = [&](std::size_t index) { - return (index + intersection.size() - 1) % intersection.size(); - }; - - // This map stores for all edges that participated in a merging operation in which edge id they - // end up in the end. We only store what we have merged into other edges. - std::vector merging_map; - const auto merge = [this, &merging_map](const IntersectionShapeData &first, - const IntersectionShapeData &second, - const double opposite_bearing) { - - const auto direction = DetermineMergeDirection(first, second); - BOOST_ASSERT( - std::find_if(merging_map.begin(), merging_map.end(), [direction](const auto pair) { - return pair.merged_eid == direction.merged_eid; - }) == merging_map.end()); - merging_map.push_back(direction); - return MergeRoads(direction, first, second, opposite_bearing); - }; - - if (intersection.size() <= 1) - return {intersection, merging_map}; - - const auto intersection_copy = intersection; - const auto opposite_bearing = [this, intersection_copy](const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs) { - if (node_based_graph.GetEdgeData(lhs.eid).reversed) - { - return intersection_copy.FindClosestBearing(util::bearing::reverse(rhs.bearing)) - ->bearing; - } - else - { - BOOST_ASSERT(node_based_graph.GetEdgeData(rhs.eid).reversed); - return intersection_copy.FindClosestBearing(util::bearing::reverse(lhs.bearing)) - ->bearing; - } - }; - // check for merges including the basic u-turn - // 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. - // these result in an adjustment of all other angles - if (CanMerge(intersection_node, intersection, intersection.size() - 1, 0)) - { - // moving `a` to the left - const auto opposite = opposite_bearing(intersection.front(), intersection.back()); - intersection[0] = merge(intersection.front(), intersection.back(), opposite); - // FIXME if we have a left-sided country, we need to switch this off and enable it - // below - intersection.pop_back(); - } - else if (CanMerge(intersection_node, intersection, 0, 1)) - { - const auto opposite = opposite_bearing(intersection.front(), intersection[1]); - intersection[0] = merge(intersection.front(), intersection[1], opposite); - intersection.erase(intersection.begin() + 1); - } - - // a merge including the first u-turn requires 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 (CanMerge(intersection_node, intersection, getRight(index), index)) - { - const auto opposite = - opposite_bearing(intersection[getRight(index)], intersection[index]); - intersection[getRight(index)] = - merge(intersection[getRight(index)], intersection[index], opposite); - intersection.erase(intersection.begin() + index); - --index; - } - } - return {intersection, merging_map}; -} - -// OSM can have some very steep angles for joining roads. Considering the following intersection: -// x -// | -// v __________c -// / -// 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 -// | -// a __ d __ v__________c -// | -// |_______________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 -// situation, where `v` would be the node at the intersection. -IntersectionShape -IntersectionNormalizer::AdjustBearingsForMergeAtDestination(const NodeID node_at_intersection, - IntersectionShape intersection) const -{ - // nothing to do for dead ends - if (intersection.size() <= 1) - return intersection; - - // we don't adjust any road that is longer than 30 meters (between centers of intersections), - // since the road is probably too long otherwise to impact perception. - const double constexpr PRUNING_DISTANCE = 30; - // never adjust u-turns - for (std::size_t index = 0; index < intersection.size(); ++index) - { - auto &road = intersection[index]; - // only consider roads that are close - if (road.segment_length > PRUNING_DISTANCE) - continue; - - // 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 = intersection_generator.ComputeIntersectionShape( - node_based_graph.GetTarget(road.eid), node_at_intersection); - - if (next_intersection_along_road.size() <= 1) - continue; - - const auto node_at_next_intersection = node_based_graph.GetTarget(road.eid); - - const auto adjustAngle = [](double angle, double offset) { - angle += offset; - if (angle > 360) - return angle - 360.; - else if (angle < 0) - return angle + 360.; - return angle; - }; - - const auto range = node_based_graph.GetAdjacentEdgeRange(node_at_next_intersection); - if (range.size() <= 1) - continue; - - // the order does not matter - const auto get_offset = [](const IntersectionShapeData &lhs, - const IntersectionShapeData &rhs) { - return 0.5 * angularDeviation(lhs.bearing, rhs.bearing); - }; - - // When offsetting angles in our turns, we don't want to get past the next turn. This - // function simply limits an offset to be at most half the distance to the next turn in the - // offfset direction - const auto get_corrected_offset = []( - const double offset, - const IntersectionShapeData &road, - const IntersectionShapeData &next_road_in_offset_direction) { - const auto offset_limit = - angularDeviation(road.bearing, next_road_in_offset_direction.bearing); - // limit the offset with an additional buffer - return (offset + MAXIMAL_ALLOWED_NO_TURN_DEVIATION > offset_limit) ? 0.5 * offset_limit - : offset; - }; - - // only if straighmost angles get smaller, we consider it an improvement - auto const improves_straightmost = [&](auto const index, auto const offset) { - const auto itr = next_intersection_along_road.FindClosestBearing( - util::bearing::reverse(next_intersection_along_road[index].bearing)); - const auto angle = util::bearing::angleBetween( - util::bearing::reverse(itr->bearing), next_intersection_along_road[index].bearing); - - return util::angularDeviation(angle, STRAIGHT_ANGLE) > - util::angularDeviation(angle + offset, STRAIGHT_ANGLE); - }; - - // 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)) - { - const auto offset = - get_offset(next_intersection_along_road[0], next_intersection_along_road[1]); - - if (improves_straightmost(0, -offset) && improves_straightmost(1, offset)) - { - const auto corrected_offset = get_corrected_offset( - offset, - road, - intersection[(intersection.size() + index - 1) % intersection.size()]); - // at the target intersection, we merge to the right, so we need to shift the - // current - // angle to the left - road.bearing = adjustAngle(road.bearing, corrected_offset); - } - } - else if (CanMerge(node_at_next_intersection, - next_intersection_along_road, - next_intersection_along_road.size() - 1, - 0)) - { - const auto offset = - get_offset(next_intersection_along_road[0], - next_intersection_along_road[next_intersection_along_road.size() - 1]); - - if (improves_straightmost(0, offset) && - improves_straightmost(next_intersection_along_road.size() - 1, -offset)) - { - const auto corrected_offset = get_corrected_offset( - offset, road, intersection[(index + 1) % intersection.size()]); - // at the target intersection, we merge to the left, so we need to shift the current - // angle to the right - road.bearing = adjustAngle(road.bearing, -corrected_offset); - } - } - } - - return intersection; -} - -} // namespace guidance -} // namespace extractor -} // namespace osrm diff --git a/src/extractor/guidance/roundabout_handler.cpp b/src/extractor/guidance/roundabout_handler.cpp index 1aa8f595197..c947fc1a2d0 100644 --- a/src/extractor/guidance/roundabout_handler.cpp +++ b/src/extractor/guidance/roundabout_handler.cpp @@ -56,7 +56,6 @@ bool RoundaboutHandler::canProcess(const NodeID from_nid, Intersection RoundaboutHandler:: operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersection) const { - invalidateExitAgainstDirection(from_nid, via_eid, intersection); const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection); const auto roundabout_type = getRoundaboutType(node_based_graph.GetTarget(via_eid)); // find the radius of the roundabout @@ -107,77 +106,6 @@ detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags( return {on_roundabout, can_enter_roundabout, can_exit_roundabout_separately}; } -void RoundaboutHandler::invalidateExitAgainstDirection(const NodeID from_nid, - const EdgeID via_eid, - Intersection &intersection) const -{ - const auto &in_edge_class = node_based_graph.GetEdgeData(via_eid).flags; - if (in_edge_class.roundabout || in_edge_class.circular) - return; - - // Find range in which exits that must be invalidated (shaded areas): - // exit..end exit..end begin..exit for ↺ roundabouts - // ************************************* - // * <--. ^ <--. / <--. * - // * | / | /░ | * - // * |/ |v░░ -->| * - // * |^ |\ ░ ░░░|\ * - // * |░\ |░\░ ░░░| \ * - // * --'░░░\ --'░░░v --' v * - // ************************************* - // - // begin..exit begin..exit exit..end for ↻ roundabouts - // ************************************* - // * --.░░░^ --.░░░/ --. ^ * - // * |░/░ |░/ ░░░| / * - // * |/░░ |v ░░░|/ * - // * |^░░ |\ -->| * - // * | \░ | \ | * - // * <--' \ <--' v <--' * - // ************************************* - bool roundabout_entry_first = false; - auto invalidate_from = intersection.end(), invalidate_to = intersection.end(); - for (auto road = intersection.begin(); road != intersection.end(); ++road) - { - const auto &edge = node_based_graph.GetEdgeData(road->eid); - if (edge.flags.roundabout || edge.flags.circular) - { - if (edge.reversed) - { - if (roundabout_entry_first) - { // invalidate turns in range exit..end - invalidate_from = road + 1; - invalidate_to = intersection.end(); - } - else - { // invalidate turns in range begin..exit - invalidate_from = intersection.begin() + 1; - invalidate_to = road; - } - } - else - { - roundabout_entry_first = true; - } - } - } - - OSRM_ASSERT(invalidate_from <= invalidate_to, coordinates[from_nid]); - - // Exiting roundabouts at an entry point is technically a data-modelling issue. - // This workaround handles cases in which an exit precedes and entry. The resulting - // u-turn against the roundabout direction is invalidated. - for (; invalidate_from != invalidate_to; ++invalidate_from) - { - const auto &edge = node_based_graph.GetEdgeData(invalidate_from->eid); - if (!edge.flags.roundabout && !edge.flags.circular && - node_based_graph.GetTarget(invalidate_from->eid) != from_nid) - { - invalidate_from->entry_allowed = false; - } - } -} - // If we want to see a roundabout as a turn, the exits have to be distinct enough to be seen a // dedicated turns. We are limiting it to four-way intersections with well distinct bearings. // All entry/roads and exit roads have to be simple. Not segregated roads. diff --git a/src/extractor/guidance/turn_analysis.cpp b/src/extractor/guidance/turn_analysis.cpp index b687c088295..76eca2047d2 100644 --- a/src/extractor/guidance/turn_analysis.cpp +++ b/src/extractor/guidance/turn_analysis.cpp @@ -35,12 +35,6 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph, barrier_nodes, coordinates, compressed_edge_container), - intersection_normalizer(node_based_graph, - node_data_container, - coordinates, - name_table, - street_name_suffix_table, - intersection_generator), roundabout_handler(node_based_graph, node_data_container, coordinates, @@ -88,24 +82,6 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph, { } -Intersection TurnAnalysis::operator()(const NodeID node_prior_to_intersection, - const EdgeID entering_via_edge) const -{ - TurnAnalysis::ShapeResult shape_result = - ComputeIntersectionShapes(node_based_graph.GetTarget(entering_via_edge)); - - // assign valid flags to normalized_shape - const auto intersection_view = intersection_generator.TransformIntersectionShapeIntoView( - node_prior_to_intersection, - entering_via_edge, - shape_result.annotated_normalized_shape.normalized_shape, - shape_result.intersection_shape, - shape_result.annotated_normalized_shape.performed_merges); - - // assign the turn types to the intersection - return AssignTurnTypes(node_prior_to_intersection, entering_via_edge, intersection_view); -} - Intersection TurnAnalysis::AssignTurnTypes(const NodeID node_prior_to_intersection, const EdgeID entering_via_edge, const IntersectionView &intersection_view) const @@ -191,19 +167,6 @@ Intersection TurnAnalysis::AssignTurnTypes(const NodeID node_prior_to_intersecti return intersection; } -TurnAnalysis::ShapeResult -TurnAnalysis::ComputeIntersectionShapes(const NodeID node_at_center_of_intersection) const -{ - ShapeResult intersection_shape; - intersection_shape.intersection_shape = - intersection_generator.ComputeIntersectionShape(node_at_center_of_intersection); - - intersection_shape.annotated_normalized_shape = intersection_normalizer( - node_at_center_of_intersection, intersection_shape.intersection_shape); - - return intersection_shape; -} - // Sets basic turn types as fallback for otherwise unhandled turns Intersection TurnAnalysis::setTurnTypes(const NodeID node_prior_to_intersection, const EdgeID, diff --git a/src/extractor/guidance/turn_lane_matcher.cpp b/src/extractor/guidance/turn_lane_matcher.cpp index acab3930b3a..3959817c7dc 100644 --- a/src/extractor/guidance/turn_lane_matcher.cpp +++ b/src/extractor/guidance/turn_lane_matcher.cpp @@ -211,35 +211,6 @@ Intersection triviallyMatchLanesToTurns(Intersection intersection, road.lane_data_id = lane_data_to_id.ConcurrentFindOrAdd(key); }; - if (!lane_data.empty() && lane_data.front().tag == TurnLaneType::uturn) - { - // the very first is a u-turn to the right - if (intersection[0].entry_allowed) - { - std::size_t u_turn = 0; - if (node_based_graph.GetEdgeData(intersection[0].eid).reversed) - { - if (intersection.size() <= 1 || !intersection[1].entry_allowed || - intersection[1].instruction.direction_modifier != DirectionModifier::SharpRight) - { - // cannot match u-turn in a valid way - return intersection; - } - u_turn = 1; - road_index = 2; - } - intersection[u_turn].entry_allowed = true; - intersection[u_turn].instruction.type = TurnType::Continue; - intersection[u_turn].instruction.direction_modifier = DirectionModifier::UTurn; - - matchRoad(intersection[u_turn], lane_data.back()); - // continue with the first lane - lane = 1; - } - else - return intersection; - } - for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index) { if (intersection[road_index].entry_allowed) diff --git a/src/extractor/intersection/intersection_analysis.cpp b/src/extractor/intersection/intersection_analysis.cpp index 5944a17b0cd..ea644ad3788 100644 --- a/src/extractor/intersection/intersection_analysis.cpp +++ b/src/extractor/intersection/intersection_analysis.cpp @@ -1,8 +1,13 @@ #include "extractor/intersection/intersection_analysis.hpp" +#include "util/assert.hpp" #include "util/bearing.hpp" #include "util/coordinate_calculation.hpp" +#include "extractor/guidance/coordinate_extractor.hpp" + +#include + namespace osrm { namespace extractor @@ -38,7 +43,8 @@ IntersectionEdges getOutgoingEdges(const util::NodeBasedDynamicGraph &graph, for (const auto outgoing_edge : graph.GetAdjacentEdgeRange(intersection_node)) { - if (!graph.GetEdgeData(outgoing_edge).reversed) + // TODO: to use TurnAnalysis all outgoing edges are required, to be uncommented later + // if (!graph.GetEdgeData(outgoing_edge).reversed) { result.push_back({intersection_node, outgoing_edge}); } @@ -65,7 +71,7 @@ getEdgeCoordinates(const extractor::CompressedEdgeContainer &compressed_geometri // extracts the geometry in coordinates from the compressed edge container std::vector result; const auto &geometry = compressed_geometries.GetBucketReference(edge); - result.reserve(geometry.size() + 2); + result.reserve(geometry.size() + 1); result.push_back(node_coordinates[from_node]); std::transform(geometry.begin(), @@ -74,67 +80,395 @@ getEdgeCoordinates(const extractor::CompressedEdgeContainer &compressed_geometri [&node_coordinates](const auto &compressed_edge) { return node_coordinates[compressed_edge.node_id]; }); - result.push_back(node_coordinates[to_node]); // filter duplicated coordinates result.erase(std::unique(result.begin(), result.end()), result.end()); return result; } -IntersectionEdgeBearings -getIntersectionBearings(const util::NodeBasedDynamicGraph &graph, - const extractor::CompressedEdgeContainer &compressed_geometries, - const std::vector &node_coordinates, - const NodeID intersection_node) +namespace +{ +double findAngleBisector(double alpha, double beta) +{ + alpha *= M_PI / 180.; + beta *= M_PI / 180.; + const auto average = + 180. * std::atan2(std::sin(alpha) + std::sin(beta), std::cos(alpha) + std::cos(beta)) / + M_PI; + return std::fmod(average + 360., 360.); +} + +double findClosestOppositeBearing(const IntersectionEdgeGeometries &edge_geometries, + const double bearing) { - IntersectionEdgeBearings result; + BOOST_ASSERT(!edge_geometries.empty()); + const auto min = std::min_element( + edge_geometries.begin(), + edge_geometries.end(), + [bearing = util::bearing::reverse(bearing)](const auto &lhs, const auto &rhs) { + return util::angularDeviation(lhs.perceived_bearing, bearing) < + util::angularDeviation(rhs.perceived_bearing, bearing); + }); + return util::bearing::reverse(min->perceived_bearing); +} + +std::pair findMergedBearing(const util::NodeBasedDynamicGraph &graph, + const IntersectionEdgeGeometries &edge_geometries, + std::size_t lhs_index, + std::size_t rhs_index, + bool neighbor_intersection) +{ + // Function returns a pair with a flag and a value of bearing for merged roads + // If the flag is false the bearing must not be used as a merged value at neighbor intersections + + using guidance::STRAIGHT_ANGLE; + using guidance::MAXIMAL_ALLOWED_NO_TURN_DEVIATION; + using util::bearing::angleBetween; + using util::angularDeviation; + const auto &lhs = edge_geometries[lhs_index]; + const auto &rhs = edge_geometries[rhs_index]; + BOOST_ASSERT(graph.GetEdgeData(lhs.edge).reversed != graph.GetEdgeData(rhs.edge).reversed); + + const auto &entry = graph.GetEdgeData(lhs.edge).reversed ? rhs : lhs; + const auto opposite_bearing = + findClosestOppositeBearing(edge_geometries, entry.perceived_bearing); + const auto merged_bearing = findAngleBisector(rhs.perceived_bearing, lhs.perceived_bearing); + + if (angularDeviation(angleBetween(opposite_bearing, entry.perceived_bearing), STRAIGHT_ANGLE) < + MAXIMAL_ALLOWED_NO_TURN_DEVIATION) + { + // In some intersections, turning roads can introduce artificial turns if we merge here. + // Consider a scenario like: + //  + // a . g - f + // | . + // | . + // |. + // d-b--------e + // | + // c + //  + // Merging `bgf` and `be` would introduce an angle, even though d-b-e is perfectly straight + // We don't change the angle, if such an opposite road exists + return {false, entry.perceived_bearing}; + } + + if (neighbor_intersection) + { + // Check that the merged bearing makes both turns closer to straight line + const auto turn_angle_lhs = angleBetween(opposite_bearing, lhs.perceived_bearing); + const auto turn_angle_rhs = angleBetween(opposite_bearing, rhs.perceived_bearing); + const auto turn_angle_new = angleBetween(opposite_bearing, merged_bearing); + + if (util::angularDeviation(turn_angle_lhs, STRAIGHT_ANGLE) < + util::angularDeviation(turn_angle_new, STRAIGHT_ANGLE) || + util::angularDeviation(turn_angle_rhs, STRAIGHT_ANGLE) < + util::angularDeviation(turn_angle_new, STRAIGHT_ANGLE)) + return {false, opposite_bearing}; + } + + return {true, merged_bearing}; +} + +bool isRoadsPairMergeable(const guidance::MergableRoadDetector &detector, + const IntersectionEdgeGeometries &edge_geometries, + const NodeID intersection_node, + const std::size_t index) +{ + const auto size = edge_geometries.size(); + BOOST_ASSERT(index < size); + + const auto &llhs = edge_geometries[(index + size - 1) % size]; + const auto &lhs = edge_geometries[index]; + const auto &rhs = edge_geometries[(index + 1) % size]; + const auto &rrhs = edge_geometries[(index + 2) % size]; + + // TODO: check IsDistinctFrom - it is an angle and name-only check + // also check CanMergeRoad for all merging scenarios + return detector.IsDistinctFrom({llhs.edge, llhs.perceived_bearing, llhs.length}, + {lhs.edge, lhs.perceived_bearing, lhs.length}) && + detector.CanMergeRoad(intersection_node, + {lhs.edge, lhs.perceived_bearing, lhs.length}, + {rhs.edge, rhs.perceived_bearing, rhs.length}) && + detector.IsDistinctFrom({rhs.edge, rhs.perceived_bearing, rhs.length}, + {rrhs.edge, rrhs.perceived_bearing, rrhs.length}); +} + +auto getIntersectionLanes(const util::NodeBasedDynamicGraph &graph, const NodeID intersection_node) +{ + std::uint8_t max_lanes_intersection = 0; + for (auto outgoing_edge : graph.GetAdjacentEdgeRange(intersection_node)) + { + max_lanes_intersection = + std::max(max_lanes_intersection, + graph.GetEdgeData(outgoing_edge).flags.road_classification.GetNumberOfLanes()); + } + return max_lanes_intersection; +} + +IntersectionEdgeGeometries +getIntersectionOutgoingGeometries(const util::NodeBasedDynamicGraph &graph, + const extractor::CompressedEdgeContainer &compressed_geometries, + const std::vector &node_coordinates, + const NodeID intersection_node) +{ + IntersectionEdgeGeometries edge_geometries; + + // TODO: keep CoordinateExtractor to reproduce bearings, simplify later + const guidance::CoordinateExtractor coordinate_extractor( + graph, compressed_geometries, node_coordinates); + + const auto max_lanes_intersection = getIntersectionLanes(graph, intersection_node); + + // Collect outgoing edges for (const auto outgoing_edge : graph.GetAdjacentEdgeRange(intersection_node)) { const auto remote_node = graph.GetTarget(outgoing_edge); - const auto incoming_edge = graph.FindEdge(remote_node, intersection_node); const auto &geometry = getEdgeCoordinates( compressed_geometries, node_coordinates, intersection_node, outgoing_edge, remote_node); - // TODO: add MergableRoadDetector logic - const auto outgoing_bearing = - util::coordinate_calculation::bearing(geometry[0], geometry[1]); + // OSRM_ASSERT(geometry.size() >= 2, node_coordinates[intersection_node]); + + const auto close_coordinate = + coordinate_extractor.ExtractCoordinateAtLength(2. /*m*/, geometry); + const auto initial_bearing = + util::coordinate_calculation::bearing(geometry[0], close_coordinate); + + const auto representative_coordinate = + graph.GetOutDegree(intersection_node) <= 2 + ? coordinate_extractor.GetCoordinateCloseToTurn( + intersection_node, outgoing_edge, false, remote_node) + : coordinate_extractor.ExtractRepresentativeCoordinate(intersection_node, + outgoing_edge, + false, + remote_node, + max_lanes_intersection, + geometry); + const auto perceived_bearing = + util::coordinate_calculation::bearing(geometry[0], representative_coordinate); + + const auto edge_length = util::coordinate_calculation::getLength( + geometry.begin(), geometry.end(), util::coordinate_calculation::haversineDistance); + + edge_geometries.push_back({outgoing_edge, initial_bearing, perceived_bearing, edge_length}); + } + + // TODO: remove to fix https://github.com/Project-OSRM/osrm-backend/issues/4704 + if (!edge_geometries.empty()) + { // Adjust perceived bearings to keep the initial order with respect to the first edge + // Sort geometries by initial bearings + std::sort(edge_geometries.begin(), + edge_geometries.end(), + [base_bearing = util::bearing::reverse(edge_geometries.front().initial_bearing)]( + const auto &lhs, const auto &rhs) { + return (util::bearing::angleBetween(lhs.initial_bearing, base_bearing) < + util::bearing::angleBetween(rhs.initial_bearing, base_bearing)) || + (lhs.initial_bearing == rhs.initial_bearing && + util::bearing::angleBetween(lhs.perceived_bearing, + rhs.perceived_bearing) < 180.); + }); + + // Make a bearings ordering functor + const auto base_bearing = util::bearing::reverse(edge_geometries.front().perceived_bearing); + const auto bearings_order = [base_bearing](const auto &lhs, const auto &rhs) { + return util::bearing::angleBetween(lhs.perceived_bearing, base_bearing) < + util::bearing::angleBetween(rhs.perceived_bearing, base_bearing); + }; + + // Check the perceived bearings order is the same as the initial one + for (auto curr = edge_geometries.begin(), next = std::next(curr); + next != edge_geometries.end(); + ++curr, ++next) + { + if (bearings_order(*next, *curr)) + { // If the true bearing is out of the initial order (next before current) then + // adjust the next bearing to keep the order. The adjustment angle is at most + // 0.5° or a half-angle between the current bearing and the base bearing. + // to prevent overlapping over base bearing + 360°. + const auto angle_adjustment = std::min( + .5, + util::restrictAngleToValidRange(base_bearing - curr->perceived_bearing) / 2.); + next->perceived_bearing = + util::restrictAngleToValidRange(curr->perceived_bearing + angle_adjustment); + } + } + } - result.push_back({outgoing_edge, static_cast(outgoing_bearing)}); - result.push_back( - {incoming_edge, static_cast(util::bearing::reverse(outgoing_bearing))}); + return edge_geometries; +} +} - for (auto x : geometry) - std::cout << x << ", "; - std::cout << "\n"; +std::pair> +getIntersectionGeometries(const util::NodeBasedDynamicGraph &graph, + const extractor::CompressedEdgeContainer &compressed_geometries, + const std::vector &node_coordinates, + const guidance::MergableRoadDetector &detector, + const NodeID intersection_node) +{ + IntersectionEdgeGeometries edge_geometries = getIntersectionOutgoingGeometries( + graph, compressed_geometries, node_coordinates, intersection_node); + + const auto edges_number = edge_geometries.size(); + + std::vector merged_edges(edges_number, false); + + // TODO: intersection views do not contain merged and not allowed edges + // but contain other restricted edges that are used in TurnAnalysis, + // to be deleted after TurnAnalysis refactoring + std::unordered_set merged_edge_ids; + + if (edges_number >= 3) + { // Adjust bearings of mergeable roads + for (std::size_t index = 0; index < edges_number; ++index) + { + if (isRoadsPairMergeable(detector, edge_geometries, intersection_node, index)) + { // Merge bearings of roads left & right + const auto next = (index + 1) % edges_number; + auto &lhs = edge_geometries[index]; + auto &rhs = edge_geometries[next]; + merged_edges[index] = true; + merged_edges[next] = true; + + const auto merge = findMergedBearing(graph, edge_geometries, index, next, false); + + lhs.perceived_bearing = merge.second; + rhs.perceived_bearing = merge.second; + merged_edge_ids.insert(lhs.edge); + merged_edge_ids.insert(rhs.edge); + } + } } - for (auto x : result) - std::cout << x.edge << "," << x.bearing << "; "; - std::cout << "\n"; + if (edges_number >= 2) + { // Adjust bearings of roads that will be merged at the neighbor intersections + const double constexpr PRUNING_DISTANCE = 30.; - // Enforce ordering of edges - std::sort(result.begin(), result.end()); - return result; + for (std::size_t index = 0; index < edges_number; ++index) + { + auto &edge_geometry = edge_geometries[index]; + + // Don't adjust bearings of roads that were merged at the current intersection + // or have neighbor intersection farer than the pruning distance + if (merged_edges[index] || edge_geometry.length > PRUNING_DISTANCE) + continue; + + const auto neighbor_intersection_node = graph.GetTarget(edge_geometry.edge); + + const auto neighbor_geometries = getIntersectionOutgoingGeometries( + graph, compressed_geometries, node_coordinates, neighbor_intersection_node); + + const auto neighbor_edges = neighbor_geometries.size(); + if (neighbor_edges <= 1) + continue; + + const auto neighbor_curr = std::distance( + neighbor_geometries.begin(), + std::find_if(neighbor_geometries.begin(), + neighbor_geometries.end(), + [&graph, &intersection_node](const auto &road) { + return graph.GetTarget(road.edge) == intersection_node; + })); + BOOST_ASSERT(static_cast(neighbor_curr) != neighbor_geometries.size()); + const auto neighbor_prev = (neighbor_curr + neighbor_edges - 1) % neighbor_edges; + const auto neighbor_next = (neighbor_curr + 1) % neighbor_edges; + + if (isRoadsPairMergeable( + detector, neighbor_geometries, neighbor_intersection_node, neighbor_prev)) + { // Neighbor intersection has mergable neighbor_prev and neighbor_curr roads + BOOST_ASSERT(!isRoadsPairMergeable( + detector, neighbor_geometries, neighbor_intersection_node, neighbor_curr)); + + // TODO: merge with an angle bisector, but not a reversed closed turn, to be + // checked as a difference with the previous implementation + const auto merge = findMergedBearing( + graph, neighbor_geometries, neighbor_prev, neighbor_curr, true); + + if (merge.first) + { + const auto offset = util::angularDeviation( + merge.second, neighbor_geometries[neighbor_curr].perceived_bearing); + + // Adjust bearing of AB at the node A if at the node B roads BA (neighbor_curr) + // and BC (neighbor_prev) will be merged and will have merged bearing Bb. + // The adjustment value is ∠bBA with negative sign (counter-clockwise) to Aa + // A ~~~ a + // \  + // b --- B --- + // / + // C + edge_geometry.perceived_bearing = + std::fmod(edge_geometry.perceived_bearing + 360. - offset, 360.); + } + } + else if (isRoadsPairMergeable( + detector, neighbor_geometries, neighbor_intersection_node, neighbor_curr)) + { // Neighbor intersection has mergable neighbor_curr and neighbor_next roads + BOOST_ASSERT(!isRoadsPairMergeable( + detector, neighbor_geometries, neighbor_intersection_node, neighbor_prev)); + + // TODO: merge with an angle bisector, but not a reversed closed turn, to be + // checked as a difference with the previous implementation + const auto merge = findMergedBearing( + graph, neighbor_geometries, neighbor_curr, neighbor_next, true); + if (merge.first) + { + const auto offset = util::angularDeviation( + merge.second, neighbor_geometries[neighbor_curr].perceived_bearing); + + // Adjust bearing of AB at the node A if at the node B roads BA (neighbor_curr) + // and BC (neighbor_next) will be merged and will have merged bearing Bb. + // The adjustment value is ∠bBA with positive sign (clockwise) to Aa + // a ~~~ A + // / + // --- B --- b + // \  + // C + edge_geometry.perceived_bearing = + std::fmod(edge_geometry.perceived_bearing + offset, 360.); + } + } + } + } + + // Add incoming edges with reversed bearings + edge_geometries.resize(2 * edges_number); + for (std::size_t index = 0; index < edges_number; ++index) + { + const auto &geometry = edge_geometries[index]; + const auto remote_node = graph.GetTarget(geometry.edge); + const auto incoming_edge = graph.FindEdge(remote_node, intersection_node); + edge_geometries[edges_number + index] = {incoming_edge, + util::bearing::reverse(geometry.initial_bearing), + util::bearing::reverse(geometry.perceived_bearing), + geometry.length}; + } + + // Enforce ordering of edges by IDs + std::sort(edge_geometries.begin(), edge_geometries.end()); + + return std::make_pair(edge_geometries, merged_edge_ids); } -auto findEdgeBearing(const IntersectionEdgeBearings &bearings, const EdgeID &edge) +inline auto findEdge(const IntersectionEdgeGeometries &geometries, const EdgeID &edge) { const auto it = std::lower_bound( - bearings.begin(), bearings.end(), edge, [](const auto &edge_bearing, const auto edge) { - return edge_bearing.edge < edge; + geometries.begin(), geometries.end(), edge, [](const auto &geometry, const auto edge) { + return geometry.edge < edge; }); - BOOST_ASSERT(it != bearings.end() && it->edge == edge); - return it->bearing; + BOOST_ASSERT(it != geometries.end() && it->edge == edge); + return it; +} + +double findEdgeBearing(const IntersectionEdgeGeometries &geometries, const EdgeID &edge) +{ + return findEdge(geometries, edge)->perceived_bearing; } -double computeTurnAngle(const IntersectionEdgeBearings &bearings, - const IntersectionEdge &from, - const IntersectionEdge &to) +double findEdgeLength(const IntersectionEdgeGeometries &geometries, const EdgeID &edge) { - return util::bearing::angleBetween(findEdgeBearing(bearings, from.edge), - findEdgeBearing(bearings, to.edge)); + return findEdge(geometries, edge)->length; } template @@ -164,13 +498,17 @@ bool isTurnAllowed(const util::NodeBasedDynamicGraph &graph, const EdgeBasedNodeDataContainer &node_data_container, const RestrictionMap &restriction_map, const std::unordered_set &barrier_nodes, - const IntersectionEdgeBearings &bearings, + const IntersectionEdgeGeometries &geometries, const guidance::TurnLanesIndexedArray &turn_lanes_data, const IntersectionEdge &from, const IntersectionEdge &to) { BOOST_ASSERT(graph.GetTarget(from.edge) == to.node); + // TODO: to use TurnAnalysis all outgoing edges are required, to be removed later + if (graph.GetEdgeData(from.edge).reversed || graph.GetEdgeData(to.edge).reversed) + return false; + const auto intersection_node = to.node; const auto destination_node = graph.GetTarget(to.edge); auto const &restrictions = restriction_map.Restrictions(from.node, intersection_node); @@ -181,7 +519,7 @@ bool isTurnAllowed(const util::NodeBasedDynamicGraph &graph, // Precompute reversed bearing of the `from` edge const auto from_edge_reversed_bearing = - util::bearing::reverse(findEdgeBearing(bearings, from.edge)); + util::bearing::reverse(findEdgeBearing(geometries, from.edge)); // Collect some information about the intersection // 1) number of allowed exits and adjacent bidirectional edges @@ -210,7 +548,7 @@ bool isTurnAllowed(const util::NodeBasedDynamicGraph &graph, // "Linked Roundabouts" is an example of tie between two linked roundabouts // A tie breaker for that maximizes ∠(roundabout_from_bearing, ¬from_edge_bearing) const auto angle = util::bearing::angleBetween( - findEdgeBearing(bearings, reverse_edge), from_edge_reversed_bearing); + findEdgeBearing(geometries, reverse_edge), from_edge_reversed_bearing); if (angle > roundabout_from_angle) { roundabout_from = reverse_edge; @@ -221,7 +559,7 @@ bool isTurnAllowed(const util::NodeBasedDynamicGraph &graph, { // a tie breaker that maximizes ∠(¬from_edge_bearing, roundabout_to_bearing) const auto angle = util::bearing::angleBetween(from_edge_reversed_bearing, - findEdgeBearing(bearings, eid)); + findEdgeBearing(geometries, eid)); if (angle > roundabout_to_angle) { roundabout_to = eid; @@ -281,9 +619,9 @@ bool isTurnAllowed(const util::NodeBasedDynamicGraph &graph, if (roundabout_from != SPECIAL_EDGEID && roundabout_to != SPECIAL_EDGEID) { // Get bearings of edges - const auto roundabout_from_bearing = findEdgeBearing(bearings, roundabout_from); - const auto roundabout_to_bearing = findEdgeBearing(bearings, roundabout_to); - const auto to_bearing = findEdgeBearing(bearings, to.edge); + const auto roundabout_from_bearing = findEdgeBearing(geometries, roundabout_from); + const auto roundabout_to_bearing = findEdgeBearing(geometries, roundabout_to); + const auto to_bearing = findEdgeBearing(geometries, to.edge); // Get angles from the roundabout edge to three other edges const auto roundabout_angle = @@ -312,6 +650,72 @@ bool isTurnAllowed(const util::NodeBasedDynamicGraph &graph, return true; } + +// TODO: the function adapts intersection geometry data to TurnAnalysis +guidance::IntersectionView +convertToIntersectionView(const util::NodeBasedDynamicGraph &graph, + const EdgeBasedNodeDataContainer &node_data_container, + const RestrictionMap &restriction_map, + const std::unordered_set &barrier_nodes, + const IntersectionEdgeGeometries &edge_geometries, + const guidance::TurnLanesIndexedArray &turn_lanes_data, + const IntersectionEdge &incoming_edge, + const IntersectionEdges &outgoing_edges, + const std::unordered_set &merged_edges) +{ + const auto incoming_bearing = findEdgeBearing(edge_geometries, incoming_edge.edge); + + guidance::IntersectionView intersection_view; + guidance::IntersectionViewData uturn_road{{SPECIAL_EDGEID, 0., 0.}, false, 0.}; + for (const auto &outgoing_edge : outgoing_edges) + { + const auto outgoing_bearing = findEdgeBearing(edge_geometries, outgoing_edge.edge); + const auto segment_length = findEdgeLength(edge_geometries, outgoing_edge.edge); + const auto turn_angle = util::bearing::angleBetween(incoming_bearing, outgoing_bearing); + const auto is_turn_allowed = intersection::isTurnAllowed(graph, + node_data_container, + restriction_map, + barrier_nodes, + edge_geometries, + turn_lanes_data, + incoming_edge, + outgoing_edge); + + // Don't include merged edges that have no entry but always include U-turn roads + const auto is_uturn = std::fabs(turn_angle) < std::numeric_limits::epsilon(); + if (graph.GetEdgeData(outgoing_edge.edge).reversed && + merged_edges.count(outgoing_edge.edge) != 0 && !is_uturn) + continue; + + if (is_uturn) + { // Prefer U-turns that have allowed entry + if (!uturn_road.entry_allowed) + { + uturn_road = {{outgoing_edge.edge, outgoing_bearing, segment_length}, + is_turn_allowed, + turn_angle}; + } + continue; + } + + intersection_view.push_back({{outgoing_edge.edge, + outgoing_bearing, + findEdgeLength(edge_geometries, outgoing_edge.edge)}, + is_turn_allowed, + turn_angle}); + } + + // Add the U-turn road + BOOST_ASSERT(uturn_road.eid != SPECIAL_EDGEID); + intersection_view.push_back(uturn_road); + + // Order roads from in counter-clockwise order from the U-turn edge + std::sort(intersection_view.begin(), + intersection_view.end(), + [](const auto &lhs, const auto &rhs) { return lhs.angle < rhs.angle; }); + + return intersection_view; +} } } }