bool isEndOfRoad(const ConnectedRoad &, const ConnectedRoad &possible_right_turn, const ConnectedRoad &possible_left_turn) { return angularDeviation(possible_right_turn.turn.angle, 90) < NARROW_TURN_ANGLE && angularDeviation(possible_left_turn.turn.angle, 270) < NARROW_TURN_ANGLE && angularDeviation(possible_right_turn.turn.angle, possible_left_turn.turn.angle) > 2 * NARROW_TURN_ANGLE; }
std::size_t BearingClass::findMatchingBearing(const double bearing) const { BOOST_ASSERT(!available_bearings.empty()); // the small size of the intersections allows a linear compare auto discrete_bearing = static_cast<DiscreteBearing>(bearing); auto max_element = std::max_element(available_bearings.begin(), available_bearings.end(), [&](const DiscreteBearing first, const DiscreteBearing second) { return angularDeviation(first, discrete_bearing) > angularDeviation(second, discrete_bearing); }); return std::distance(available_bearings.begin(), max_element); }
void IntersectionHandler::assignFork(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad ¢er, ConnectedRoad &right) const { // TODO handle low priority road classes in a reasonable way if (left.entry_allowed && center.entry_allowed && right.entry_allowed) { left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; if (angularDeviation(center.turn.angle, 180) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION) { const auto &in_data = node_based_graph.GetEdgeData(via_edge); const auto &out_data = node_based_graph.GetEdgeData(center.turn.eid); if (detail::requiresAnnouncement(in_data, out_data)) { center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight}; } else { center.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; } } else { center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight}; } right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; } else if (left.entry_allowed) { if (right.entry_allowed) assignFork(via_edge, left, right); else if (center.entry_allowed) assignFork(via_edge, left, center); else left.turn.instruction = {findBasicTurnType(via_edge, left), getTurnDirection(left.turn.angle)}; } else if (right.entry_allowed) { if (center.entry_allowed) assignFork(via_edge, center, right); else right.turn.instruction = {findBasicTurnType(via_edge, right), getTurnDirection(right.turn.angle)}; } else { if (center.entry_allowed) center.turn.instruction = {findBasicTurnType(via_edge, center), getTurnDirection(center.turn.angle)}; } }
bool IntersectionHandler::isThroughStreet(const std::size_t index, const Intersection &intersection) const { if (node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id == EMPTY_NAMEID) return false; for (const auto &road : intersection) { // a through street cannot start at our own position if (road.turn.angle < std::numeric_limits<double>::epsilon()) continue; if (angularDeviation(road.turn.angle, intersection[index].turn.angle) > (STRAIGHT_ANGLE - NARROW_TURN_ANGLE) && node_based_graph.GetEdgeData(road.turn.eid).name_id == node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id) return true; } return false; }
void ConnectedRoad::mirror() { const constexpr DirectionModifier::Enum mirrored_modifiers[] = {DirectionModifier::UTurn, DirectionModifier::SharpLeft, DirectionModifier::Left, DirectionModifier::SlightLeft, DirectionModifier::Straight, DirectionModifier::SlightRight, DirectionModifier::Right, DirectionModifier::SharpRight}; static_assert(sizeof(mirrored_modifiers) / sizeof(DirectionModifier::Enum) == DirectionModifier::MaxDirectionModifier, "The list of mirrored modifiers needs to match the available modifiers in size."); if (angularDeviation(angle, 0) > std::numeric_limits<double>::epsilon()) { angle = 360 - angle; instruction.direction_modifier = mirrored_modifiers[instruction.direction_modifier]; } }
inline bool isThroughStreet(const std::size_t index, const IntersectionType &intersection, const util::NodeBasedDynamicGraph &node_based_graph, const EdgeBasedNodeDataContainer &node_data_container, const util::NameTable &name_table, const SuffixTable &street_name_suffix_table) { const auto &data_at_index = node_data_container.GetAnnotation( node_based_graph.GetEdgeData(intersection[index].eid).annotation_data); if (data_at_index.name_id == EMPTY_NAMEID) return false; // a through street cannot start at our own position -> index 1 for (std::size_t road_index = 1; road_index < intersection.size(); ++road_index) { if (road_index == index) continue; const auto &road = intersection[road_index]; const auto &road_data = node_data_container.GetAnnotation( node_based_graph.GetEdgeData(road.eid).annotation_data); // roads have a near straight angle (180 degree) const bool is_nearly_straight = angularDeviation(road.angle, intersection[index].angle) > (STRAIGHT_ANGLE - FUZZY_ANGLE_DIFFERENCE); const bool have_same_name = HaveIdenticalNames( data_at_index.name_id, road_data.name_id, name_table, street_name_suffix_table); const bool have_same_category = node_based_graph.GetEdgeData(intersection[index].eid).flags.road_classification == node_based_graph.GetEdgeData(road.eid).flags.road_classification; if (is_nearly_straight && have_same_name && have_same_category) return true; } return false; }
boost::optional<EdgeID> SelectRoadByNameOnlyChoiceAndStraightness:: operator()(const NodeID /*nid*/, const EdgeID /*via_edge_id*/, const IntersectionView &intersection, const util::NodeBasedDynamicGraph &node_based_graph, const EdgeBasedNodeDataContainer &node_data_container) const { BOOST_ASSERT(!intersection.empty()); const auto comparator = [&](const IntersectionViewData &lhs, const IntersectionViewData &rhs) { // the score of an elemnt results in an ranking preferring valid entries, if required over // invalid requested name_ids over non-requested narrow deviations over non-narrow const auto score = [&](const IntersectionViewData &road) { double result_score = 0; // since angular deviation is limited by 0-180, we add 360 for invalid if (requires_entry && !road.entry_allowed) result_score += 360.; // 180 for undesired name-ids if (desired_name_id != node_data_container .GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data) .name_id) result_score += 180; return result_score + angularDeviation(road.angle, STRAIGHT_ANGLE); }; return score(lhs) < score(rhs); }; const auto min_element = std::min_element(std::next(std::begin(intersection)), std::end(intersection), comparator); if (min_element == intersection.end() || (requires_entry && !min_element->entry_allowed)) return {}; else return (*min_element).eid; }
// "Sliproads" occur when we've got a link between two roads (MOTORWAY_LINK, etc), but // the two roads are *also* directly connected shortly afterwards. // In these cases, we tag the turn-type as "sliproad", and then in post-processing // we emit a "turn", instead of "take the ramp"+"merge" Intersection TurnAnalysis::handleSliproads(const EdgeID source_edge_id, Intersection intersection) const { auto intersection_node_id = node_based_graph.GetTarget(source_edge_id); const auto linkTest = [this](const ConnectedRoad &road) { return // isLinkClass( // node_based_graph.GetEdgeData(road.turn.eid).road_classification.road_class) && !node_based_graph.GetEdgeData(road.turn.eid).roundabout && road.entry_allowed && angularDeviation(road.turn.angle, STRAIGHT_ANGLE) <= 2 * NARROW_TURN_ANGLE; }; bool hasNarrow = std::find_if(intersection.begin(), intersection.end(), linkTest) != intersection.end(); if (!hasNarrow) return intersection; const auto source_edge_data = node_based_graph.GetEdgeData(source_edge_id); // Find the continuation of the intersection we're on auto next_road = std::find_if( intersection.begin(), intersection.end(), [this, source_edge_data](const ConnectedRoad &road) { const auto road_edge_data = node_based_graph.GetEdgeData(road.turn.eid); // Test to see if the source edge and the one we're looking at are the same road return road_edge_data.road_classification.road_class == source_edge_data.road_classification.road_class && road_edge_data.name_id != EMPTY_NAMEID && road_edge_data.name_id == source_edge_data.name_id && road.entry_allowed && angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE; }); const bool hasNext = next_road != intersection.end(); if (!hasNext) { return intersection; } // Threshold check, if the intersection is too far away, don't bother continuing const auto &next_road_data = node_based_graph.GetEdgeData(next_road->turn.eid); if (next_road_data.distance > MAX_SLIPROAD_THRESHOLD) { return intersection; } const auto next_road_next_intersection = intersection_generator(intersection_node_id, next_road->turn.eid); const auto next_intersection_node = node_based_graph.GetTarget(next_road->turn.eid); std::unordered_set<NameID> target_road_names; for (const auto &road : next_road_next_intersection) { const auto &target_data = node_based_graph.GetEdgeData(road.turn.eid); target_road_names.insert(target_data.name_id); } for (auto &road : intersection) { if (linkTest(road)) { auto target_intersection = intersection_generator(intersection_node_id, road.turn.eid); for (const auto &candidate_road : target_intersection) { const auto &candidate_data = node_based_graph.GetEdgeData(candidate_road.turn.eid); if (target_road_names.count(candidate_data.name_id) > 0 && node_based_graph.GetTarget(candidate_road.turn.eid) == next_intersection_node) { road.turn.instruction.type = TurnType::Sliproad; break; } } } } if (next_road->turn.instruction.type == TurnType::Fork) { const auto &next_data = node_based_graph.GetEdgeData(next_road->turn.eid); if (next_data.name_id == source_edge_data.name_id) { if (angularDeviation(next_road->turn.angle, STRAIGHT_ANGLE) < 5) next_road->turn.instruction.type = TurnType::Suppressed; else next_road->turn.instruction.type = TurnType::Continue; next_road->turn.instruction.direction_modifier = getTurnDirection(next_road->turn.angle); } else if (next_data.name_id != EMPTY_NAMEID) { next_road->turn.instruction.type = TurnType::NewName; next_road->turn.instruction.direction_modifier = getTurnDirection(next_road->turn.angle); } else { next_road->turn.instruction.type = TurnType::Suppressed; } } return intersection; }
/* * Segregated Roads often merge onto a single intersection. * While technically representing different roads, they are * often looked at as a single road. * Due to the merging, turn Angles seem off, wenn we compute them from the * initial positions. * * b<b<b<b(1)<b<b<b * aaaaa-b * b>b>b>b(2)>b>b>b * * Would be seen as a slight turn going fro a to (2). A Sharp turn going from * (1) to (2). * * In cases like these, we megre this segregated roads into a single road to * end up with a case like: * * aaaaa-bbbbbb * * for the turn representation. * Anything containing the first u-turn in a merge affects all other angles * and is handled separately from all others. */ Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersection) const { const auto getRight = [&](std::size_t index) { return (index + intersection.size() - 1) % intersection.size(); }; const auto mergable = [&](std::size_t first, std::size_t second) -> bool { const auto &first_data = node_based_graph.GetEdgeData(intersection[first].turn.eid); const auto &second_data = node_based_graph.GetEdgeData(intersection[second].turn.eid); return first_data.name_id != INVALID_NAME_ID && first_data.name_id == second_data.name_id && !first_data.roundabout && !second_data.roundabout && first_data.travel_mode == second_data.travel_mode && first_data.road_classification == second_data.road_classification && // compatible threshold angularDeviation(intersection[first].turn.angle, intersection[second].turn.angle) < 60 && first_data.reversed != second_data.reversed; }; const auto merge = [](const ConnectedRoad &first, const ConnectedRoad &second) -> ConnectedRoad { if (!first.entry_allowed) { ConnectedRoad result = second; result.turn.angle = (first.turn.angle + second.turn.angle) / 2; if (first.turn.angle - second.turn.angle > 180) result.turn.angle += 180; if (result.turn.angle > 360) result.turn.angle -= 360; return result; } else { BOOST_ASSERT(!second.entry_allowed); ConnectedRoad result = first; result.turn.angle = (first.turn.angle + second.turn.angle) / 2; if (first.turn.angle - second.turn.angle > 180) result.turn.angle += 180; if (result.turn.angle > 360) result.turn.angle -= 360; return result; } }; if (intersection.size() <= 1) return intersection; const bool is_connected_to_roundabout = [this, &intersection]() { for (const auto &road : intersection) { if (node_based_graph.GetEdgeData(road.turn.eid).roundabout) return true; } return false; }(); // check for merges including the basic u-turn // these result in an adjustment of all other angles if (mergable(0, intersection.size() - 1)) { const double correction_factor = (360 - intersection[intersection.size() - 1].turn.angle) / 2; for (std::size_t i = 1; i + 1 < intersection.size(); ++i) intersection[i].turn.angle += correction_factor; // FIXME if we have a left-sided country, we need to switch this off and enable it below intersection[0] = merge(intersection.front(), intersection.back()); intersection[0].turn.angle = 0; if (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; } intersection.pop_back(); } else if (mergable(0, 1)) { const double correction_factor = (intersection[1].turn.angle) / 2; for (std::size_t i = 2; i < intersection.size(); ++i) intersection[i].turn.angle += correction_factor; intersection[0] = merge(intersection[0], intersection[1]); intersection[0].turn.angle = 0; intersection.erase(intersection.begin() + 1); } // a merge including the first u-turn requres an adjustment of the turn angles // therefore these are handled prior to this step for (std::size_t index = 2; index < intersection.size(); ++index) { if (mergable(index, getRight(index))) { intersection[getRight(index)] = merge(intersection[getRight(index)], intersection[index]); intersection.erase(intersection.begin() + index); --index; } } const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) { return first.turn.angle < second.turn.angle; }; std::sort(std::begin(intersection), std::end(intersection), ByAngle); return intersection; }
TurnInstruction IntersectionHandler::getInstructionForObvious(const std::size_t num_roads, const EdgeID via_edge, const bool through_street, const ConnectedRoad &road) const { const auto type = findBasicTurnType(via_edge, road); // handle travel modes: const auto in_mode = node_based_graph.GetEdgeData(via_edge).travel_mode; const auto out_mode = node_based_graph.GetEdgeData(road.turn.eid).travel_mode; if (type == TurnType::OnRamp) { return {TurnType::OnRamp, getTurnDirection(road.turn.angle)}; } if (angularDeviation(road.turn.angle, 0) < 0.01) { return {TurnType::Turn, DirectionModifier::UTurn}; } if (type == TurnType::Turn) { const auto &in_data = node_based_graph.GetEdgeData(via_edge); const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid); if (in_data.name_id != out_data.name_id && requiresNameAnnounced(name_table.GetNameForID(in_data.name_id), name_table.GetNameForID(out_data.name_id), street_name_suffix_table)) { // obvious turn onto a through street is a merge if (through_street) { return {TurnType::Merge, road.turn.angle > STRAIGHT_ANGLE ? DirectionModifier::SlightRight : DirectionModifier::SlightLeft}; } else { return {TurnType::NewName, getTurnDirection(road.turn.angle)}; } } else { if (in_mode == out_mode) return {TurnType::Suppressed, getTurnDirection(road.turn.angle)}; else return {TurnType::Notification, getTurnDirection(road.turn.angle)}; } } BOOST_ASSERT(type == TurnType::Continue); if (in_mode != out_mode) { return {TurnType::Notification, getTurnDirection(road.turn.angle)}; } if (num_roads > 2) { return {TurnType::Suppressed, getTurnDirection(road.turn.angle)}; } else { return {TurnType::NoTurn, getTurnDirection(road.turn.angle)}; } }
void IntersectionHandler::assignFork(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const { const auto &in_data = node_based_graph.GetEdgeData(via_edge); const bool low_priority_left = isLowPriorityRoadClass( node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class); const bool low_priority_right = isLowPriorityRoadClass( node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class); if ((angularDeviation(left.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION && angularDeviation(right.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)) { // left side is actually straight const auto &out_data = node_based_graph.GetEdgeData(left.turn.eid); if (detail::requiresAnnouncement(in_data, out_data)) { if (low_priority_right && !low_priority_left) { left.turn.instruction = getInstructionForObvious(3, via_edge, false, left); right.turn.instruction = {findBasicTurnType(via_edge, right), DirectionModifier::SlightRight}; } else { if (low_priority_left && !low_priority_right) { left.turn.instruction = {findBasicTurnType(via_edge, left), DirectionModifier::SlightLeft}; right.turn.instruction = {findBasicTurnType(via_edge, right), DirectionModifier::SlightRight}; } else { left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; } } } else { left.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; right.turn.instruction = {findBasicTurnType(via_edge, right), DirectionModifier::SlightRight}; } } else if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION && angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) { // right side is actually straight const auto &out_data = node_based_graph.GetEdgeData(right.turn.eid); if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION && angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) { if (detail::requiresAnnouncement(in_data, out_data)) { if (low_priority_left && !low_priority_right) { left.turn.instruction = {findBasicTurnType(via_edge, left), DirectionModifier::SlightLeft}; right.turn.instruction = getInstructionForObvious(3, via_edge, false, right); } else { if (low_priority_right && !low_priority_left) { left.turn.instruction = {findBasicTurnType(via_edge, left), DirectionModifier::SlightLeft}; right.turn.instruction = {findBasicTurnType(via_edge, right), DirectionModifier::SlightRight}; } else { right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; } } } else { right.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight}; left.turn.instruction = {findBasicTurnType(via_edge, left), DirectionModifier::SlightLeft}; } } } // left side of fork if (low_priority_right && !low_priority_left) left.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft}; else { if (low_priority_left && !low_priority_right) left.turn.instruction = {TurnType::Turn, DirectionModifier::SlightLeft}; else left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft}; } // right side of fork if (low_priority_left && !low_priority_right) right.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft}; else { if (low_priority_right && !low_priority_left) right.turn.instruction = {TurnType::Turn, DirectionModifier::SlightRight}; else right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight}; } }
bool findPreviousIntersection(const NodeID node_v, const EdgeID via_edge, const Intersection intersection, const TurnAnalysis &turn_analysis, const util::NodeBasedDynamicGraph &node_based_graph, // output parameters NodeID &result_node, EdgeID &result_via_edge, Intersection &result_intersection) { /* We need to find the intersection that is located prior to via_edge. * * NODE_U -> PREVIOUS_ID -> NODE_V -> VIA_EDGE -> NODE_W:INTERSECTION * NODE_U? <- STRAIGHTMOST <- NODE_V <- UTURN * NODE_U? -> UTURN == PREVIOUSE_ID? -> NODE_V -> VIA_EDGE * * To do so, we first get the intersection atNODE and find the straightmost turn from that * node. This will result in NODE_X. The uturn in the intersection at NODE_X should be * PREVIOUS_ID. To verify that find, we check the intersection using our PREVIOUS_ID candidate * to check the intersection at NODE for via_edge */ const constexpr double COMBINE_DISTANCE_CUTOFF = 30; // we check if via-edge is too short. In this case the previous turn cannot influence the turn // at via_edge and the intersection at NODE_W if (node_based_graph.GetEdgeData(via_edge).distance > COMBINE_DISTANCE_CUTOFF) return false; // Node -> Via_Edge -> Intersection[0 == UTURN] -> reverse_of(via_edge) -> Intersection at node // (looking at the reverse direction). const auto node_w = node_based_graph.GetTarget(via_edge); const auto u_turn_at_node_w = intersection[0].turn.eid; const auto node_v_reverse_intersection = turn_analysis.getIntersection(node_w, u_turn_at_node_w); // 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 = findClosestTurn(node_v_reverse_intersection, STRAIGHT_ANGLE); if (angularDeviation(straightmost_at_v_in_reverse->turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) return false; const auto node_u = node_based_graph.GetTarget(straightmost_at_v_in_reverse->turn.eid); const auto node_u_reverse_intersection = turn_analysis.getIntersection(node_v, straightmost_at_v_in_reverse->turn.eid); // now check that the u-turn at the given intersection connects to via-edge // The u-turn at the now found intersection should, hopefully, represent the previous edge. result_node = node_u; result_via_edge = node_u_reverse_intersection[0].turn.eid; // if the edge is not traversable, we obviously don't have a previous intersection or couldn't // find it. if (node_based_graph.GetEdgeData(result_via_edge).reversed) return false; result_intersection = turn_analysis.getIntersection(node_u, result_via_edge); const auto check_via_edge = findClosestTurn(result_intersection, STRAIGHT_ANGLE)->turn.eid; if (check_via_edge != via_edge) return false; result_intersection = turn_analysis.assignTurnTypes(node_u, result_via_edge, std::move(result_intersection)); return true; }
boost::optional<EdgeID> SelectStraightmostRoadByNameAndOnlyChoice:: operator()(const NodeID /*nid*/, const EdgeID /*via_edge_id*/, const IntersectionView &intersection, const util::NodeBasedDynamicGraph &node_based_graph, const EdgeBasedNodeDataContainer &node_data_container) const { BOOST_ASSERT(!intersection.empty()); if (intersection.size() == 1) return {}; const auto comparator = [&](const IntersectionViewData &lhs, const IntersectionViewData &rhs) { // the score of an elemnt results in an ranking preferring valid entries, if required over // invalid requested name_ids over non-requested narrow deviations over non-narrow const auto score = [&](const IntersectionViewData &road) { double result_score = 0; // since angular deviation is limited by 0-180, we add 360 for invalid if (requires_entry && !road.entry_allowed) result_score += 360.; // 180 for undesired name-ids if (desired_name_id != node_data_container .GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data) .name_id) result_score += 180; return result_score + angularDeviation(road.angle, STRAIGHT_ANGLE); }; return score(lhs) < score(rhs); }; const auto count_desired_name = std::count_if(std::begin(intersection), std::end(intersection), [&](const auto &road) { return node_data_container .GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data) .name_id == desired_name_id; }); if (count_desired_name > 2) return {}; const auto min_element = std::min_element(std::next(std::begin(intersection)), std::end(intersection), comparator); const auto is_valid_choice = !requires_entry || min_element->entry_allowed; if (!is_valid_choice) return {}; // only road exiting or continuing in the same general direction const auto has_valid_angle = ((intersection.size() == 2 || intersection.findClosestTurn(STRAIGHT_ANGLE) == min_element) && angularDeviation(min_element->angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE) && angularDeviation(initial_bearing, min_element->perceived_bearing) < NARROW_TURN_ANGLE; if (has_valid_angle) return (*min_element).eid; // in some cases, stronger turns are appropriate. We allow turns of just a bit over 90 degrees, // if it's not a end of road situation. These angles come into play where roads split into dual // carriage-ways. // // e - - f // a - - - - b // c - - d // | // g // // is technically // // // a - - - - b (ce) - - (fg) // | // g const auto is_only_choice_with_same_name = count_desired_name <= 2 && // <= in case we come from a bridge, otherwise we have a u-turn // and the outgoing edge node_data_container .GetAnnotation(node_based_graph.GetEdgeData(min_element->eid).annotation_data) .name_id == desired_name_id && angularDeviation(min_element->angle, STRAIGHT_ANGLE) < 100; // don't do crazy turns // do not allow major turns in the road, if other similar turns are present // e.g.a turn at the end of the road: // // a - - a - - a - b - b // | // a - - a // | // c // // Such a turn can never be part of a merge // We check if there is a similar turn to the other side. If such a turn exists, we consider the // continuation of the road not possible if (stop_on_ambiguous_turns && util::angularDeviation(STRAIGHT_ANGLE, min_element->angle) > GROUP_ANGLE) { auto opposite = intersection.findClosestTurn(util::bearing::reverse(min_element->angle)); auto opposite_deviation = util::angularDeviation(min_element->angle, opposite->angle); // d - - - - c - - - -e // | // | // a - - - - b // from b-c onto min_element d with opposite side e if (opposite_deviation > (180 - FUZZY_ANGLE_DIFFERENCE)) return {}; // e // | // a - - - - b - - - - -d // doing a left turn while straight is a choice auto const best = intersection.findClosestTurn(STRAIGHT_ANGLE); if (util::angularDeviation(best->angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE) return {}; } return is_only_choice_with_same_name ? boost::optional<EdgeID>(min_element->eid) : boost::none; }