diff --git a/features/guidance/advanced-lanes.feature b/features/guidance/advanced-lanes.feature index 9f4322b00..201ec04e7 100644 --- a/features/guidance/advanced-lanes.feature +++ b/features/guidance/advanced-lanes.feature @@ -72,16 +72,16 @@ Feature: Turn Lane Guidance Given the node map """ e - a b c g - d - f - - - - - - - i h j + a - - b.-.- - c-g + | ' 'd + | f + | + | + | + | + | + | + i - - h - - - j """ And the ways diff --git a/features/guidance/perception.feature b/features/guidance/perception.feature index facf81558..3d92e2b7b 100644 --- a/features/guidance/perception.feature +++ b/features/guidance/perception.feature @@ -10,12 +10,12 @@ Feature: Simple Turns """ a b - - + ^ + / \ c d - - e - + |\ + | e + | f """ @@ -96,16 +96,16 @@ Feature: Simple Turns Given the node map """ a - - b + | + .b. c h - - + | | + | | 1 2 - + | | d g - e - + 'e' + | f """ diff --git a/features/guidance/turn-angles.feature b/features/guidance/turn-angles.feature index 7d6f65a3d..c177c2b54 100644 --- a/features/guidance/turn-angles.feature +++ b/features/guidance/turn-angles.feature @@ -443,9 +443,9 @@ Feature: Simple Turns | ef | residential | road | 2 | yes | When I route I should get - | waypoints | route | turns | - | g,f | turn,road | depart,arrive | - | c,f | road,road,road | depart,continue right,arrive | + | waypoints | route | turns | locations | + | g,f | turn,road,road | depart,turn left,arrive | g,e,f | + | c,f | road,road,road | depart,continue right,arrive | c,b,f | #http://www.openstreetmap.org/search?query=52.479264%2013.295617#map=19/52.47926/13.29562 Scenario: Splitting Roads with curved split diff --git a/features/guidance/turn-lanes.feature b/features/guidance/turn-lanes.feature index 527fd0874..391990833 100644 --- a/features/guidance/turn-lanes.feature +++ b/features/guidance/turn-lanes.feature @@ -116,24 +116,25 @@ Feature: Turn Lane Guidance Scenario: Basic Turn Lane 4-Way With U-Turn Lane Given the node map """ - e - a 1 b c - d + e + f a-1-b---c + d """ And the ways - | nodes | turn:lanes | turn:lanes:forward | name | - | ab | | reverse;left\|through;right | in | - | bc | | | straight | - | bd | | | right | - | be | | | left | + | nodes | turn:lanes | turn:lanes:forward | name | # | + | ab | | reverse;left\|through;right | in | | + | bc | | | straight | | + | bd | | | right | | + | be | | | left | | + | fa | | | uturn-avoider | #due to https://github.com/Project-OSRM/osrm-backend/issues/3359 | When I route I should get - | from | to | bearings | route | turns | lanes | - | a | c | 180,180 180,180 | in,straight,straight | depart,new name straight,arrive | ,left;uturn:false straight;right:true, | - | a | d | 180,180 180,180 | in,right,right | depart,turn right,arrive | ,left;uturn:false straight;right:true, | - | a | e | 180,180 180,180 | in,left,left | depart,turn left,arrive | ,left;uturn:true straight;right:false, | - | 1 | a | 90,2 270,2 | in,in,in | depart,turn uturn,arrive | ,left;uturn:true straight;right:false, | + | from | to | bearings | route | turns | lanes | locations | + | a | c | 180,180 180,180 | in,straight,straight | depart,new name straight,arrive | ,left;uturn:false straight;right:true, | a,b,c | + | a | d | 180,180 180,180 | in,right,right | depart,turn right,arrive | ,left;uturn:false straight;right:true, | a,b,d | + | a | e | 180,180 180,180 | in,left,left | depart,turn left,arrive | ,left;uturn:true straight;right:false, | a,b,e | + | 1 | a | 90,2 270,2 | in,in,in | depart,turn uturn,arrive | ,left;uturn:true straight;right:false, | _,b,a | #this next test requires decision on how to announce lanes for going straight if there is no turn diff --git a/features/guidance/turn.feature b/features/guidance/turn.feature index f0cb47c9e..2b547cd07 100644 --- a/features/guidance/turn.feature +++ b/features/guidance/turn.feature @@ -1179,8 +1179,8 @@ Feature: Simple Turns Scenario: Obvious Index wigh very narrow turn to the right Given the node map """ - a b c - d + a - b -.-.- - - c + ' ' 'd """ And the ways @@ -1198,8 +1198,8 @@ Feature: Simple Turns Scenario: Obvious Index wigh very narrow turn to the right Given the node map """ - a b c - e d f + a - b - . -.- - c + e - -'-'d-f """ And the ways @@ -1218,8 +1218,8 @@ Feature: Simple Turns Scenario: Obvious Index wigh very narrow turn to the left Given the node map """ - d - a b c + . . .d + a - b -'-'- - - c """ And the ways @@ -1237,8 +1237,8 @@ Feature: Simple Turns Scenario: Obvious Index wigh very narrow turn to the left Given the node map """ - e d f - a b c + e - -.- d-f + a - b - ' - - - c """ And the ways diff --git a/include/contractor/query_edge.hpp b/include/contractor/query_edge.hpp index 83681935a..ebc1ee990 100644 --- a/include/contractor/query_edge.hpp +++ b/include/contractor/query_edge.hpp @@ -26,6 +26,9 @@ struct QueryEdge forward = other.forward; backward = other.backward; } + // this ID is either the middle node of the shortcut, or the ID of the edge based node (node + // based edge) storing the appropriate data. If `shortcut` is set to true, we get the middle + // node. Otherwise we see the edge based node to access node data. NodeID id : 31; bool shortcut : 1; int weight : 30; diff --git a/include/extractor/edge_based_edge.hpp b/include/extractor/edge_based_edge.hpp index e4e5ef3f3..3dd3fe4c5 100644 --- a/include/extractor/edge_based_edge.hpp +++ b/include/extractor/edge_based_edge.hpp @@ -3,6 +3,7 @@ #include "extractor/travel_mode.hpp" #include "util/typedefs.hpp" +#include namespace osrm { @@ -60,19 +61,12 @@ inline EdgeBasedEdge::EdgeBasedEdge(const NodeID source, inline bool EdgeBasedEdge::operator<(const EdgeBasedEdge &other) const { - if (source == other.source) - { - if (target == other.target) - { - if (weight == other.weight) - { - return forward && backward && ((!other.forward) || (!other.backward)); - } - return weight < other.weight; - } - return target < other.target; - } - return source < other.source; + const auto unidirectional = (!forward || !backward); + const auto other_is_unidirectional = (!other.forward || !other.backward); + // if all items are the same, we want to keep bidirectional edges. due to the `<` operator, + // preferring 0 (false) over 1 (true), we need to compare the inverse of `bidirectional` + return std::tie(source, target, weight, unidirectional) < + std::tie(other.source, other.target, other.weight, other_is_unidirectional); } } // ns extractor } // ns osrm diff --git a/include/extractor/guidance/coordinate_extractor.hpp b/include/extractor/guidance/coordinate_extractor.hpp index c6cb17b12..7957ef9cc 100644 --- a/include/extractor/guidance/coordinate_extractor.hpp +++ b/include/extractor/guidance/coordinate_extractor.hpp @@ -38,7 +38,8 @@ class CoordinateExtractor const NodeID to_node, const std::uint8_t number_of_in_lanes) const; - // same as above, only with precomputed coordinate vector (move it in) + // Given a set of precomputed coordinates, select the representative coordinate along the road + // that best describes the turn OSRM_ATTR_WARN_UNUSED util::Coordinate ExtractRepresentativeCoordinate(const NodeID intersection_node, @@ -240,6 +241,14 @@ class CoordinateExtractor const double segment_length, const std::vector &segment_distances, const std::uint8_t considered_lanes) const; + + // find the coordinate at a specific location in the vector + util::Coordinate ExtractCoordinateAtLength(const double distance, + const std::vector &coordinates, + const std::vector &length_cache) const; + util::Coordinate + ExtractCoordinateAtLength(const double distance, + const std::vector &coordinates) const; }; } // namespace guidance diff --git a/include/extractor/guidance/intersection.hpp b/include/extractor/guidance/intersection.hpp index 74783970d..d02e89d69 100644 --- a/include/extractor/guidance/intersection.hpp +++ b/include/extractor/guidance/intersection.hpp @@ -5,12 +5,11 @@ #include #include "extractor/guidance/turn_instruction.hpp" +#include "util/bearing.hpp" #include "util/guidance/toolkit.hpp" #include "util/node_based_graph.hpp" #include "util/typedefs.hpp" // EdgeID -#include - namespace osrm { namespace extractor @@ -18,16 +17,37 @@ namespace extractor namespace guidance { -// Every Turn Operation describes a way of switching onto a segment, indicated by an EdgeID. The -// associated turn is described by an angle and an instruction that is used to announce it. -// The Turn Operation indicates what is exposed to the outside of the turn analysis. -struct TurnOperation +// the shape of an intersection only knows about edge IDs and bearings +struct IntersectionShapeData { EdgeID eid; - double angle; double bearing; - TurnInstruction instruction; - LaneDataID lane_data_id; + double segment_length; +}; + +inline auto makeCompareShapeDataByBearing(const double base_bearing) +{ + return [base_bearing](const IntersectionShapeData &lhs, const IntersectionShapeData &rhs) { + return util::bearing::angleBetweenBearings(base_bearing, lhs.bearing) < + util::bearing::angleBetweenBearings(base_bearing, rhs.bearing); + }; +}; + +// When viewing an intersection from an incoming edge, we can transform a shape into a view which +// gives additional information on angles and whether a turn is allowed +struct IntersectionViewData : IntersectionShapeData +{ + IntersectionViewData(const IntersectionShapeData &shape, + const bool entry_allowed, + const double angle) + : IntersectionShapeData(shape), entry_allowed(entry_allowed), angle(angle) + { + } + + bool entry_allowed; + double angle; + + bool CompareByAngle(const IntersectionViewData &other) const; }; // A Connected Road is the internal representation of a potential turn. Internally, we require @@ -51,17 +71,17 @@ struct TurnOperation // aaaaaaaa // // We would perceive a->c as a sharp turn, a->b as a slight turn, and b->c as a slight turn. -struct ConnectedRoad final : public TurnOperation +struct ConnectedRoad final : IntersectionViewData { - using Base = TurnOperation; + ConnectedRoad(const IntersectionViewData &view, + const TurnInstruction instruction, + const LaneDataID lane_data_id) + : IntersectionViewData(view), instruction(instruction), lane_data_id(lane_data_id) + { + } - ConnectedRoad(const TurnOperation turn, - const bool entry_allowed = false, - const boost::optional segment_length = {}); - - // a turn may be relevant to good instructions, even if we cannot enter the road - bool entry_allowed; - boost::optional segment_length; + TurnInstruction instruction; + LaneDataID lane_data_id; // used to sort the set of connected roads (we require sorting throughout turn handling) bool compareByAngle(const ConnectedRoad &other) const; @@ -76,10 +96,24 @@ struct ConnectedRoad final : public TurnOperation // small helper function to print the content of a connected road std::string toString(const ConnectedRoad &road); -struct Intersection final : public std::vector +using IntersectionShape = std::vector; + +struct IntersectionView final : std::vector +{ + using Base = std::vector; + + bool valid() const + { + return std::is_sorted(begin(), end(), std::mem_fn(&IntersectionViewData::CompareByAngle)); + }; + + Base::iterator findClosestTurn(double angle); + Base::const_iterator findClosestTurn(double angle) const; +}; + +struct Intersection final : std::vector { using Base = std::vector; - /* * find the turn whose angle offers the least angularDeviation to the specified angle * E.g. for turn angles [0,90,260] and a query of 180 we return the 260 degree turn (difference diff --git a/include/extractor/guidance/intersection_generator.hpp b/include/extractor/guidance/intersection_generator.hpp index 6deb09f09..71b6b3b8e 100644 --- a/include/extractor/guidance/intersection_generator.hpp +++ b/include/extractor/guidance/intersection_generator.hpp @@ -11,8 +11,11 @@ #include "util/typedefs.hpp" #include +#include #include +#include + namespace osrm { namespace extractor @@ -32,7 +35,18 @@ class IntersectionGenerator const std::vector &node_info_list, const CompressedEdgeContainer &compressed_edge_container); - Intersection operator()(const NodeID nid, const EdgeID via_eid) const; + IntersectionView operator()(const NodeID nid, const EdgeID via_eid) const; + + /* + * Compute the shape of an intersection, returning a set of connected roads, without any further + * concern for which of the entries are actually allowed. + * The shape also only comes with turn bearings, not with turn angles. All turn angles will be + * set to zero + */ + IntersectionShape + ComputeIntersectionShape(const NodeID center_node, + const boost::optional sorting_base = boost::none, + bool use_low_precision_angles = false) const; // Graph Compression cannot compress every setting. For example any barrier/traffic light cannot // be compressed. As a result, a simple road of the form `a ----- b` might end up as having an @@ -40,10 +54,10 @@ class IntersectionGenerator // down a road, finding the next actual decision requires the look at multiple intersections. // Here we follow the road until we either reach a dead end or find the next intersection with // more than a single next road. - Intersection GetActualNextIntersection(const NodeID starting_node, - const EdgeID via_edge, - NodeID *resulting_from_node, - EdgeID *resulting_via_edge) const; + IntersectionView GetActualNextIntersection(const NodeID starting_node, + const EdgeID via_edge, + NodeID *resulting_from_node, + EdgeID *resulting_via_edge) const; // Allow access to the coordinate extractor for all owners const CoordinateExtractor &GetCoordinateExtractor() const; @@ -56,9 +70,28 @@ class IntersectionGenerator // turns. Even good enough to do some simple angle verifications. It is mostly available to // allow for faster graph traversal in the extraction phase. OSRM_ATTR_WARN_UNUSED - Intersection GetConnectedRoads(const NodeID from_node, - const EdgeID via_eid, - const bool use_low_precision_angles = false) const; + IntersectionView GetConnectedRoads(const NodeID from_node, + const EdgeID via_eid, + const bool use_low_precision_angles = false) const; + + /* + * To be used in the road network, we need to check for valid/restricted turns. These two + * functions transform a basic intersection / a normalised intersection into the + * correct view when entering via a given edge. + */ + OSRM_ATTR_WARN_UNUSED + IntersectionView + TransformIntersectionShapeIntoView(const NodeID previous_node, + const EdgeID entering_via_edge, + const IntersectionShape &intersection) const; + // version for normalised intersection + OSRM_ATTR_WARN_UNUSED + IntersectionView TransformIntersectionShapeIntoView( + const NodeID previous_node, + const EdgeID entering_via_edge, + const IntersectionShape &normalised_intersection, + const IntersectionShape &intersection, + const std::vector> &merging_map) const; private: const util::NodeBasedDynamicGraph &node_based_graph; @@ -68,6 +101,14 @@ class IntersectionGenerator // own state, used to find the correct coordinates along a road const CoordinateExtractor coordinate_extractor; + + // check turn restrictions to find a node that is the only allowed target when coming from a + // node to an intersection + // d + // | + // a - b - c and `only_straight_on ab | bc would return `c` for `a,b` + boost::optional GetOnlyAllowedTurnIfExistent(const NodeID coming_from_node, + const NodeID node_at_intersection) const; }; } // namespace guidance diff --git a/include/extractor/guidance/intersection_normalizer.hpp b/include/extractor/guidance/intersection_normalizer.hpp index 74bd45fbf..c296262ca 100644 --- a/include/extractor/guidance/intersection_normalizer.hpp +++ b/include/extractor/guidance/intersection_normalizer.hpp @@ -13,6 +13,7 @@ #include "extractor/suffix_table.hpp" #include "util/name_table.hpp" +#include #include namespace osrm @@ -45,7 +46,8 @@ class IntersectionNormalizer // 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 - Intersection operator()(const NodeID node_at_intersection, Intersection intersection) const; + std::pair>> + operator()(const NodeID node_at_intersection, IntersectionShape intersection) const; private: const util::NodeBasedDynamicGraph &node_based_graph; @@ -57,12 +59,26 @@ class IntersectionNormalizer // 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 + // 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 Intersection &intersection, + const IntersectionShape &intersection, std::size_t first_index, std::size_t second_index) const; + // A tool called by CanMerge. It checks whether two indices can be merged, not concerned without + // remaining parts of the intersection. + bool InnerCanMerge(const NodeID intersection_node, + const IntersectionShape &intersection, + std::size_t first_index, + std::size_t second_index) const; + // Merge segregated roads to omit invalid turns in favor of treating segregated roads as // one. // This function combines roads the following way: @@ -75,8 +91,8 @@ class IntersectionNormalizer // The treatment results in a straight turn angle of 180ยบ rather than a turn angle of approx // 160 OSRM_ATTR_WARN_UNUSED - Intersection MergeSegregatedRoads(const NodeID intersection_node, - Intersection intersection) const; + std::pair>> + 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. @@ -90,8 +106,8 @@ class IntersectionNormalizer // // for the local view of b at a. OSRM_ATTR_WARN_UNUSED - Intersection AdjustForJoiningRoads(const NodeID node_at_intersection, - Intersection intersection) const; + IntersectionShape AdjustBearingsForMergeAtDestination(const NodeID node_at_intersection, + IntersectionShape intersection) const; }; } // namespace guidance diff --git a/include/extractor/guidance/node_based_graph_walker.hpp b/include/extractor/guidance/node_based_graph_walker.hpp index 0da9f9e77..0c24fa477 100644 --- a/include/extractor/guidance/node_based_graph_walker.hpp +++ b/include/extractor/guidance/node_based_graph_walker.hpp @@ -133,7 +133,7 @@ struct IntersectionFinderAccumulator // the result we are looking for NodeID nid; EdgeID via_edge_id; - Intersection intersection; + IntersectionView intersection; }; template diff --git a/include/extractor/guidance/turn_analysis.hpp b/include/extractor/guidance/turn_analysis.hpp index c99fef1fe..afa483e25 100644 --- a/include/extractor/guidance/turn_analysis.hpp +++ b/include/extractor/guidance/turn_analysis.hpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -36,7 +37,6 @@ namespace guidance class TurnAnalysis { - public: TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph, const std::vector &node_info_list, @@ -47,29 +47,35 @@ class TurnAnalysis const SuffixTable &street_name_suffix_table, const ProfileProperties &profile_properties); + /* Full Analysis Process for a single node/edge combination. Use with caution, as the process is + * relatively expensive */ + OSRM_ATTR_WARN_UNUSED + 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; + // normalised shape, merged some roads into others, adjusted bearings + // see intersection_normaliser for further explanations + IntersectionShape normalised_intersection_shape; + // map containing information about which road was merged into which + std::vector> merging_map; + }; OSRM_ATTR_WARN_UNUSED - Intersection operator()(const NodeID from_node, const EdgeID via_eid) const; + ShapeResult ComputeIntersectionShapes(const NodeID node_at_center_of_intersection) const; - /* - * Post-Processing a generated intersection is useful for any intersection that was simply - * generated using an intersection generator. In the normal use case, you don't have to call - * this function. - * This function is part of the normal process of the operator(). - */ + // Select turn types based on the intersection shape OSRM_ATTR_WARN_UNUSED - Intersection - PostProcess(const NodeID from_node, const EdgeID via_eid, Intersection intersection) const; - - std::vector - transformIntersectionIntoTurns(const Intersection &intersection) const; - - Intersection - assignTurnTypes(const NodeID from_node, const EdgeID via_eid, Intersection intersection) const; + Intersection AssignTurnTypes(const NodeID from_node, + const EdgeID via_eid, + const IntersectionView &intersection) const; const IntersectionGenerator &GetIntersectionGenerator() const; diff --git a/include/extractor/guidance/turn_discovery.hpp b/include/extractor/guidance/turn_discovery.hpp index fefdfc454..fa758d760 100644 --- a/include/extractor/guidance/turn_discovery.hpp +++ b/include/extractor/guidance/turn_discovery.hpp @@ -28,7 +28,7 @@ bool findPreviousIntersection( // output parameters, will be in an arbitrary state on failure NodeID &result_node, EdgeID &result_via_edge, - Intersection &result_intersection); + IntersectionView &result_intersection); } // namespace lanes } // namespace guidance diff --git a/include/extractor/node_based_edge.hpp b/include/extractor/node_based_edge.hpp index 18c1ad44e..6bbf2fa68 100644 --- a/include/extractor/node_based_edge.hpp +++ b/include/extractor/node_based_edge.hpp @@ -73,8 +73,9 @@ struct NodeBasedEdgeWithOSM : NodeBasedEdge inline NodeBasedEdge::NodeBasedEdge() : source(SPECIAL_NODEID), target(SPECIAL_NODEID), name_id(0), weight(0), forward(false), - backward(false), roundabout(false), circular(false), access_restricted(false), startpoint(true), - is_split(false), travel_mode(false), lane_description_id(INVALID_LANE_DESCRIPTIONID) + backward(false), roundabout(false), circular(false), access_restricted(false), + startpoint(true), is_split(false), travel_mode(false), + lane_description_id(INVALID_LANE_DESCRIPTIONID) { } @@ -93,9 +94,10 @@ inline NodeBasedEdge::NodeBasedEdge(NodeID source, const LaneDescriptionID lane_description_id, guidance::RoadClassification road_classification) : source(source), target(target), name_id(name_id), weight(weight), forward(forward), - backward(backward), roundabout(roundabout), circular(circular), access_restricted(access_restricted), - startpoint(startpoint), is_split(is_split), travel_mode(travel_mode), - lane_description_id(lane_description_id), road_classification(std::move(road_classification)) + backward(backward), roundabout(roundabout), circular(circular), + access_restricted(access_restricted), startpoint(startpoint), is_split(is_split), + travel_mode(travel_mode), lane_description_id(lane_description_id), + road_classification(std::move(road_classification)) { } diff --git a/include/util/bearing.hpp b/include/util/bearing.hpp index de308adce..dedd2c943 100644 --- a/include/util/bearing.hpp +++ b/include/util/bearing.hpp @@ -8,9 +8,9 @@ namespace osrm { namespace util { - namespace bearing { + inline std::string get(const double heading) { BOOST_ASSERT(heading >= 0); @@ -96,8 +96,40 @@ inline double reverseBearing(const double bearing) return bearing - 180.; return bearing + 180; } -} -} + +// Compute the angle between two bearings on a normal turn circle +// +// Bearings Angles +// +// 0 180 +// 315 45 225 135 +// +// 270 x 90 270 x 90 +// +// 225 135 315 45 +// 180 0 +// +// A turn from north to north-east offerst bearing 0 and 45 has to be translated +// into a turn of 135 degrees. The same holdes for 90 - 135 (east to south +// east). +// For north, the transformation works by angle = 540 (360 + 180) - exit_bearing +// % 360; +// All other cases are handled by first rotating both bearings to an +// entry_bearing of 0. +inline double angleBetweenBearings(const double entry_bearing, const double exit_bearing) +{ + const double offset = 360 - entry_bearing; + const double rotated_exit = [](double bearing, const double offset) { + bearing += offset; + return bearing > 360 ? bearing - 360 : bearing; + }(exit_bearing, offset); + + const auto angle = 540 - rotated_exit; + return angle >= 360 ? angle - 360 : angle; } +} // namespace bearing +} // namespace util +} // namespace osrm + #endif // BEARING_HPP diff --git a/src/engine/guidance/post_processing.cpp b/src/engine/guidance/post_processing.cpp index c13d8b0d6..98afa1dca 100644 --- a/src/engine/guidance/post_processing.cpp +++ b/src/engine/guidance/post_processing.cpp @@ -1,13 +1,13 @@ +#include "engine/guidance/post_processing.hpp" #include "extractor/guidance/constants.hpp" #include "extractor/guidance/turn_instruction.hpp" -#include "engine/guidance/post_processing.hpp" #include "engine/guidance/toolkit.hpp" #include "engine/guidance/assemble_steps.hpp" #include "engine/guidance/lane_processing.hpp" #include "engine/guidance/toolkit.hpp" -#include "util/attributes.hpp" +#include "util/bearing.hpp" #include "util/guidance/toolkit.hpp" #include "util/guidance/turn_lanes.hpp" @@ -92,37 +92,6 @@ bool compatible(const RouteStep &lhs, const RouteStep &rhs) { return lhs.mode == // invalidate a step and set its content to nothing void invalidateStep(RouteStep &step) { step = getInvalidRouteStep(); } -// Compute the angle between two bearings on a normal turn circle -// -// Bearings Angles -// -// 0 180 -// 315 45 225 135 -// -// 270 x 90 270 x 90 -// -// 225 135 315 45 -// 180 0 -// -// A turn from north to north-east offerst bearing 0 and 45 has to be translated -// into a turn of 135 degrees. The same holdes for 90 - 135 (east to south -// east). -// For north, the transformation works by angle = 540 (360 + 180) - exit_bearing -// % 360; -// All other cases are handled by first rotating both bearings to an -// entry_bearing of 0. -double turn_angle(const double entry_bearing, const double exit_bearing) -{ - const double offset = 360 - entry_bearing; - const double rotated_exit = [](double bearing, const double offset) { - bearing += offset; - return bearing > 360 ? bearing - 360 : bearing; - }(exit_bearing, offset); - - const auto angle = 540 - rotated_exit; - return angle > 360 ? angle - 360 : angle; -} - // Checks if name change happens the user wants to know about. // Treats e.g. "Name (Ref)" -> "Name" changes still as same name. bool isNoticeableNameChange(const RouteStep &lhs, const RouteStep &rhs) @@ -330,10 +299,10 @@ void closeOffRoundabout(const bool on_roundabout, TurnType::EnterRoundaboutIntersectionAtExit) { BOOST_ASSERT(!propagation_step.intersections.empty()); - const double angle = - turn_angle(util::bearing::reverseBearing( - entry_intersection.bearings[entry_intersection.in]), - exit_bearing); + const double angle = util::bearing::angleBetweenBearings( + util::bearing::reverseBearing( + entry_intersection.bearings[entry_intersection.in]), + exit_bearing); auto bearings = propagation_step.intersections.front().bearings; propagation_step.maneuver.instruction.direction_modifier = @@ -396,10 +365,13 @@ double findTotalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_ste util::bearing::reverseBearing(entry_intersection.bearings[entry_intersection.in]); const auto entry_step_exit_bearing = entry_intersection.bearings[entry_intersection.out]; - const auto exit_angle = turn_angle(exit_step_entry_bearing, exit_step_exit_bearing); - const auto entry_angle = turn_angle(entry_step_entry_bearing, entry_step_exit_bearing); + const auto exit_angle = + util::bearing::angleBetweenBearings(exit_step_entry_bearing, exit_step_exit_bearing); + const auto entry_angle = + util::bearing::angleBetweenBearings(entry_step_entry_bearing, entry_step_exit_bearing); - const double total_angle = turn_angle(entry_step_entry_bearing, exit_step_exit_bearing); + const double total_angle = + util::bearing::angleBetweenBearings(entry_step_entry_bearing, exit_step_exit_bearing); // We allow for minor deviations from a straight line if (((entry_step.distance < MAX_COLLAPSE_DISTANCE && exit_step.intersections.size() == 1) || (entry_angle <= 185 && exit_angle <= 185) || (entry_angle >= 175 && exit_angle >= 175)) && @@ -608,12 +580,12 @@ void collapseTurnAt(std::vector &steps, return step.intersections.front().bearings[step.intersections.front().out]; }; - const auto first_angle = - turn_angle(in_bearing(one_back_step), out_bearing(one_back_step)); - const auto second_angle = - turn_angle(in_bearing(current_step), out_bearing(current_step)); - const auto bearing_turn_angle = - turn_angle(in_bearing(one_back_step), out_bearing(current_step)); + const auto first_angle = util::bearing::angleBetweenBearings( + in_bearing(one_back_step), out_bearing(one_back_step)); + const auto second_angle = util::bearing::angleBetweenBearings( + in_bearing(current_step), out_bearing(current_step)); + const auto bearing_turn_angle = util::bearing::angleBetweenBearings( + in_bearing(one_back_step), out_bearing(current_step)); // When looking at an intersection, some angles, even though present, feel more like // a straight turn. This happens most often at segregated intersections. @@ -834,7 +806,7 @@ bool isStaggeredIntersection(const std::vector &steps, const auto &intersection = step.intersections.front(); const auto entry_bearing = intersection.bearings[intersection.in]; const auto exit_bearing = intersection.bearings[intersection.out]; - return turn_angle(entry_bearing, exit_bearing); + return util::bearing::angleBetweenBearings(entry_bearing, exit_bearing); }; // Instead of using turn modifiers (e.g. as in isRightTurn) we want to be more strict here. diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index 9ab5dd981..432b0b75c 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -363,25 +363,60 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( bearing_class_by_node_based_node.resize(m_node_based_graph->GetNumberOfNodes(), std::numeric_limits::max()); - for (const auto node_u : util::irange(0u, m_node_based_graph->GetNumberOfNodes())) + // going over all nodes (which form the center of an intersection), we compute all + // possible turns along these intersections. + for (const auto node_at_center_of_intersection : + util::irange(0u, m_node_based_graph->GetNumberOfNodes())) { - progress.PrintStatus(node_u); - for (const EdgeID edge_from_u : m_node_based_graph->GetAdjacentEdgeRange(node_u)) - { - if (m_node_based_graph->GetEdgeData(edge_from_u).reversed) - { - continue; - } + progress.PrintStatus(node_at_center_of_intersection); + + const auto shape_result = + turn_analysis.ComputeIntersectionShapes(node_at_center_of_intersection); + + // 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, but the cost we need to pay to + // generate edges here is worth the additional search overhead. + // + // a -> b <-> c + // | + // v + // d + // + // will have: + // a: b,rev=0 + // b: a,rev=1 c,rev=0 d,rev=0 + // c: b,rev=0 + // + // 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` + for (const EdgeID outgoing_edge : + m_node_based_graph->GetAdjacentEdgeRange(node_at_center_of_intersection)) + { + 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; - const NodeID node_v = m_node_based_graph->GetTarget(edge_from_u); ++node_based_edge_counter; - auto intersection = turn_analysis(node_u, edge_from_u); + + auto intersection_with_flags_and_angles = + turn_analysis.GetIntersectionGenerator().TransformIntersectionShapeIntoView( + node_along_road_entering, + incoming_edge, + shape_result.normalised_intersection_shape, + shape_result.intersection_shape, + shape_result.merging_map); + + auto intersection = turn_analysis.AssignTurnTypes( + node_along_road_entering, incoming_edge, intersection_with_flags_and_angles); + BOOST_ASSERT(intersection.valid()); - intersection = - turn_lane_handler.assignTurnLanes(node_u, edge_from_u, std::move(intersection)); - - const auto possible_turns = turn_analysis.transformIntersectionIntoTurns(intersection); + intersection = turn_lane_handler.assignTurnLanes( + node_along_road_entering, incoming_edge, std::move(intersection)); // the entry class depends on the turn, so we have to classify the interesction for // every edge @@ -412,12 +447,16 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( return bearing_class_hash.find(bearing_class)->second; } }(turn_classification.second); - bearing_class_by_node_based_node[node_v] = bearing_class_id; + bearing_class_by_node_based_node[node_at_center_of_intersection] = bearing_class_id; - for (const auto turn : possible_turns) + for (const auto &turn : intersection) { + // only keep valid turns + if (!turn.entry_allowed) + continue; + // only add an edge if turn is not prohibited - const EdgeData &edge_data1 = m_node_based_graph->GetEdgeData(edge_from_u); + const EdgeData &edge_data1 = m_node_based_graph->GetEdgeData(incoming_edge); const EdgeData &edge_data2 = m_node_based_graph->GetEdgeData(turn.eid); BOOST_ASSERT(edge_data1.edge_id != edge_data2.edge_id); @@ -426,7 +465,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // the following is the core of the loop. unsigned distance = edge_data1.distance; - if (m_traffic_lights.find(node_v) != m_traffic_lights.end()) + if (m_traffic_lights.find(node_at_center_of_intersection) != m_traffic_lights.end()) { distance += profile_properties.traffic_signal_penalty; } @@ -435,7 +474,6 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( scripting_environment.GetTurnPenalty(180. - turn.angle); const auto turn_instruction = turn.instruction; - if (turn_instruction.direction_modifier == guidance::DirectionModifier::UTurn) { distance += profile_properties.u_turn_penalty; @@ -447,16 +485,16 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( distance += turn_penalty; const bool is_encoded_forwards = - m_compressed_edge_container.HasZippedEntryForForwardID(edge_from_u); + m_compressed_edge_container.HasZippedEntryForForwardID(incoming_edge); const bool is_encoded_backwards = - m_compressed_edge_container.HasZippedEntryForReverseID(edge_from_u); + m_compressed_edge_container.HasZippedEntryForReverseID(incoming_edge); BOOST_ASSERT(is_encoded_forwards || is_encoded_backwards); if (is_encoded_forwards) { original_edge_data_vector.emplace_back( - GeometryID{ - m_compressed_edge_container.GetZippedPositionForForwardID(edge_from_u), - true}, + GeometryID{m_compressed_edge_container.GetZippedPositionForForwardID( + incoming_edge), + true}, edge_data1.name_id, turn.lane_data_id, turn_instruction, @@ -468,9 +506,9 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( else if (is_encoded_backwards) { original_edge_data_vector.emplace_back( - GeometryID{ - m_compressed_edge_container.GetZippedPositionForReverseID(edge_from_u), - false}, + GeometryID{m_compressed_edge_container.GetZippedPositionForReverseID( + incoming_edge), + false}, edge_data1.name_id, turn.lane_data_id, turn_instruction, @@ -498,6 +536,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( distance, true, false); + BOOST_ASSERT(original_edges_counter == m_edge_based_edge_list.size()); // Here is where we write out the mapping between the edge-expanded edges, and // the node-based edges that are originally used to calculate the `distance` @@ -514,8 +553,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( if (generate_edge_lookup) { const auto node_based_edges = - m_compressed_edge_container.GetBucketReference(edge_from_u); - NodeID previous = node_u; + m_compressed_edge_container.GetBucketReference(incoming_edge); + NodeID previous = node_along_road_entering; const unsigned node_count = node_based_edges.size() + 1; const QueryNode &first_node = m_node_info_list[previous]; @@ -547,18 +586,19 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // If this edge is 'trivial' -- where the compressed edge corresponds // exactly to an original OSM segment -- we can pull the turn's preceding - // node ID directly with `node_u`; otherwise, we need to look up the node + // node ID directly with `node_along_road_entering`; otherwise, we need to look + // up the node // immediately preceding the turn from the compressed edge container. - const bool isTrivial = m_compressed_edge_container.IsTrivial(edge_from_u); + const bool isTrivial = m_compressed_edge_container.IsTrivial(incoming_edge); const auto &from_node = isTrivial - ? m_node_info_list[node_u] + ? m_node_info_list[node_along_road_entering] : m_node_info_list[m_compressed_edge_container.GetLastEdgeSourceID( - edge_from_u)]; + incoming_edge)]; const auto &via_node = m_node_info_list[m_compressed_edge_container.GetLastEdgeTargetID( - edge_from_u)]; + incoming_edge)]; const auto &to_node = m_node_info_list[m_compressed_edge_container.GetFirstEdgeTargetID( turn.eid)]; diff --git a/src/extractor/extractor.cpp b/src/extractor/extractor.cpp index ac884a801..98de089be 100644 --- a/src/extractor/extractor.cpp +++ b/src/extractor/extractor.cpp @@ -245,7 +245,6 @@ int Extractor::run(ScriptingEnvironment &scripting_environment) // Transform the node-based graph that OSM is based on into an edge-based graph // that is better for routing. Every edge becomes a node, and every valid // movement (e.g. turn from A->B, and B->A) becomes an edge - // util::SimpleLogger().Write() << "Generating edge-expanded graph representation"; TIMER_START(expansion); diff --git a/src/extractor/guidance/coordinate_extractor.cpp b/src/extractor/guidance/coordinate_extractor.cpp index ed89dafde..f02b83e1b 100644 --- a/src/extractor/guidance/coordinate_extractor.cpp +++ b/src/extractor/guidance/coordinate_extractor.cpp @@ -32,7 +32,7 @@ const constexpr double LOOKAHEAD_DISTANCE_WITHOUT_LANES = 10.0; // smaller widths, ranging from 2.5 to 3.25 meters. As a compromise, we use // the 3.25 here for our angle calculations const constexpr double ASSUMED_LANE_WIDTH = 3.25; -const constexpr double FAR_LOOKAHEAD_DISTANCE = 20.0; +const constexpr double FAR_LOOKAHEAD_DISTANCE = 40.0; // The count of lanes assumed when no lanes are present. Since most roads will have lanes for both // directions or a lane count specified, we use 2. Overestimating only makes our calculations safer, @@ -94,16 +94,34 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( const std::uint8_t intersection_lanes, std::vector coordinates) const { + const auto is_valid_result = [&](const util::Coordinate coordinate) { + return util::Coordinate(traversed_in_reverse + ? node_coordinates[to_node] + : node_coordinates[intersection_node]) != coordinate; + }; + // this is only used for debug purposes in assertions. We don't want warnings about it + (void)is_valid_result; + + // the lane count might not always be set. We need to assume a positive number, though. Here we + // select the number of lanes to operate on const auto considered_lanes = GetOffsetCorrectionFactor(node_based_graph.GetEdgeData(turn_edge).road_classification) * ((intersection_lanes == 0) ? ASSUMED_LANE_COUNT : intersection_lanes); + // Fallback. These roads are small broken self-loops that shouldn't be in the data at all + if (intersection_node == to_node) + return coordinates[1]; + /* if we are looking at a straight line, we don't care where exactly the coordinate * is. Simply return the final coordinate. Turn angles/turn vectors are the same no matter which * coordinate we look at. */ if (coordinates.size() <= 2) + { + // Here we can't check for validity, due to possible dead-ends with repeated coordinates + // BOOST_ASSERT(is_valid_result(coordinates.back())); return coordinates.back(); + } // due to repeated coordinates / smaller offset errors we skip over the very first parts of the // coordinate set to add a small level of fault tolerance @@ -111,9 +129,12 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( // fallback, mostly necessary for dead ends if (intersection_node == to_node) - return TrimCoordinatesToLength(std::move(coordinates), - distance_to_skip_over_due_to_coordinate_inaccuracies) - .back(); + { + const auto result = ExtractCoordinateAtLength( + distance_to_skip_over_due_to_coordinate_inaccuracies, coordinates); + BOOST_ASSERT(is_valid_result(coordinates.back())); + return result; + } // If this reduction leaves us with only two coordinates, the turns/angles are represented in a // valid way. Only curved roads and other difficult scenarios will require multiple coordinates. @@ -124,9 +145,12 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( // roundabouts, check early to avoid other costly checks if (turn_edge_data.roundabout || turn_edge_data.circular) - return TrimCoordinatesToLength(std::move(coordinates), - distance_to_skip_over_due_to_coordinate_inaccuracies) - .back(); + { + const auto result = ExtractCoordinateAtLength( + distance_to_skip_over_due_to_coordinate_inaccuracies, coordinates); + BOOST_ASSERT(is_valid_result(result)); + return result; + } const util::Coordinate turn_coordinate = node_coordinates[traversed_in_reverse ? to_node : intersection_node]; @@ -144,9 +168,17 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( if (coordinates.size() > 2 && util::coordinate_calculation::haversineDistance(turn_coordinate, coordinates[1]) < ASSUMED_LANE_WIDTH) - return GetCorrectedCoordinate(turn_coordinate, coordinates[1], coordinates.back()); + { + const auto result = + GetCorrectedCoordinate(turn_coordinate, coordinates[1], coordinates.back()); + BOOST_ASSERT(is_valid_result(result)); + return result; + } else + { + BOOST_ASSERT(is_valid_result(coordinates.back())); return coordinates.back(); + } } const auto first_distance = @@ -165,6 +197,7 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( if (first_coordinate_is_far_away) { + BOOST_ASSERT(is_valid_result(coordinates[1])); return coordinates[1]; } @@ -197,9 +230,16 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( segment_distances.back() = std::min(segment_distances.back(), lookahead_distance); BOOST_ASSERT(segment_distances.size() == coordinates.size()); - // if we are now left with two, well than we don't have to worry - if (coordinates.size() == 2) + const auto total_distance = + std::accumulate(segment_distances.begin(), segment_distances.end(), 0.); + + // if we are now left with two, well than we don't have to worry, or the segment is very small + if (coordinates.size() == 2 || + total_distance <= distance_to_skip_over_due_to_coordinate_inaccuracies) + { + BOOST_ASSERT(is_valid_result(coordinates.back())); return coordinates.back(); + } const double max_deviation_from_straight = GetMaxDeviation( coordinates.begin(), coordinates.end(), coordinates.front(), coordinates.back()); @@ -207,7 +247,22 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( // if the deviation from a straight line is small, we can savely use the coordinate. We use half // a lane as heuristic to determine if the road is straight enough. if (max_deviation_from_straight < 0.5 * ASSUMED_LANE_WIDTH) - return coordinates.back(); + { + // At loops in traffic circles, we can have small deviations as well (if the circle is tiny) + // As a back-up, we have to check for this case + if (coordinates.front() == coordinates.back()) + { + const auto result = ExtractCoordinateAtLength( + distance_to_skip_over_due_to_coordinate_inaccuracies, coordinates); + BOOST_ASSERT(is_valid_result(result)); + return result; + } + else + { + BOOST_ASSERT(is_valid_result(coordinates.back())); + return coordinates.back(); + } + } /* * if a road turns barely in the beginning, it is similar to the first coordinate being @@ -243,7 +298,9 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( if (starts_of_without_turn) { // skip over repeated coordinates - return TrimCoordinatesToLength(std::move(coordinates), 5, segment_distances).back(); + const auto result = ExtractCoordinateAtLength(5, coordinates, segment_distances); + BOOST_ASSERT(is_valid_result(result)); + return result; } // compute the regression vector based on the sum of least squares @@ -274,18 +331,20 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( util::coordinate_calculation::projectPointOnSegment( regression_line.first, regression_line.second, coordinates.back()) .second; - return GetCorrectedCoordinate(turn_coordinate, coord_between_front, coord_between_back); + const auto result = + GetCorrectedCoordinate(turn_coordinate, coord_between_front, coord_between_back); + BOOST_ASSERT(is_valid_result(result)); + return result; } - const auto total_distance = - std::accumulate(segment_distances.begin(), segment_distances.end(), 0.); - if (IsCurve(coordinates, segment_distances, total_distance, considered_lanes * 0.5 * ASSUMED_LANE_WIDTH, turn_edge_data)) { + if (total_distance <= distance_to_skip_over_due_to_coordinate_inaccuracies) + return coordinates.back(); /* * In curves we now have to distinguish between larger curves and tiny curves modelling the * actual turn in the beginnig. @@ -293,7 +352,8 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( * We distinguish between turns that simply model the initial way of getting onto the * destination lanes and the ones that performa a larger turn. */ - const double offset = 0.5 * considered_lanes * ASSUMED_LANE_WIDTH; + const double offset = + std::min(0.5 * considered_lanes * ASSUMED_LANE_WIDTH, 0.2 * segment_distances.back()); coordinates = TrimCoordinatesToLength(std::move(coordinates), offset, segment_distances); BOOST_ASSERT(coordinates.size() >= 2); segment_distances.resize(coordinates.size()); @@ -303,7 +363,10 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( coordinates = TrimCoordinatesToLength(std::move(coordinates), 0.5 * offset, segment_distances); BOOST_ASSERT(coordinates.size() >= 2); - return GetCorrectedCoordinate(turn_coordinate, coordinates.back(), vector_head); + const auto result = + GetCorrectedCoordinate(turn_coordinate, coordinates.back(), vector_head); + BOOST_ASSERT(is_valid_result(result)); + return result; } if (IsDirectOffset(coordinates, @@ -317,8 +380,11 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( // compare // http://www.openstreetmap.org/search?query=52.411243%2013.363575#map=19/52.41124/13.36357 const auto offset_index = std::max(1, straight_index); - return GetCorrectedCoordinate( + const auto result = GetCorrectedCoordinate( turn_coordinate, coordinates[offset_index], coordinates[offset_index + 1]); + + BOOST_ASSERT(is_valid_result(result)); + return result; } { @@ -340,16 +406,95 @@ util::Coordinate CoordinateExtractor::ExtractRepresentativeCoordinate( regression_line_trimmed.second); if (max_deviation_from_trimmed_regression < 0.5 * ASSUMED_LANE_WIDTH) - return GetCorrectedCoordinate( + { + const auto result = GetCorrectedCoordinate( turn_coordinate, regression_line_trimmed.first, regression_line_trimmed.second); + BOOST_ASSERT(is_valid_result(result)); + return result; + } } } // We use the locations on the regression line to offset the regression line onto the // intersection. - return TrimCoordinatesToLength( - std::move(coordinates), LOOKAHEAD_DISTANCE_WITHOUT_LANES, segment_distances) - .back(); + const auto result = + ExtractCoordinateAtLength(LOOKAHEAD_DISTANCE_WITHOUT_LANES, coordinates, segment_distances); + BOOST_ASSERT(is_valid_result(result)); + return result; +} + +util::Coordinate +CoordinateExtractor::ExtractCoordinateAtLength(const double distance, + const std::vector &coordinates, + const std::vector &length_cache) const +{ + BOOST_ASSERT(length_cache.size() == coordinates.size()); + BOOST_ASSERT(!coordinates.empty()); + + double accumulated_distance = 0.; + auto length_cache_itr = length_cache.begin() + 1; + // find the end of the segment containing the coordinate which is at least distance away + const auto find_coordinate_at_distance = [distance, &accumulated_distance, &length_cache_itr]( + const util::Coordinate /*coordinate*/) mutable { + const auto result = (accumulated_distance + *length_cache_itr) >= distance; + if (!result) + { + accumulated_distance += *length_cache_itr; + ++length_cache_itr; + } + return result; + }; + + // find the beginning fo the segment (begin here and above for the length cache need to match + // up!) + const auto coordinate_base = + std::find_if(coordinates.begin() + 1, coordinates.end(), find_coordinate_at_distance) - 1; + + if (static_cast(std::distance(coordinates.begin(), coordinate_base) + 1) >= + coordinates.size()) + return coordinates.back(); + + const auto interpolation_factor = + ComputeInterpolationFactor(distance - accumulated_distance, 0, *length_cache_itr); + + return util::coordinate_calculation::interpolateLinear( + interpolation_factor, *coordinate_base, *(coordinate_base + 1)); +} + +util::Coordinate CoordinateExtractor::ExtractCoordinateAtLength( + const double distance, const std::vector &coordinates) const +{ + BOOST_ASSERT(!coordinates.empty()); + double accumulated_distance = 0.; + // checks (via its state) for an accumulated distance + const auto coordinate_at_distance = + [ distance, &accumulated_distance, last_coordinate = coordinates.front() ]( + const util::Coordinate coordinate) mutable + { + const double segment_distance = + util::coordinate_calculation::haversineDistance(last_coordinate, coordinate); + const auto result = (accumulated_distance + segment_distance) >= distance; + if (!result) + accumulated_distance += segment_distance; + + return result; + }; + + // find the begin of the segment containing the coordinate + const auto coordinate_base = + std::find_if(coordinates.begin() + 1, coordinates.end(), coordinate_at_distance) - 1; + + if (static_cast(std::distance(coordinates.begin(), coordinate_base) + 1) >= + coordinates.size()) + return coordinates.back(); + + const auto interpolation_factor = ComputeInterpolationFactor( + distance - accumulated_distance, + 0, + util::coordinate_calculation::haversineDistance(*coordinate_base, *(coordinate_base + 1))); + + return util::coordinate_calculation::interpolateLinear( + interpolation_factor, *coordinate_base, *(coordinate_base + 1)); } util::Coordinate CoordinateExtractor::GetCoordinateCloseToTurn(const NodeID from_node, @@ -691,7 +836,7 @@ bool CoordinateExtractor::IsDirectOffset(const std::vector &co return false; // the segment itself cannot be short - if (segment_length < 0.8 * FAR_LOOKAHEAD_DISTANCE) + if (segment_length < 0.4 * FAR_LOOKAHEAD_DISTANCE) return false; // if the remaining segment is short, we don't consider it an offset @@ -733,17 +878,19 @@ CoordinateExtractor::PrepareLengthCache(const std::vector &coo segment_distances.reserve(coordinates.size()); segment_distances.push_back(0); // sentinel - auto last_coordinate = coordinates.front(); - std::find_if( - std::next(std::begin(coordinates)), - std::end(coordinates), - [&last_coordinate, limit, &segment_distances](const util::Coordinate current_coordinate) { - const auto distance = util::coordinate_calculation::haversineDistance( - last_coordinate, current_coordinate); - last_coordinate = current_coordinate; - segment_distances.push_back(distance); - return distance >= limit; - }); + std::find_if(std::next(std::begin(coordinates)), std::end(coordinates), [ + last_coordinate = coordinates.front(), + limit, + &segment_distances, + accumulated_distance = 0. + ](const util::Coordinate current_coordinate) mutable { + const auto distance = + util::coordinate_calculation::haversineDistance(last_coordinate, current_coordinate); + accumulated_distance += distance; + last_coordinate = current_coordinate; + segment_distances.push_back(distance); + return accumulated_distance >= limit; + }); return segment_distances; } @@ -775,21 +922,18 @@ CoordinateExtractor::TrimCoordinatesToLength(std::vector coord if (use_cache && length_cache.back() < desired_length && coordinates.size() >= 2) { - if (coordinates.size() == length_cache.size()) - return coordinates; + if (coordinates.size() > length_cache.size()) + coordinates.erase(coordinates.begin() + length_cache.size(), coordinates.end()); - else - { - const auto distance_between_last_coordinates = - util::coordinate_calculation::haversineDistance(*(coordinates.end() - 2), - *(coordinates.end() - 1)); - const auto interpolation_factor = ComputeInterpolationFactor( - desired_length - length_cache.back(), 0, distance_between_last_coordinates); + const auto distance_between_last_coordinates = + util::coordinate_calculation::haversineDistance(*(coordinates.end() - 2), + *(coordinates.end() - 1)); + const auto interpolation_factor = + ComputeInterpolationFactor(length_cache.back(), 0, distance_between_last_coordinates); - coordinates.back() = util::coordinate_calculation::interpolateLinear( - interpolation_factor, *(coordinates.end() - 2), coordinates.back()); - return coordinates; - } + coordinates.back() = util::coordinate_calculation::interpolateLinear( + interpolation_factor, *(coordinates.end() - 2), coordinates.back()); + return coordinates; } else { diff --git a/src/extractor/guidance/intersection.cpp b/src/extractor/guidance/intersection.cpp index b23e703b9..45a56f936 100644 --- a/src/extractor/guidance/intersection.cpp +++ b/src/extractor/guidance/intersection.cpp @@ -17,11 +17,9 @@ namespace extractor namespace guidance { -ConnectedRoad::ConnectedRoad(const TurnOperation turn, - const bool entry_allowed, - boost::optional segment_length) - : TurnOperation(turn), entry_allowed(entry_allowed), segment_length(segment_length) +bool IntersectionViewData::CompareByAngle(const IntersectionViewData &other) const { + return angle < other.angle; } bool ConnectedRoad::compareByAngle(const ConnectedRoad &other) const { return angle < other.angle; } @@ -72,6 +70,23 @@ std::string toString(const ConnectedRoad &road) return result; } +IntersectionView::Base::iterator IntersectionView::findClosestTurn(double angle) +{ + // use the const operator to avoid code duplication + return begin() + + std::distance(cbegin(), + static_cast(this)->findClosestTurn(angle)); +} + +IntersectionView::Base::const_iterator IntersectionView::findClosestTurn(double angle) const +{ + return std::min_element( + begin(), end(), [angle](const IntersectionViewData &lhs, const IntersectionViewData &rhs) { + return util::guidance::angularDeviation(lhs.angle, angle) < + util::guidance::angularDeviation(rhs.angle, angle); + }); +} + Intersection::Base::iterator Intersection::findClosestTurn(double angle) { // use the const operator to avoid code duplication diff --git a/src/extractor/guidance/intersection_generator.cpp b/src/extractor/guidance/intersection_generator.cpp index 4ceb2e5a2..f04f6fce5 100644 --- a/src/extractor/guidance/intersection_generator.cpp +++ b/src/extractor/guidance/intersection_generator.cpp @@ -2,6 +2,7 @@ #include "extractor/guidance/constants.hpp" #include "extractor/guidance/toolkit.hpp" +#include "util/bearing.hpp" #include "util/guidance/toolkit.hpp" #include @@ -39,11 +40,88 @@ IntersectionGenerator::IntersectionGenerator( { } -Intersection IntersectionGenerator::operator()(const NodeID from_node, const EdgeID via_eid) const +IntersectionView IntersectionGenerator::operator()(const NodeID from_node, + const EdgeID via_eid) const { return GetConnectedRoads(from_node, via_eid, USE_HIGH_PRECISION_MODE); } +IntersectionShape +IntersectionGenerator::ComputeIntersectionShape(const NodeID node_at_center_of_intersection, + const boost::optional sorting_base, + const bool use_low_precision_angles) const +{ + IntersectionShape intersection; + // reserve enough items (+ the possibly missing u-turn edge) + const auto intersection_degree = node_based_graph.GetOutDegree(node_at_center_of_intersection); + intersection.reserve(intersection_degree); + const util::Coordinate turn_coordinate = node_info_list[node_at_center_of_intersection]; + + // number of lanes at the intersection changes how far we look down the road + const auto intersection_lanes = + getLaneCountAtIntersection(node_at_center_of_intersection, node_based_graph); + + for (const EdgeID edge_connected_to_intersection : + node_based_graph.GetAdjacentEdgeRange(node_at_center_of_intersection)) + { + BOOST_ASSERT(edge_connected_to_intersection != SPECIAL_EDGEID); + const NodeID to_node = node_based_graph.GetTarget(edge_connected_to_intersection); + double bearing = 0.; + + auto coordinates = coordinate_extractor.GetCoordinatesAlongRoad( + node_at_center_of_intersection, edge_connected_to_intersection, !INVERT, to_node); + + const auto segment_length = util::coordinate_calculation::getLength( + coordinates, util::coordinate_calculation::haversineDistance); + + const auto extract_coordinate = [&](const NodeID from_node, + const EdgeID via_eid, + const bool traversed_in_reverse, + const NodeID to_node) { + return (use_low_precision_angles || intersection_degree <= 2) + ? coordinate_extractor.GetCoordinateCloseToTurn( + from_node, via_eid, traversed_in_reverse, to_node) + : coordinate_extractor.ExtractRepresentativeCoordinate( + from_node, + via_eid, + traversed_in_reverse, + to_node, + intersection_lanes, + std::move(coordinates)); + }; + + // we have to look down the road a bit to get the correct turn + const auto coordinate_along_edge_leaving = extract_coordinate( + node_at_center_of_intersection, edge_connected_to_intersection, !INVERT, to_node); + + bearing = + util::coordinate_calculation::bearing(turn_coordinate, coordinate_along_edge_leaving); + + intersection.push_back({edge_connected_to_intersection, bearing, segment_length}); + } + + if (!intersection.empty()) + { + const auto base_bearing = [&]() { + if (sorting_base) + { + const auto itr = + std::find_if(intersection.begin(), + intersection.end(), + [&](const IntersectionShapeData &data) { + return node_based_graph.GetTarget(data.eid) == *sorting_base; + }); + if (itr != intersection.end()) + return util::bearing::reverseBearing(itr->bearing); + } + return util::bearing::reverseBearing(intersection.begin()->bearing); + }(); + std::sort( + intersection.begin(), intersection.end(), makeCompareShapeDataByBearing(base_bearing)); + } + return intersection; +} + // a // | // | @@ -57,185 +135,22 @@ Intersection IntersectionGenerator::operator()(const NodeID from_node, const Edg // That means we not only get (from_node, turn_node, c) in the above example // but also (from_node, turn_node, a), (from_node, turn_node, b). These turns are // marked as invalid and only needed for intersection classification. -Intersection IntersectionGenerator::GetConnectedRoads(const NodeID from_node, - const EdgeID via_eid, - const bool use_low_precision_angles) const +IntersectionView IntersectionGenerator::GetConnectedRoads(const NodeID from_node, + const EdgeID via_eid, + const bool use_low_precision_angles) const { - Intersection intersection; - const NodeID turn_node = node_based_graph.GetTarget(via_eid); - // reserve enough items (+ the possibly missing u-turn edge) - intersection.reserve(node_based_graph.GetOutDegree(turn_node) + 1); - const NodeID only_restriction_to_node = [&]() { - // If only restrictions refer to invalid ways somewhere far away, we rather ignore the - // restriction than to not route over the intersection at all. - const auto only_restriction_to_node = - restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node); - if (only_restriction_to_node != SPECIAL_NODEID) - { - // check if we can find an edge in the edge-rage - for (const auto onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) - if (only_restriction_to_node == node_based_graph.GetTarget(onto_edge)) - return only_restriction_to_node; - } - // Ignore broken only restrictions. - return SPECIAL_NODEID; - }(); - const bool is_barrier_node = barrier_nodes.find(turn_node) != barrier_nodes.end(); + // make sure the via-eid is valid + BOOST_ASSERT([this](const NodeID from_node, const EdgeID via_eid) { + const auto range = node_based_graph.GetAdjacentEdgeRange(from_node); + return range.front() <= via_eid && via_eid <= range.back(); + }(from_node, via_eid)); - bool has_uturn_edge = false; - bool uturn_could_be_valid = false; - const util::Coordinate turn_coordinate = node_info_list[turn_node]; - - const auto intersection_lanes = getLaneCountAtIntersection(turn_node, node_based_graph); - - const auto extract_coordinate = [&](const NodeID from_node, - const EdgeID via_eid, - const bool traversed_in_reverse, - const NodeID to_node) { - return use_low_precision_angles - ? coordinate_extractor.GetCoordinateCloseToTurn( - from_node, via_eid, traversed_in_reverse, to_node) - : coordinate_extractor.GetCoordinateAlongRoad( - from_node, via_eid, traversed_in_reverse, to_node, intersection_lanes); - }; - - // The first coordinate (the origin) can depend on the number of lanes turning onto, - // just as the target coordinate can. Here we compute the corrected coordinate for the - // incoming edge - - // to compute the length along the path - const auto in_segment_length = [&]() { - const auto in_coordinates = - coordinate_extractor.GetCoordinatesAlongRoad(from_node, via_eid, INVERT, turn_node); - return util::coordinate_calculation::getLength( - in_coordinates, util::coordinate_calculation::haversineDistance); - }(); - const auto first_coordinate = extract_coordinate(from_node, via_eid, INVERT, turn_node); - - for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) - { - BOOST_ASSERT(onto_edge != SPECIAL_EDGEID); - const NodeID to_node = node_based_graph.GetTarget(onto_edge); - const auto &onto_data = node_based_graph.GetEdgeData(onto_edge); - - bool turn_is_valid = - // reverse edges are never valid turns because the resulting turn would look like this: - // from_node --via_edge--> turn_node <--onto_edge-- to_node - // however we need this for capture intersection shape for incoming one-ways - !onto_data.reversed && - // we are not turning over a barrier - (!is_barrier_node || from_node == to_node) && - // We are at an only_-restriction but not at the right turn. - (only_restriction_to_node == SPECIAL_NODEID || to_node == only_restriction_to_node) && - // the turn is not restricted - !restriction_map.CheckIfTurnIsRestricted(from_node, turn_node, to_node); - - double bearing = 0., out_segment_length = 0., angle = 0.; - if (from_node == to_node) - { - bearing = util::coordinate_calculation::bearing(turn_coordinate, first_coordinate); - uturn_could_be_valid = turn_is_valid; - if (turn_is_valid && !is_barrier_node) - { - // we only add u-turns for dead-end streets. - if (node_based_graph.GetOutDegree(turn_node) > 1) - { - auto number_of_emmiting_bidirectional_edges = 0; - for (auto edge : node_based_graph.GetAdjacentEdgeRange(turn_node)) - { - auto target = node_based_graph.GetTarget(edge); - auto reverse_edge = node_based_graph.FindEdge(target, turn_node); - BOOST_ASSERT(reverse_edge != SPECIAL_EDGEID); - if (!node_based_graph.GetEdgeData(reverse_edge).reversed) - { - ++number_of_emmiting_bidirectional_edges; - } - } - // is a dead-end, only possible road is to go back - turn_is_valid = number_of_emmiting_bidirectional_edges <= 1; - } - } - has_uturn_edge = true; - out_segment_length = in_segment_length; - BOOST_ASSERT(angle >= 0. && angle < std::numeric_limits::epsilon()); - } - else - { - // the default distance we lookahead on a road. This distance prevents small mapping - // errors to impact the turn angles. - { - // segment of out segment - const auto out_coordinates = coordinate_extractor.GetCoordinatesAlongRoad( - turn_node, onto_edge, !INVERT, to_node); - out_segment_length = util::coordinate_calculation::getLength( - out_coordinates, util::coordinate_calculation::haversineDistance); - } - const auto third_coordinate = extract_coordinate(turn_node, onto_edge, !INVERT, to_node); - - angle = util::coordinate_calculation::computeAngle( - first_coordinate, turn_coordinate, third_coordinate); - - bearing = util::coordinate_calculation::bearing(turn_coordinate, third_coordinate); - - if (std::abs(angle) < std::numeric_limits::epsilon()) - has_uturn_edge = true; - } - intersection.push_back( - ConnectedRoad(TurnOperation{onto_edge, - angle, - bearing, - {TurnType::Invalid, DirectionModifier::UTurn}, - INVALID_LANE_DATAID}, - turn_is_valid, - out_segment_length)); - } - - // We hit the case of a street leading into nothing-ness. Since the code here assumes - // that this - // will never happen we add an artificial invalid uturn in this case. - if (!has_uturn_edge) - { - const double bearing = - util::coordinate_calculation::bearing(turn_coordinate, first_coordinate); - - intersection.push_back({TurnOperation{via_eid, - 0., - bearing, - {TurnType::Invalid, DirectionModifier::UTurn}, - INVALID_LANE_DATAID}, - false, - in_segment_length}); - } - - std::sort(std::begin(intersection), - std::end(intersection), - std::mem_fn(&ConnectedRoad::compareByAngle)); - - BOOST_ASSERT(intersection[0].angle >= 0. && - intersection[0].angle < std::numeric_limits::epsilon()); - - const auto valid_count = - boost::count_if(intersection, [](const ConnectedRoad &road) { return road.entry_allowed; }); - if (0 == valid_count && uturn_could_be_valid) - { - // after intersections sorting by angles, find the u-turn with (from_node == - // to_node) - // that was inserted together with setting uturn_could_be_valid flag - std::size_t self_u_turn = 0; - while (self_u_turn < intersection.size() && - intersection[self_u_turn].angle < std::numeric_limits::epsilon() && - from_node != node_based_graph.GetTarget(intersection[self_u_turn].eid)) - { - ++self_u_turn; - } - - BOOST_ASSERT(from_node == node_based_graph.GetTarget(intersection[self_u_turn].eid)); - intersection[self_u_turn].entry_allowed = true; - } - return intersection; + auto intersection = ComputeIntersectionShape( + node_based_graph.GetTarget(via_eid), boost::none, use_low_precision_angles); + return TransformIntersectionShapeIntoView(from_node, via_eid, std::move(intersection)); } -Intersection +IntersectionView IntersectionGenerator::GetActualNextIntersection(const NodeID starting_node, const EdgeID via_edge, NodeID *resulting_from_node = nullptr, @@ -278,6 +193,191 @@ IntersectionGenerator::GetActualNextIntersection(const NodeID starting_node, return GetConnectedRoads(query_node, query_edge); } +IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView( + const NodeID previous_node, + const EdgeID entering_via_edge, + const IntersectionShape &intersection_shape) const +{ + // requires a copy of the intersection + return TransformIntersectionShapeIntoView(previous_node, + entering_via_edge, + intersection_shape, // creates a copy + intersection_shape, // reference to local + {}); // empty vector of performed merges +} + +IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView( + const NodeID previous_node, + const EdgeID entering_via_edge, + const IntersectionShape &normalised_intersection, + const IntersectionShape &intersection, + const std::vector> &performed_merges) const +{ + const auto node_at_intersection = node_based_graph.GetTarget(entering_via_edge); + + // check if there is a single valid turn entering the current intersection + const auto only_valid_turn = GetOnlyAllowedTurnIfExistent(previous_node, node_at_intersection); + + // barriers change our behaviour regarding u-turns + const bool is_barrier_node = barrier_nodes.find(node_at_intersection) != barrier_nodes.end(); + + const auto connect_to_previous_node = [this, previous_node](const IntersectionShapeData road) { + return node_based_graph.GetTarget(road.eid) == previous_node; + }; + + // check which of the edges is the u-turn edge + const auto uturn_edge_itr = + std::find_if(intersection.begin(), intersection.end(), connect_to_previous_node); + + // there needs to be a connection, otherwise stuff went seriously wrong. Note that this is not + // necessarily the same id as `entering_via_edge`. + // In cases where parallel edges are present, we only remember the minimal edge. Both share + // exactly the same coordinates, so the u-turn is still the best choice here. + BOOST_ASSERT(uturn_edge_itr != intersection.end()); + + const auto is_restricted = [&](const NodeID destination) { + // check if we have a dedicated destination + if (only_valid_turn && *only_valid_turn != destination) + return true; + + // not explicitly forbidden + return restriction_map.CheckIfTurnIsRestricted( + previous_node, node_at_intersection, destination); + }; + + const auto is_allowed_turn = [&](const IntersectionShapeData &road) { + const auto &road_data = node_based_graph.GetEdgeData(road.eid); + const NodeID road_destination_node = node_based_graph.GetTarget(road.eid); + // reverse edges are never valid turns because the resulting turn would look like this: + // from_node --via_edge--> node_at_intersection <--onto_edge-- to_node + // however we need this for capture intersection shape for incoming one-ways + return !road_data.reversed && + // we are not turning over a barrier + (!is_barrier_node || road_destination_node == previous_node) && + // don't allow restricted turns + !is_restricted(road_destination_node); + + }; + + // due to merging of roads, the u-turn might actually not be part of the intersection anymore + const auto uturn_bearing = [&]() { + const auto merge_entry = std::find_if( + performed_merges.begin(), performed_merges.end(), [&uturn_edge_itr](const auto entry) { + return entry.first == uturn_edge_itr->eid; + }); + if (merge_entry != performed_merges.end()) + { + const auto merged_into_id = merge_entry->second; + const auto merged_u_turn = std::find_if( + normalised_intersection.begin(), + normalised_intersection.end(), + [&](const IntersectionShapeData &road) { return road.eid == merged_into_id; }); + BOOST_ASSERT(merged_u_turn != normalised_intersection.end()); + return util::bearing::reverseBearing(merged_u_turn->bearing); + } + else + { + const auto uturn_edge_at_normalised_intersection_itr = + std::find_if(normalised_intersection.begin(), + normalised_intersection.end(), + connect_to_previous_node); + BOOST_ASSERT(uturn_edge_at_normalised_intersection_itr != + normalised_intersection.end()); + return util::bearing::reverseBearing( + uturn_edge_at_normalised_intersection_itr->bearing); + } + }(); + + IntersectionView intersection_view; + intersection_view.reserve(normalised_intersection.size()); + std::transform(normalised_intersection.begin(), + normalised_intersection.end(), + std::back_inserter(intersection_view), + [&](const IntersectionShapeData &road) { + return IntersectionViewData( + road, + is_allowed_turn(road), + util::bearing::angleBetweenBearings(uturn_bearing, road.bearing)); + }); + + const auto uturn_edge_at_intersection_view_itr = + std::find_if(intersection_view.begin(), intersection_view.end(), connect_to_previous_node); + // number of found valid exit roads + const auto valid_count = + std::count_if(intersection_view.begin(), + intersection_view.end(), + [](const IntersectionViewData &road) { return road.entry_allowed; }); + // in general, we don't wan't to allow u-turns. If we don't look at a barrier, we have to check + // for dead end streets. These are the only ones that we allow uturns for, next to barriers + // (which are also kind of a dead end, but we don't have to check these again :)) + if (uturn_edge_at_intersection_view_itr != intersection_view.end() && + ((uturn_edge_at_intersection_view_itr->entry_allowed && !is_barrier_node && + valid_count != 1) || + valid_count == 0)) + { + const auto allow_uturn_at_dead_end = [&]() { + const auto &uturn_data = node_based_graph.GetEdgeData(uturn_edge_itr->eid); + + // we can't turn back onto oneway streets + if (uturn_data.reversed) + return false; + + // don't allow explicitly restricted turns + if (is_restricted(previous_node)) + return false; + + // we define dead ends as roads that can only be entered via the possible u-turn + const auto is_bidirectional = [&](const EdgeID entering_via_edge) { + const auto to_node = node_based_graph.GetTarget(entering_via_edge); + const auto reverse_edge = node_based_graph.FindEdge(to_node, node_at_intersection); + BOOST_ASSERT(reverse_edge != SPECIAL_EDGEID); + return !node_based_graph.GetEdgeData(reverse_edge).reversed; + }; + + const auto bidirectional_edges = [&]() { + std::uint32_t count = 0; + for (const auto eid : node_based_graph.GetAdjacentEdgeRange(node_at_intersection)) + if (is_bidirectional(eid)) + ++count; + return count; + }(); + + // Checking for dead-end streets is kind of difficult. There is obvious dead ends + // (single road connected) + return bidirectional_edges <= 1; + }(); + uturn_edge_at_intersection_view_itr->entry_allowed = allow_uturn_at_dead_end; + } + std::sort(std::begin(intersection_view), + std::end(intersection_view), + std::mem_fn(&IntersectionViewData::CompareByAngle)); + + BOOST_ASSERT(intersection_view[0].angle >= 0. && + intersection_view[0].angle < std::numeric_limits::epsilon()); + + return intersection_view; +} + +boost::optional +IntersectionGenerator::GetOnlyAllowedTurnIfExistent(const NodeID coming_from_node, + const NodeID node_at_intersection) const +{ + // If only restrictions refer to invalid ways somewhere far away, we rather ignore the + // restriction than to not route over the intersection at all. + const auto only_restriction_to_node = + restriction_map.CheckForEmanatingIsOnlyTurn(coming_from_node, node_at_intersection); + if (only_restriction_to_node != SPECIAL_NODEID) + { + // if the mentioned node does not exist anymore, we don't return it. This checks for broken + // turn restrictions + for (const auto onto_edge : node_based_graph.GetAdjacentEdgeRange(node_at_intersection)) + if (only_restriction_to_node == node_based_graph.GetTarget(onto_edge)) + return only_restriction_to_node; + } + // Ignore broken only restrictions. + return boost::none; +} + const CoordinateExtractor &IntersectionGenerator::GetCoordinateExtractor() const { return coordinate_extractor; diff --git a/src/extractor/guidance/intersection_handler.cpp b/src/extractor/guidance/intersection_handler.cpp index fad091116..84e1e1920 100644 --- a/src/extractor/guidance/intersection_handler.cpp +++ b/src/extractor/guidance/intersection_handler.cpp @@ -749,16 +749,9 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge, // obvious due to a very short segment in between. So if the segment coming in is very // short, we check the previous intersection for other continues in the opposite bearing. const auto node_at_intersection = node_based_graph.GetTarget(via_edge); - const util::Coordinate coordinate_at_intersection = node_info_list[node_at_intersection]; - - const auto node_at_u_turn = node_based_graph.GetTarget(intersection[0].eid); - const util::Coordinate coordinate_at_u_turn = node_info_list[node_at_u_turn]; const double constexpr MAX_COLLAPSE_DISTANCE = 30; - const auto distance_at_u_turn = intersection[0].segment_length - ? *intersection[0].segment_length - : util::coordinate_calculation::haversineDistance( - coordinate_at_intersection, coordinate_at_u_turn); + const auto distance_at_u_turn = intersection[0].segment_length; if (distance_at_u_turn < MAX_COLLAPSE_DISTANCE) { // this request here actually goes against the direction of the ingoing edgeid. This can diff --git a/src/extractor/guidance/intersection_normalizer.cpp b/src/extractor/guidance/intersection_normalizer.cpp index 85a150c14..a36c756c8 100644 --- a/src/extractor/guidance/intersection_normalizer.cpp +++ b/src/extractor/guidance/intersection_normalizer.cpp @@ -1,5 +1,6 @@ #include "extractor/guidance/intersection_normalizer.hpp" #include "extractor/guidance/toolkit.hpp" +#include "util/bearing.hpp" #include "util/guidance/toolkit.hpp" namespace osrm @@ -21,33 +22,71 @@ IntersectionNormalizer::IntersectionNormalizer( { } -Intersection IntersectionNormalizer::operator()(const NodeID node_at_intersection, - Intersection intersection) const +std::pair>> IntersectionNormalizer:: +operator()(const NodeID node_at_intersection, IntersectionShape intersection) const { - return AdjustForJoiningRoads( - node_at_intersection, MergeSegregatedRoads(node_at_intersection, std::move(intersection))); + auto merged_shape_and_merges = + MergeSegregatedRoads(node_at_intersection, std::move(intersection)); + merged_shape_and_merges.first = AdjustBearingsForMergeAtDestination( + node_at_intersection, std::move(merged_shape_and_merges.first)); + return merged_shape_and_merges; +} + +bool IntersectionNormalizer::CanMerge(const NodeID intersection_node, + const IntersectionShape &intersection, + std::size_t first_index, + std::size_t second_index) const +{ + BOOST_ASSERT(((first_index + 1) % intersection.size()) == second_index); + + // call wrapper to capture intersection_node and intersection + const auto mergable = [this, intersection_node, &intersection](const std::size_t left_index, + const std::size_t right_index) { + return InnerCanMerge(intersection_node, intersection, left_index, right_index); + }; + + /* + * 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 + */ + if (mergable(first_index, second_index)) + { + const auto is_distinct_merge = + !mergable(second_index, (second_index + 1) % intersection.size()) && + !mergable((first_index + intersection.size() - 1) % intersection.size(), first_index) && + !mergable(second_index, + (first_index + intersection.size() - 1) % intersection.size()) && + !mergable(first_index, (second_index + 1) % intersection.size()); + return is_distinct_merge; + } + else + return false; } // Checks for mergability of two ways that represent the same intersection. For further -// information -// see interface documentation in header. -bool IntersectionNormalizer::CanMerge(const NodeID node_at_intersection, - const Intersection &intersection, - std::size_t first_index, - std::size_t second_index) const +// information see interface documentation in header. +bool IntersectionNormalizer::InnerCanMerge(const NodeID node_at_intersection, + const IntersectionShape &intersection, + std::size_t first_index, + std::size_t second_index) const { const auto &first_data = node_based_graph.GetEdgeData(intersection[first_index].eid); const auto &second_data = node_based_graph.GetEdgeData(intersection[second_index].eid); // only merge named ids - if (first_data.name_id == EMPTY_NAMEID) + if (first_data.name_id == EMPTY_NAMEID || second_data.name_id == EMPTY_NAMEID) return false; // need to be same name - if (second_data.name_id != EMPTY_NAMEID && - util::guidance::requiresNameAnnounced( + if (util::guidance::requiresNameAnnounced( first_data.name_id, second_data.name_id, name_table, street_name_suffix_table)) return false; + // needs to be symmetrical for names + if (util::guidance::requiresNameAnnounced( + second_data.name_id, first_data.name_id, name_table, street_name_suffix_table)) + return false; // compatibility is required if (first_data.travel_mode != second_data.travel_mode) @@ -64,35 +103,17 @@ bool IntersectionNormalizer::CanMerge(const NodeID node_at_intersection, if (first_data.reversed == second_data.reversed) return false; - // one of them needs to be invalid - if (intersection[first_index].entry_allowed && intersection[second_index].entry_allowed) - return false; - // mergeable if the angle is not too big const auto angle_between = - angularDeviation(intersection[first_index].angle, intersection[second_index].angle); - - const auto intersection_lanes = intersection.getHighestConnectedLaneCount(node_based_graph); - - const auto coordinate_at_in_edge = - intersection_generator.GetCoordinateExtractor().GetCoordinateAlongRoad( - node_at_intersection, - intersection[0].eid, - !INVERT, - node_based_graph.GetTarget(intersection[0].eid), - intersection_lanes); + angularDeviation(intersection[first_index].bearing, intersection[second_index].bearing); const auto coordinate_at_intersection = node_coordinates[node_at_intersection]; if (angle_between >= 120) return false; - const auto isValidYArm = [this, - intersection, - coordinate_at_in_edge, - coordinate_at_intersection, - node_at_intersection](const std::size_t index, - const std::size_t other_index) { + const auto isValidYArm = [this, intersection, coordinate_at_intersection, node_at_intersection]( + const std::size_t index, const std::size_t other_index) { const auto GetActualTarget = [&](const std::size_t index) { EdgeID last_in_edge_id; intersection_generator.GetActualNextIntersection( @@ -108,21 +129,19 @@ bool IntersectionNormalizer::CanMerge(const NodeID node_at_intersection, const auto coordinate_at_target = node_coordinates[target_id]; const auto coordinate_at_other_target = node_coordinates[other_target_id]; - const auto turn_angle = util::coordinate_calculation::computeAngle( - coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_target); - const auto other_turn_angle = util::coordinate_calculation::computeAngle( - coordinate_at_in_edge, coordinate_at_intersection, coordinate_at_other_target); + const auto turn_bearing = + util::coordinate_calculation::bearing(coordinate_at_intersection, coordinate_at_target); + const auto other_turn_bearing = util::coordinate_calculation::bearing( + coordinate_at_intersection, coordinate_at_other_target); + // fuzzy becomes narrower due to minor differences in angle computations, yay floating point const bool becomes_narrower = - angularDeviation(turn_angle, other_turn_angle) < NARROW_TURN_ANGLE && - angularDeviation(turn_angle, other_turn_angle) <= - angularDeviation(intersection[index].angle, intersection[other_index].angle); + angularDeviation(turn_bearing, other_turn_bearing) < NARROW_TURN_ANGLE && + angularDeviation(turn_bearing, other_turn_bearing) <= + angularDeviation(intersection[index].bearing, intersection[other_index].bearing) + + MAXIMAL_ALLOWED_NO_TURN_DEVIATION; - const bool has_same_deviation = - std::abs(angularDeviation(intersection[index].angle, STRAIGHT_ANGLE) - - angularDeviation(intersection[other_index].angle, STRAIGHT_ANGLE)) < - MAXIMAL_ALLOWED_NO_TURN_DEVIATION; - return becomes_narrower || has_same_deviation; + return becomes_narrower; }; const bool is_y_arm_first = isValidYArm(first_index, second_index); @@ -162,8 +181,8 @@ bool IntersectionNormalizer::CanMerge(const NodeID node_at_intersection, // we only allow collapsing of a Y like fork. So the angle to the third index has to be // roughly equal: const auto y_angle_difference = angularDeviation( - angularDeviation(intersection[third_index].angle, intersection[first_index].angle), - angularDeviation(intersection[third_index].angle, intersection[second_index].angle)); + angularDeviation(intersection[third_index].bearing, intersection[first_index].bearing), + angularDeviation(intersection[third_index].bearing, intersection[second_index].bearing)); // Allow larger angles if its three roads only of the same name // This is a heuristic and might need to be revised. const bool assume_y_intersection = @@ -194,8 +213,9 @@ bool IntersectionNormalizer::CanMerge(const NodeID node_at_intersection, * Anything containing the first u-turn in a merge affects all other angles * and is handled separately from all others. */ -Intersection IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node, - Intersection intersection) const +std::pair>> +IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node, + IntersectionShape intersection) const { const auto getRight = [&](std::size_t index) { return (index + intersection.size() - 1) % intersection.size(); @@ -216,34 +236,34 @@ Intersection IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersect { const auto offset = angularDeviation(first, second); auto new_angle = std::max(first, second) + .5 * offset; - if (new_angle > 360) + if (new_angle >= 360) return new_angle - 360; return new_angle; } }; - const auto merge = [combineAngles](const ConnectedRoad &first, - const ConnectedRoad &second) -> ConnectedRoad { - ConnectedRoad result = first.entry_allowed ? first : second; - result.angle = combineAngles(first.angle, second.angle); + // 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, combineAngles, &merging_map](const IntersectionShapeData &first, + const IntersectionShapeData &second) { + IntersectionShapeData result = + !node_based_graph.GetEdgeData(first.eid).reversed ? first : second; result.bearing = combineAngles(first.bearing, second.bearing); - BOOST_ASSERT(0 <= result.angle && result.angle <= 360.0); - BOOST_ASSERT(0 <= result.bearing && result.bearing <= 360.0); + BOOST_ASSERT(0 <= result.bearing && result.bearing < 360.0); + // the other ID + const auto merged_from = result.eid == first.eid ? second.eid : first.eid; + BOOST_ASSERT( + std::find_if(merging_map.begin(), merging_map.end(), [merged_from](const auto pair) { + return pair.first == merged_from; + }) == merging_map.end()); + merging_map.push_back(std::make_pair(merged_from, result.eid)); return result; }; if (intersection.size() <= 1) - return intersection; - - const bool is_connected_to_roundabout = [this, &intersection]() { - for (const auto &road : intersection) - { - if (node_based_graph.GetEdgeData(road.eid).roundabout || - node_based_graph.GetEdgeData(road.eid).circular) - return true; - } - return false; - }(); + return std::make_pair(intersection, merging_map); // 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 @@ -278,52 +298,27 @@ Intersection IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersect // the difference to all angles. Otherwise we subtract it. bool merged_first = false; // these result in an adjustment of all other angles - if (CanMerge(intersection_node, intersection, 0, intersection.size() - 1)) + if (CanMerge(intersection_node, intersection, intersection.size() - 1, 0)) { merged_first = true; // moving `a` to the left - const double correction_factor = (360 - intersection[intersection.size() - 1].angle) / 2; - for (std::size_t i = 1; i + 1 < intersection.size(); ++i) - intersection[i].angle += correction_factor; - + intersection[0] = merge(intersection.front(), intersection.back()); // FIXME if we have a left-sided country, we need to switch this off and enable it // below - intersection[0] = merge(intersection.front(), intersection.back()); - intersection[0].angle = 0; intersection.pop_back(); } else if (CanMerge(intersection_node, intersection, 0, 1)) { merged_first = true; - // moving `a` to the right - const double correction_factor = (intersection[1].angle) / 2; - for (std::size_t i = 2; i < intersection.size(); ++i) - intersection[i].angle -= correction_factor; - intersection[0] = merge(intersection[0], intersection[1]); - intersection[0].angle = 0; + intersection[0] = merge(intersection.front(), intersection[1]); intersection.erase(intersection.begin() + 1); } - if (merged_first && is_connected_to_roundabout) - { - /* - * We are merging a u-turn against the direction of a roundabout - * - * -----------> roundabout - * / \ - * out in - * - * These cases have to be disabled, even if they are not forbidden specifically by a - * relation - */ - intersection[0].entry_allowed = false; - } - // a merge including the first u-turn 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, index, getRight(index))) + if (CanMerge(intersection_node, intersection, getRight(index), index)) { intersection[getRight(index)] = merge(intersection[getRight(index)], intersection[index]); @@ -332,10 +327,7 @@ Intersection IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersect } } - std::sort(std::begin(intersection), - std::end(intersection), - std::mem_fn(&ConnectedRoad::compareByAngle)); - return intersection; + return std::make_pair(intersection, merging_map); } // OSM can have some very steep angles for joining roads. Considering the following intersection: @@ -358,8 +350,9 @@ Intersection IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersect // 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. -Intersection IntersectionNormalizer::AdjustForJoiningRoads(const NodeID node_at_intersection, - Intersection intersection) const +IntersectionShape +IntersectionNormalizer::AdjustBearingsForMergeAtDestination(const NodeID node_at_intersection, + IntersectionShape intersection) const { // nothing to do for dead ends if (intersection.size() <= 1) @@ -369,18 +362,18 @@ Intersection IntersectionNormalizer::AdjustForJoiningRoads(const NodeID node_at_ // 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 = 1; index < intersection.size(); ++index) + for (std::size_t index = 0; index < intersection.size(); ++index) { auto &road = intersection[index]; // only consider roads that are close - if (road.segment_length && *(road.segment_length) > PRUNING_DISTANCE) + 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(node_at_intersection, road.eid); + 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; @@ -401,18 +394,20 @@ Intersection IntersectionNormalizer::AdjustForJoiningRoads(const NodeID node_at_ continue; // the order does not matter - const auto get_offset = [](const ConnectedRoad &lhs, const ConnectedRoad &rhs) { - return 0.5 * angularDeviation(lhs.angle, rhs.angle); + 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 ConnectedRoad &road, - const ConnectedRoad &next_road_in_offset_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.angle, next_road_in_offset_direction.angle); + 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; @@ -426,28 +421,28 @@ Intersection IntersectionNormalizer::AdjustForJoiningRoads(const NodeID node_at_ const auto offset = get_offset(next_intersection_along_road[0], next_intersection_along_road[1]); - const auto corrected_offset = - get_corrected_offset(offset, road, intersection[(index + 1) % intersection.size()]); + 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.angle = adjustAngle(road.angle, corrected_offset); - road.bearing = adjustAngle(road.bearing, corrected_offset); + road.bearing = adjustAngle(road.bearing, -corrected_offset); } else if (CanMerge(node_at_next_intersection, next_intersection_along_road, - 0, - next_intersection_along_road.size() - 1)) + 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]); const auto corrected_offset = - get_corrected_offset(offset, road, intersection[index - 1]); + 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.angle = adjustAngle(road.angle, -corrected_offset); - road.bearing = adjustAngle(road.bearing, -corrected_offset); + road.bearing = adjustAngle(road.bearing, corrected_offset); } } return intersection; diff --git a/src/extractor/guidance/sliproad_handler.cpp b/src/extractor/guidance/sliproad_handler.cpp index 174b26720..f5cfac017 100644 --- a/src/extractor/guidance/sliproad_handler.cpp +++ b/src/extractor/guidance/sliproad_handler.cpp @@ -106,7 +106,7 @@ operator()(const NodeID, const EdgeID source_edge_id, Intersection intersection) // itself. If that is the case, the road is obviously not a sliproad. // a sliproad has to enter a road without choice - const auto couldBeSliproad = [](const Intersection &intersection) { + const auto couldBeSliproad = [](const IntersectionView &intersection) { if (intersection.size() != 3) return false; if ((intersection[1].entry_allowed && intersection[2].entry_allowed) || @@ -218,7 +218,7 @@ operator()(const NodeID, const EdgeID source_edge_id, Intersection intersection) std::find_if( target_intersection.begin() + 1, target_intersection.end(), - [this, &link_data](const ConnectedRoad &road) { + [this, &link_data](const IntersectionViewData &road) { const auto &road_edge_data = node_based_graph.GetEdgeData(road.eid); const auto same_name = diff --git a/src/extractor/guidance/turn_analysis.cpp b/src/extractor/guidance/turn_analysis.cpp index a1dfd34b1..d1575c93a 100644 --- a/src/extractor/guidance/turn_analysis.cpp +++ b/src/extractor/guidance/turn_analysis.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -74,36 +75,74 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph, { } -Intersection TurnAnalysis::assignTurnTypes(const NodeID from_nid, - const EdgeID via_eid, - Intersection intersection) const +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 normalised_shape + const auto intersection_view = intersection_generator.TransformIntersectionShapeIntoView( + node_prior_to_intersection, + entering_via_edge, + shape_result.normalised_intersection_shape, + shape_result.intersection_shape, + shape_result.merging_map); + + // 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 { // Roundabouts are a main priority. If there is a roundabout instruction present, we process the // turn as a roundabout - if (roundabout_handler.canProcess(from_nid, via_eid, intersection)) + + // the following lines create a partly invalid intersection object. We might want to refactor + // this at some point + Intersection intersection; + intersection.reserve(intersection_view.size()); + std::transform(intersection_view.begin(), + intersection_view.end(), + std::back_inserter(intersection), + [&](const IntersectionViewData &data) { + return ConnectedRoad(data, + {TurnType::Invalid, DirectionModifier::UTurn}, + INVALID_LANE_DATAID); + }); + if (roundabout_handler.canProcess(node_prior_to_intersection, entering_via_edge, intersection)) { - intersection = roundabout_handler(from_nid, via_eid, std::move(intersection)); + intersection = roundabout_handler( + node_prior_to_intersection, entering_via_edge, std::move(intersection)); } else { // set initial defaults for normal turns and modifier based on angle - intersection = setTurnTypes(from_nid, via_eid, std::move(intersection)); - if (motorway_handler.canProcess(from_nid, via_eid, intersection)) + intersection = + setTurnTypes(node_prior_to_intersection, entering_via_edge, std::move(intersection)); + if (motorway_handler.canProcess( + node_prior_to_intersection, entering_via_edge, intersection)) { - intersection = motorway_handler(from_nid, via_eid, std::move(intersection)); + intersection = motorway_handler( + node_prior_to_intersection, entering_via_edge, std::move(intersection)); } else { - BOOST_ASSERT(turn_handler.canProcess(from_nid, via_eid, intersection)); - intersection = turn_handler(from_nid, via_eid, std::move(intersection)); + BOOST_ASSERT(turn_handler.canProcess( + node_prior_to_intersection, entering_via_edge, intersection)); + intersection = turn_handler( + node_prior_to_intersection, entering_via_edge, std::move(intersection)); } } // Handle sliproads - if (sliproad_handler.canProcess(from_nid, via_eid, intersection)) - intersection = sliproad_handler(from_nid, via_eid, std::move(intersection)); + if (sliproad_handler.canProcess(node_prior_to_intersection, entering_via_edge, intersection)) + intersection = sliproad_handler( + node_prior_to_intersection, entering_via_edge, std::move(intersection)); // Turn On Ramps Into Off Ramps, if we come from a motorway-like road - if (node_based_graph.GetEdgeData(via_eid).road_classification.IsMotorwayClass()) + if (node_based_graph.GetEdgeData(entering_via_edge).road_classification.IsMotorwayClass()) { std::for_each(intersection.begin(), intersection.end(), [](ConnectedRoad &road) { if (road.instruction.type == TurnType::OnRamp) @@ -113,34 +152,24 @@ Intersection TurnAnalysis::assignTurnTypes(const NodeID from_nid, return intersection; } -std::vector -TurnAnalysis::transformIntersectionIntoTurns(const Intersection &intersection) const +TurnAnalysis::ShapeResult +TurnAnalysis::ComputeIntersectionShapes(const NodeID node_at_center_of_intersection) const { - std::vector turns; - for (auto road : intersection) - if (road.entry_allowed) - turns.emplace_back(road); + ShapeResult intersection_shape; + intersection_shape.intersection_shape = + intersection_generator.ComputeIntersectionShape(node_at_center_of_intersection); - return turns; -} + std::tie(intersection_shape.normalised_intersection_shape, intersection_shape.merging_map) = + intersection_normalizer(node_at_center_of_intersection, + intersection_shape.intersection_shape); -Intersection TurnAnalysis::operator()(const NodeID from_nid, const EdgeID via_eid) const -{ - return PostProcess(from_nid, via_eid, intersection_generator(from_nid, via_eid)); -} - -Intersection TurnAnalysis::PostProcess(const NodeID from_node, - const EdgeID via_eid, - Intersection intersection) const -{ - const auto node_at_intersection = node_based_graph.GetTarget(via_eid); - return assignTurnTypes( - from_node, via_eid, intersection_normalizer(node_at_intersection, std::move(intersection))); + return intersection_shape; } // Sets basic turn types as fallback for otherwise unhandled turns -Intersection -TurnAnalysis::setTurnTypes(const NodeID from_nid, const EdgeID, Intersection intersection) const +Intersection TurnAnalysis::setTurnTypes(const NodeID node_prior_to_intersection, + const EdgeID, + Intersection intersection) const { for (auto &road : intersection) { @@ -151,8 +180,8 @@ TurnAnalysis::setTurnTypes(const NodeID from_nid, const EdgeID, Intersection int const NodeID to_nid = node_based_graph.GetTarget(onto_edge); road.instruction = {TurnType::Turn, - (from_nid == to_nid) ? DirectionModifier::UTurn - : getTurnDirection(road.angle)}; + (node_prior_to_intersection == to_nid) ? DirectionModifier::UTurn + : getTurnDirection(road.angle)}; } return intersection; } diff --git a/src/extractor/guidance/turn_discovery.cpp b/src/extractor/guidance/turn_discovery.cpp index 1951fc2d7..329ec582d 100644 --- a/src/extractor/guidance/turn_discovery.cpp +++ b/src/extractor/guidance/turn_discovery.cpp @@ -25,7 +25,7 @@ bool findPreviousIntersection(const NodeID node_v, // output parameters NodeID &result_node, EdgeID &result_via_edge, - Intersection &result_intersection) + IntersectionView &result_intersection) { /* We need to find the intersection that is located prior to via_edge. @@ -55,6 +55,7 @@ bool findPreviousIntersection(const NodeID node_v, // (looking at the reverse direction). const auto node_w = node_based_graph.GetTarget(via_edge); const auto u_turn_at_node_w = intersection[0].eid; + // make sure the ID is actually valid BOOST_ASSERT(node_based_graph.BeginEdges(node_w) <= u_turn_at_node_w && u_turn_at_node_w <= node_based_graph.EndEdges(node_w)); @@ -65,7 +66,6 @@ bool findPreviousIntersection(const NodeID node_v, const auto node_v_reverse_intersection = intersection_generator.GetConnectedRoads(node_w, u_turn_at_node_w, USE_LOW_PRECISION_MODE); - // Continue along the straightmost turn. If there is no straight turn, we cannot find a valid // previous intersection. const auto straightmost_at_v_in_reverse = @@ -102,7 +102,7 @@ bool findPreviousIntersection(const NodeID node_v, result_intersection.end() != std::find_if(result_intersection.begin(), result_intersection.end(), - [via_edge](const ConnectedRoad &road) { return road.eid == via_edge; }); + [via_edge](const IntersectionViewData &road) { return road.eid == via_edge; }); if (!check_via_edge) { diff --git a/src/extractor/guidance/turn_lane_handler.cpp b/src/extractor/guidance/turn_lane_handler.cpp index ac9a82932..1afa4b8d3 100644 --- a/src/extractor/guidance/turn_lane_handler.cpp +++ b/src/extractor/guidance/turn_lane_handler.cpp @@ -186,6 +186,7 @@ TurnLaneScenario TurnLaneHandler::deduceScenario(const NodeID at, // Due to sliproads, we might need access to the previous intersection at this point already; previous_node = SPECIAL_NODEID; previous_via_edge = SPECIAL_EDGEID; + IntersectionView previous_intersection_view; if (findPreviousIntersection(at, via_edge, intersection, @@ -193,11 +194,11 @@ TurnLaneScenario TurnLaneHandler::deduceScenario(const NodeID at, node_based_graph, previous_node, previous_via_edge, - previous_intersection)) + previous_intersection_view)) { extractLaneData(previous_via_edge, previous_description_id, previous_lane_data); - previous_intersection = turn_analysis.assignTurnTypes( - previous_node, previous_via_edge, std::move(previous_intersection)); + previous_intersection = turn_analysis.AssignTurnTypes( + previous_node, previous_via_edge, previous_intersection_view); for (std::size_t road_index = 0; road_index < previous_intersection.size(); ++road_index) { const auto &road = previous_intersection[road_index]; @@ -542,7 +543,7 @@ std::pair TurnLaneHandler::partitionLaneData( // find out about the next intersection. To check for valid matches, we also need the turn // types. We can skip merging/angle adjustments, though - const auto next_intersection = turn_analysis.assignTurnTypes( + const auto next_intersection = turn_analysis.AssignTurnTypes( at, straightmost->eid, turn_analysis.GetIntersectionGenerator()(at, straightmost->eid)); // check where we can match turn lanes diff --git a/unit_tests/util/io.cpp b/unit_tests/util/io.cpp index 6073970bd..d220b5d7f 100644 --- a/unit_tests/util/io.cpp +++ b/unit_tests/util/io.cpp @@ -117,7 +117,8 @@ BOOST_AUTO_TEST_CASE(io_read_lines) auto startiter = infile.GetLineIteratorBegin(); auto enditer = infile.GetLineIteratorEnd(); std::vector resultlines; - while (startiter != enditer) { + while (startiter != enditer) + { resultlines.push_back(*startiter); ++startiter; }