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;
}
Ejemplo n.º 2
0
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);
}
Ejemplo n.º 3
0
void IntersectionHandler::assignFork(const EdgeID via_edge,
                                     ConnectedRoad &left,
                                     ConnectedRoad &center,
                                     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)};
    }
}
Ejemplo n.º 4
0
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;
}
Ejemplo n.º 5
0
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;
}
Ejemplo n.º 8
0
// "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;
}
Ejemplo n.º 10
0
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)};
    }
}
Ejemplo n.º 11
0
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;
}