Esempio n. 1
0
inline std::uint8_t getLaneCountAtIntersection(const NodeID intersection_node,
                                               const util::NodeBasedDynamicGraph &node_based_graph)
{
    std::uint8_t lanes = 0;
    for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(intersection_node))
        lanes = std::max(
            lanes, node_based_graph.GetEdgeData(onto_edge).road_classification.GetNumberOfLanes());
    return lanes;
}
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;
}
Esempio n. 3
0
std::uint8_t
Intersection::getHighestConnectedLaneCount(const util::NodeBasedDynamicGraph &graph) const
{
    BOOST_ASSERT(valid()); // non empty()

    const std::function<std::uint8_t(const ConnectedRoad &)> to_lane_count =
        [&](const ConnectedRoad &road) {
            return graph.GetEdgeData(road.eid).road_classification.GetNumberOfLanes();
        };

    std::uint8_t max_lanes = 0;
    const auto extract_maximal_value = [&max_lanes](std::uint8_t value) {
        max_lanes = std::max(max_lanes, value);
        return false;
    };

    const auto view = *this | boost::adaptors::transformed(to_lane_count);
    boost::range::find_if(view, extract_maximal_value);
    return max_lanes;
}
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;
}
inline auto makeExtractLanesForRoad(const util::NodeBasedDynamicGraph &node_based_graph)
{
    return [&node_based_graph](const auto &road) {
        return node_based_graph.GetEdgeData(road.eid).road_classification.GetNumberOfLanes();
    };
}
void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
                               const std::unordered_set<NodeID> &traffic_lights,
                               RestrictionMap &restriction_map,
                               util::NodeBasedDynamicGraph &graph,
                               CompressedEdgeContainer &geometry_compressor)
{
    const unsigned original_number_of_nodes = graph.GetNumberOfNodes();
    const unsigned original_number_of_edges = graph.GetNumberOfEdges();

    util::Percent progress(original_number_of_nodes);

    for (const NodeID node_v : util::irange(0u, original_number_of_nodes))
    {
        progress.PrintStatus(node_v);

        // only contract degree 2 vertices
        if (2 != graph.GetOutDegree(node_v))
        {
            continue;
        }

        // don't contract barrier node
        if (barrier_nodes.end() != barrier_nodes.find(node_v))
        {
            continue;
        }

        // check if v is a via node for a turn restriction, i.e. a 'directed' barrier node
        if (restriction_map.IsViaNode(node_v))
        {
            continue;
        }

        //    reverse_e2   forward_e2
        // u <---------- v -----------> w
        //    ----------> <-----------
        //    forward_e1   reverse_e1
        //
        // Will be compressed to:
        //
        //    reverse_e1
        // u <---------- w
        //    ---------->
        //    forward_e1
        //
        // If the edges are compatible.

        const bool reverse_edge_order = graph.GetEdgeData(graph.BeginEdges(node_v)).reversed;
        const EdgeID forward_e2 = graph.BeginEdges(node_v) + reverse_edge_order;
        BOOST_ASSERT(SPECIAL_EDGEID != forward_e2);
        BOOST_ASSERT(forward_e2 >= graph.BeginEdges(node_v) && forward_e2 < graph.EndEdges(node_v));
        const EdgeID reverse_e2 = graph.BeginEdges(node_v) + 1 - reverse_edge_order;
        BOOST_ASSERT(SPECIAL_EDGEID != reverse_e2);
        BOOST_ASSERT(reverse_e2 >= graph.BeginEdges(node_v) && reverse_e2 < graph.EndEdges(node_v));

        const EdgeData &fwd_edge_data2 = graph.GetEdgeData(forward_e2);
        const EdgeData &rev_edge_data2 = graph.GetEdgeData(reverse_e2);

        const NodeID node_w = graph.GetTarget(forward_e2);
        BOOST_ASSERT(SPECIAL_NODEID != node_w);
        BOOST_ASSERT(node_v != node_w);
        const NodeID node_u = graph.GetTarget(reverse_e2);
        BOOST_ASSERT(SPECIAL_NODEID != node_u);
        BOOST_ASSERT(node_u != node_v);

        const EdgeID forward_e1 = graph.FindEdge(node_u, node_v);
        BOOST_ASSERT(SPECIAL_EDGEID != forward_e1);
        BOOST_ASSERT(node_v == graph.GetTarget(forward_e1));
        const EdgeID reverse_e1 = graph.FindEdge(node_w, node_v);
        BOOST_ASSERT(SPECIAL_EDGEID != reverse_e1);
        BOOST_ASSERT(node_v == graph.GetTarget(reverse_e1));

        const EdgeData &fwd_edge_data1 = graph.GetEdgeData(forward_e1);
        const EdgeData &rev_edge_data1 = graph.GetEdgeData(reverse_e1);

        if (graph.FindEdgeInEitherDirection(node_u, node_w) != SPECIAL_EDGEID)
        {
            continue;
        }

        // this case can happen if two ways with different names overlap
        if (fwd_edge_data1.name_id != rev_edge_data1.name_id ||
            fwd_edge_data2.name_id != rev_edge_data2.name_id)
        {
            continue;
        }

        if (fwd_edge_data1.IsCompatibleTo(fwd_edge_data2) &&
            rev_edge_data1.IsCompatibleTo(rev_edge_data2))
        {
            BOOST_ASSERT(graph.GetEdgeData(forward_e1).name_id ==
                         graph.GetEdgeData(reverse_e1).name_id);
            BOOST_ASSERT(graph.GetEdgeData(forward_e2).name_id ==
                         graph.GetEdgeData(reverse_e2).name_id);

            // Do not compress edge if it crosses a traffic signal.
            // This can't be done in IsCompatibleTo, becase we only store the
            // traffic signals in the `traffic_lights` list, which EdgeData
            // doesn't have access to.
            const bool has_node_penalty = traffic_lights.find(node_v) != traffic_lights.end();
            if (has_node_penalty)
            {
                continue;
            }

            // Get distances before graph is modified
            const int forward_weight1 = graph.GetEdgeData(forward_e1).distance;
            const int forward_weight2 = graph.GetEdgeData(forward_e2).distance;

            BOOST_ASSERT(0 != forward_weight1);
            BOOST_ASSERT(0 != forward_weight2);

            const int reverse_weight1 = graph.GetEdgeData(reverse_e1).distance;
            const int reverse_weight2 = graph.GetEdgeData(reverse_e2).distance;

            BOOST_ASSERT(0 != reverse_weight1);
            BOOST_ASSERT(0 != reverse_weight2);

            // add weight of e2's to e1
            graph.GetEdgeData(forward_e1).distance += fwd_edge_data2.distance;
            graph.GetEdgeData(reverse_e1).distance += rev_edge_data2.distance;

            // extend e1's to targets of e2's
            graph.SetTarget(forward_e1, node_w);
            graph.SetTarget(reverse_e1, node_u);

            // remove e2's (if bidir, otherwise only one)
            graph.DeleteEdge(node_v, forward_e2);
            graph.DeleteEdge(node_v, reverse_e2);

            // update any involved turn restrictions
            restriction_map.FixupStartingTurnRestriction(node_u, node_v, node_w);
            restriction_map.FixupArrivingTurnRestriction(node_u, node_v, node_w, graph);

            restriction_map.FixupStartingTurnRestriction(node_w, node_v, node_u);
            restriction_map.FixupArrivingTurnRestriction(node_w, node_v, node_u, graph);

            // store compressed geometry in container
            geometry_compressor.CompressEdge(
                forward_e1, forward_e2, node_v, node_w, forward_weight1, forward_weight2);
            geometry_compressor.CompressEdge(
                reverse_e1, reverse_e2, node_v, node_u, reverse_weight1, reverse_weight2);
        }
    }

    PrintStatistics(original_number_of_nodes, original_number_of_edges, graph);

    // Repeate the loop, but now add all edges as uncompressed values.
    // The function AddUncompressedEdge does nothing if the edge is already
    // in the CompressedEdgeContainer.
    for (const NodeID node_u : util::irange(0u, original_number_of_nodes))
    {
        for (const auto edge_id : util::irange(graph.BeginEdges(node_u), graph.EndEdges(node_u)))
        {
            const EdgeData &data = graph.GetEdgeData(edge_id);
            const NodeID target = graph.GetTarget(edge_id);
            geometry_compressor.AddUncompressedEdge(edge_id, target, data.distance);
        }
    }
}
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;
}
void GraphCompressor::Compress(
    const std::unordered_set<NodeID> &barrier_nodes,
    const std::unordered_set<NodeID> &traffic_signals,
    ScriptingEnvironment &scripting_environment,
    std::vector<TurnRestriction> &turn_restrictions,
    std::vector<ConditionalTurnRestriction> &conditional_turn_restrictions,
    std::vector<UnresolvedManeuverOverride> &maneuver_overrides,
    util::NodeBasedDynamicGraph &graph,
    const std::vector<NodeBasedEdgeAnnotation> &node_data_container,
    CompressedEdgeContainer &geometry_compressor)
{
    const unsigned original_number_of_nodes = graph.GetNumberOfNodes();
    const unsigned original_number_of_edges = graph.GetNumberOfEdges();

    RestrictionCompressor restriction_compressor(
        turn_restrictions, conditional_turn_restrictions, maneuver_overrides);

    // we do not compress turn restrictions on degree two nodes. These nodes are usually used to
    // indicated `directed` barriers
    std::unordered_set<NodeID> restriction_via_nodes;

    const auto remember_via_nodes = [&](const auto &restriction) {
        if (restriction.Type() == RestrictionType::NODE_RESTRICTION)
        {
            const auto &node = restriction.AsNodeRestriction();
            restriction_via_nodes.insert(node.via);
        }
        else
        {
            BOOST_ASSERT(restriction.Type() == RestrictionType::WAY_RESTRICTION);
            const auto &way = restriction.AsWayRestriction();
            restriction_via_nodes.insert(way.in_restriction.via);
            restriction_via_nodes.insert(way.out_restriction.via);
        }
    };
    std::for_each(turn_restrictions.begin(), turn_restrictions.end(), remember_via_nodes);
    std::for_each(conditional_turn_restrictions.begin(),
                  conditional_turn_restrictions.end(),
                  remember_via_nodes);

    {
        const auto weight_multiplier =
            scripting_environment.GetProfileProperties().GetWeightMultiplier();
        util::UnbufferedLog log;
        util::Percent progress(log, original_number_of_nodes);

        for (const NodeID node_v : util::irange(0u, original_number_of_nodes))
        {
            progress.PrintStatus(node_v);

            // only contract degree 2 vertices
            if (2 != graph.GetOutDegree(node_v))
            {
                continue;
            }

            // don't contract barrier node
            if (barrier_nodes.end() != barrier_nodes.find(node_v))
            {
                continue;
            }

            // check if v is a via node for a turn restriction, i.e. a 'directed' barrier node
            if (restriction_via_nodes.count(node_v))
            {
                continue;
            }

            //    reverse_e2   forward_e2
            // u <---------- v -----------> w
            //    ----------> <-----------
            //    forward_e1   reverse_e1
            //
            // Will be compressed to:
            //
            //    reverse_e1
            // u <---------- w
            //    ---------->
            //    forward_e1
            //
            // If the edges are compatible.
            const bool reverse_edge_order = graph.GetEdgeData(graph.BeginEdges(node_v)).reversed;
            const EdgeID forward_e2 = graph.BeginEdges(node_v) + reverse_edge_order;
            BOOST_ASSERT(SPECIAL_EDGEID != forward_e2);
            BOOST_ASSERT(forward_e2 >= graph.BeginEdges(node_v) &&
                         forward_e2 < graph.EndEdges(node_v));
            const EdgeID reverse_e2 = graph.BeginEdges(node_v) + 1 - reverse_edge_order;

            BOOST_ASSERT(SPECIAL_EDGEID != reverse_e2);
            BOOST_ASSERT(reverse_e2 >= graph.BeginEdges(node_v) &&
                         reverse_e2 < graph.EndEdges(node_v));

            const EdgeData &fwd_edge_data2 = graph.GetEdgeData(forward_e2);
            const EdgeData &rev_edge_data2 = graph.GetEdgeData(reverse_e2);

            const NodeID node_w = graph.GetTarget(forward_e2);
            BOOST_ASSERT(SPECIAL_NODEID != node_w);
            BOOST_ASSERT(node_v != node_w);
            const NodeID node_u = graph.GetTarget(reverse_e2);
            BOOST_ASSERT(SPECIAL_NODEID != node_u);
            BOOST_ASSERT(node_u != node_v);

            const EdgeID forward_e1 = graph.FindEdge(node_u, node_v);
            BOOST_ASSERT(SPECIAL_EDGEID != forward_e1);
            BOOST_ASSERT(node_v == graph.GetTarget(forward_e1));
            const EdgeID reverse_e1 = graph.FindEdge(node_w, node_v);
            BOOST_ASSERT(SPECIAL_EDGEID != reverse_e1);
            BOOST_ASSERT(node_v == graph.GetTarget(reverse_e1));

            const EdgeData &fwd_edge_data1 = graph.GetEdgeData(forward_e1);
            const EdgeData &rev_edge_data1 = graph.GetEdgeData(reverse_e1);
            const auto fwd_annotation_data1 = node_data_container[fwd_edge_data1.annotation_data];
            const auto fwd_annotation_data2 = node_data_container[fwd_edge_data2.annotation_data];
            const auto rev_annotation_data1 = node_data_container[rev_edge_data1.annotation_data];
            const auto rev_annotation_data2 = node_data_container[rev_edge_data2.annotation_data];

            if (graph.FindEdgeInEitherDirection(node_u, node_w) != SPECIAL_EDGEID)
            {
                continue;
            }

            // this case can happen if two ways with different names overlap
            if ((fwd_annotation_data1.name_id != rev_annotation_data1.name_id) ||
                (fwd_annotation_data2.name_id != rev_annotation_data2.name_id))
            {
                continue;
            }

            if ((fwd_edge_data1.flags == fwd_edge_data2.flags) &&
                (rev_edge_data1.flags == rev_edge_data2.flags) &&
                (fwd_edge_data1.reversed == fwd_edge_data2.reversed) &&
                (rev_edge_data1.reversed == rev_edge_data2.reversed) &&
                // annotations need to match, except for the lane-id which can differ
                fwd_annotation_data1.CanCombineWith(fwd_annotation_data2) &&
                rev_annotation_data1.CanCombineWith(rev_annotation_data2))
            {
                BOOST_ASSERT(!(graph.GetEdgeData(forward_e1).reversed &&
                               graph.GetEdgeData(reverse_e1).reversed));
                /*
                 * Remember Lane Data for compressed parts. This handles scenarios where lane-data
                 * is
                 * only kept up until a traffic light.
                 *
                 *                |    |
                 * ----------------    |
                 *         -^ |        |
                 * -----------         |
                 *         -v |        |
                 * ---------------     |
                 *                |    |
                 *
                 *  u ------- v ---- w
                 *
                 * Since the edge is compressable, we can transfer:
                 * "left|right" (uv) and "" (uw) into a string with "left|right" (uw) for the
                 * compressed
                 * edge.
                 * Doing so, we might mess up the point from where the lanes are shown. It should be
                 * reasonable, since the announcements have to come early anyhow. So there is a
                 * potential danger in here, but it saves us from adding a lot of additional edges
                 * for
                 * turn-lanes. Without this,we would have to treat any turn-lane beginning/ending
                 * just
                 * like a barrier.
                 */
                const auto selectAnnotation = [&node_data_container](
                    const AnnotationID front_annotation, const AnnotationID back_annotation) {
                    // A lane has tags: u - (front) - v - (back) - w
                    // During contraction, we keep only one of the tags. Usually the one closer
                    // to the intersection is preferred. If its empty, however, we keep the
                    // non-empty one
                    if (node_data_container[back_annotation].lane_description_id ==
                        INVALID_LANE_DESCRIPTIONID)
                        return front_annotation;
                    return back_annotation;
                };

                graph.GetEdgeData(forward_e1).annotation_data = selectAnnotation(
                    fwd_edge_data1.annotation_data, fwd_edge_data2.annotation_data);
                graph.GetEdgeData(reverse_e1).annotation_data = selectAnnotation(
                    rev_edge_data1.annotation_data, rev_edge_data2.annotation_data);
                graph.GetEdgeData(forward_e2).annotation_data = selectAnnotation(
                    fwd_edge_data2.annotation_data, fwd_edge_data1.annotation_data);
                graph.GetEdgeData(reverse_e2).annotation_data = selectAnnotation(
                    rev_edge_data2.annotation_data, rev_edge_data1.annotation_data);

                /*
                // Do not compress edge if it crosses a traffic signal.
                // This can't be done in CanCombineWith, becase we only store the
                // traffic signals in the `traffic signal` list, which EdgeData
                // doesn't have access to.
                */
                const bool has_node_penalty = traffic_signals.find(node_v) != traffic_signals.end();
                EdgeDuration node_duration_penalty = MAXIMAL_EDGE_DURATION;
                EdgeWeight node_weight_penalty = INVALID_EDGE_WEIGHT;
                if (has_node_penalty)
                {
                    // we cannot handle this as node penalty, if it depends on turn direction
                    if (fwd_edge_data1.flags.restricted != fwd_edge_data2.flags.restricted)
                        continue;

                    // generate an artifical turn for the turn penalty generation
                    std::vector<ExtractionTurnLeg> roads_on_the_right;
                    std::vector<ExtractionTurnLeg> roads_on_the_left;
                    ExtractionTurn extraction_turn(0,
                                                   2,
                                                   false,
                                                   true,
                                                   false,
                                                   false,
                                                   TRAVEL_MODE_DRIVING,
                                                   false,
                                                   false,
                                                   1,
                                                   0,
                                                   0,
                                                   0,
                                                   0,
                                                   false,
                                                   TRAVEL_MODE_DRIVING,
                                                   false,
                                                   false,
                                                   1,
                                                   0,
                                                   0,
                                                   0,
                                                   0,
                                                   roads_on_the_right,
                                                   roads_on_the_left);
                    scripting_environment.ProcessTurn(extraction_turn);
                    node_duration_penalty = extraction_turn.duration * 10;
                    node_weight_penalty = extraction_turn.weight * weight_multiplier;
                }

                // Get weights before graph is modified
                const auto forward_weight1 = fwd_edge_data1.weight;
                const auto forward_weight2 = fwd_edge_data2.weight;
                const auto forward_duration1 = fwd_edge_data1.duration;
                const auto forward_duration2 = fwd_edge_data2.duration;
                const auto forward_distance2 = fwd_edge_data2.distance;

                BOOST_ASSERT(0 != forward_weight1);
                BOOST_ASSERT(0 != forward_weight2);

                const auto reverse_weight1 = rev_edge_data1.weight;
                const auto reverse_weight2 = rev_edge_data2.weight;
                const auto reverse_duration1 = rev_edge_data1.duration;
                const auto reverse_duration2 = rev_edge_data2.duration;
                const auto reverse_distance2 = rev_edge_data2.distance;

#ifndef NDEBUG
                // Because distances are symmetrical, we only need one
                // per edge - here we double-check that they match
                // their mirrors.
                const auto reverse_distance1 = rev_edge_data1.distance;
                const auto forward_distance1 = fwd_edge_data1.distance;
                BOOST_ASSERT(forward_distance1 == reverse_distance2);
                BOOST_ASSERT(forward_distance2 == reverse_distance1);
#endif

                BOOST_ASSERT(0 != reverse_weight1);
                BOOST_ASSERT(0 != reverse_weight2);

                // add weight of e2's to e1
                graph.GetEdgeData(forward_e1).weight += forward_weight2;
                graph.GetEdgeData(reverse_e1).weight += reverse_weight2;

                // add duration of e2's to e1
                graph.GetEdgeData(forward_e1).duration += forward_duration2;
                graph.GetEdgeData(reverse_e1).duration += reverse_duration2;

                // add distance of e2's to e1
                graph.GetEdgeData(forward_e1).distance += forward_distance2;
                graph.GetEdgeData(reverse_e1).distance += reverse_distance2;

                if (node_weight_penalty != INVALID_EDGE_WEIGHT &&
                    node_duration_penalty != MAXIMAL_EDGE_DURATION)
                {
                    graph.GetEdgeData(forward_e1).weight += node_weight_penalty;
                    graph.GetEdgeData(reverse_e1).weight += node_weight_penalty;
                    graph.GetEdgeData(forward_e1).duration += node_duration_penalty;
                    graph.GetEdgeData(reverse_e1).duration += node_duration_penalty;
                    // Note: no penalties for distances
                }

                // extend e1's to targets of e2's
                graph.SetTarget(forward_e1, node_w);
                graph.SetTarget(reverse_e1, node_u);

                // remove e2's (if bidir, otherwise only one)
                graph.DeleteEdge(node_v, forward_e2);
                graph.DeleteEdge(node_v, reverse_e2);

                // update any involved turn restrictions
                restriction_compressor.Compress(node_u, node_v, node_w);

                // store compressed geometry in container
                geometry_compressor.CompressEdge(forward_e1,
                                                 forward_e2,
                                                 node_v,
                                                 node_w,
                                                 forward_weight1,
                                                 forward_weight2,
                                                 forward_duration1,
                                                 forward_duration2,
                                                 node_weight_penalty,
                                                 node_duration_penalty);
                geometry_compressor.CompressEdge(reverse_e1,
                                                 reverse_e2,
                                                 node_v,
                                                 node_u,
                                                 reverse_weight1,
                                                 reverse_weight2,
                                                 reverse_duration1,
                                                 reverse_duration2,
                                                 node_weight_penalty,
                                                 node_duration_penalty);
            }
        }
    }

    PrintStatistics(original_number_of_nodes, original_number_of_edges, graph);

    // Repeate the loop, but now add all edges as uncompressed values.
    // The function AddUncompressedEdge does nothing if the edge is already
    // in the CompressedEdgeContainer.
    for (const NodeID node_u : util::irange(0u, original_number_of_nodes))
    {
        for (const auto edge_id : util::irange(graph.BeginEdges(node_u), graph.EndEdges(node_u)))
        {
            const EdgeData &data = graph.GetEdgeData(edge_id);
            const NodeID target = graph.GetTarget(edge_id);
            geometry_compressor.AddUncompressedEdge(edge_id, target, data.weight, data.duration);
        }
    }
}