Intersection IntersectionGenerator::GetActualNextIntersection(const NodeID starting_node, const EdgeID via_edge, NodeID *resulting_from_node = nullptr, EdgeID *resulting_via_edge = nullptr) const { // This function skips over traffic lights/graph compression issues and similar to find the next // actual intersection Intersection result = GetConnectedRoads(starting_node, via_edge); // Skip over stuff that has not been compressed due to barriers/parallel edges NodeID node_at_intersection = starting_node; EdgeID incoming_edge = via_edge; // to prevent endless loops const auto termination_node = node_based_graph.GetTarget(via_edge); // using a maximum lookahead, we make sure not to end up in some form of loop std::unordered_set<NodeID> visited_nodes; while (visited_nodes.count(node_at_intersection) == 0 && (result.size() == 2 && node_based_graph.GetEdgeData(via_edge).IsCompatibleTo( node_based_graph.GetEdgeData(result[1].eid)))) { visited_nodes.insert(node_at_intersection); node_at_intersection = node_based_graph.GetTarget(incoming_edge); incoming_edge = result[1].eid; result = GetConnectedRoads(node_at_intersection, incoming_edge); // When looping back to the original node, we obviously are in a loop. Stop there. if (termination_node == node_based_graph.GetTarget(incoming_edge)) break; } // return output if requested if (resulting_from_node) *resulting_from_node = node_at_intersection; if (resulting_via_edge) *resulting_via_edge = incoming_edge; return result; }
/* * 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; }
std::pair<util::guidance::EntryClass, util::guidance::BearingClass> classifyIntersection(Intersection intersection) { if (intersection.empty()) return {}; std::sort(intersection.begin(), intersection.end(), [](const ConnectedRoad &left, const ConnectedRoad &right) { return left.bearing < right.bearing; }); util::guidance::EntryClass entry_class; util::guidance::BearingClass bearing_class; const bool canBeDiscretized = [&]() { if (intersection.size() <= 1) return true; DiscreteBearing last_discrete_bearing = util::guidance::BearingClass::getDiscreteBearing( std::round(intersection.back().bearing)); for (const auto road : intersection) { const DiscreteBearing discrete_bearing = util::guidance::BearingClass::getDiscreteBearing(std::round(road.bearing)); if (discrete_bearing == last_discrete_bearing) return false; last_discrete_bearing = discrete_bearing; } return true; }(); // finally transfer data to the entry/bearing classes std::size_t number = 0; if (canBeDiscretized) { if (util::guidance::BearingClass::getDiscreteBearing(intersection.back().bearing) < util::guidance::BearingClass::getDiscreteBearing(intersection.front().bearing)) { intersection.insert(intersection.begin(), intersection.back()); intersection.pop_back(); } for (const auto &road : intersection) { if (road.entry_allowed) entry_class.activate(number); auto discrete_bearing_class = util::guidance::BearingClass::getDiscreteBearing(std::round(road.bearing)); bearing_class.add(std::round(discrete_bearing_class * util::guidance::BearingClass::discrete_step_size)); ++number; } } else { for (const auto &road : intersection) { if (road.entry_allowed) entry_class.activate(number); bearing_class.add(std::round(road.bearing)); ++number; } } return std::make_pair(entry_class, bearing_class); }
// a // | // | // v // For an intersection from_node --via_edi--> turn_node ----> c // ^ // | // | // b // This functions returns _all_ turns as if the graph was undirected. // 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 { 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(); 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); 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); auto angle = 0.; double bearing = 0.; // 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. const auto first_coordinate = coordinate_extractor.GetCoordinateAlongRoad( from_node, via_eid, INVERT, turn_node, intersection_lanes); 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; BOOST_ASSERT(angle >= 0. && angle < std::numeric_limits<double>::epsilon()); } else { // the default distance we lookahead on a road. This distance prevents small mapping // errors to impact the turn angles. const auto third_coordinate = coordinate_extractor.GetCoordinateAlongRoad( turn_node, onto_edge, !INVERT, to_node, intersection_lanes); 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<double>::epsilon()) has_uturn_edge = true; } intersection.push_back( ConnectedRoad(TurnOperation{onto_edge, angle, bearing, {TurnType::Invalid, DirectionModifier::UTurn}, INVALID_LANE_DATAID}, turn_is_valid)); } // 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 auto first_coordinate = coordinate_extractor.GetCoordinateAlongRoad( from_node, via_eid, INVERT, turn_node, node_based_graph.GetEdgeData(via_eid).road_classification.GetNumberOfLanes()); 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}); } 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<double>::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<double>::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; }