BaseParser::BaseParser(
    ExtractorCallbacks   * extractor_callbacks,
    ScriptingEnvironment & scripting_environment
) : extractor_callbacks(extractor_callbacks),
    lua_state(scripting_environment.getLuaStateForThreadID(0)),
    scripting_environment(scripting_environment),
    use_turn_restrictions(true)
{
    ReadUseRestrictionsSetting();
    ReadRestrictionExceptions();
}
RestrictionParser::RestrictionParser(ScriptingEnvironment &scripting_environment)
    : use_turn_restrictions(scripting_environment.GetProfileProperties().use_turn_restrictions)
{
    if (use_turn_restrictions)
    {
        restrictions = scripting_environment.GetRestrictions();
        const unsigned count = restrictions.size();
        if (count > 0)
        {
            util::SimpleLogger().Write() << "Found " << count << " turn restriction tags:";
            for (const std::string &str : restrictions)
            {
                util::SimpleLogger().Write() << "  " << str;
            }
        }
        else
        {
            util::SimpleLogger().Write() << "Found no turn restriction tags";
        }
    }
}
示例#3
0
BaseParser::BaseParser(ExtractorCallbacks* ec, ScriptingEnvironment& se) :
extractor_callbacks(ec), scriptingEnvironment(se), luaState(NULL), use_turn_restrictions(true) {
    luaState = se.getLuaStateForThreadID(0);
    ReadUseRestrictionsSetting();
    ReadRestrictionExceptions();
}
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);
        }
    }
}
/// Actually it also generates OriginalEdgeData and serializes them...
void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
    ScriptingEnvironment &scripting_environment,
    const std::string &original_edge_data_filename,
    const std::string &turn_lane_data_filename,
    const std::string &edge_segment_lookup_filename,
    const std::string &edge_fixed_penalties_filename,
    const bool generate_edge_lookup)
{
    util::SimpleLogger().Write() << "generating edge-expanded edges";

    std::size_t node_based_edge_counter = 0;
    std::size_t original_edges_counter = 0;
    restricted_turns_counter = 0;
    skipped_uturns_counter = 0;
    skipped_barrier_turns_counter = 0;

    std::ofstream edge_data_file(original_edge_data_filename.c_str(), std::ios::binary);
    std::ofstream edge_segment_file;
    std::ofstream edge_penalty_file;

    if (generate_edge_lookup)
    {
        edge_segment_file.open(edge_segment_lookup_filename.c_str(), std::ios::binary);
        edge_penalty_file.open(edge_fixed_penalties_filename.c_str(), std::ios::binary);
    }

    // Writes a dummy value at the front that is updated later with the total length
    const unsigned length_prefix_empty_space{0};
    edge_data_file.write(reinterpret_cast<const char *>(&length_prefix_empty_space),
                         sizeof(length_prefix_empty_space));

    std::vector<OriginalEdgeData> original_edge_data_vector;
    original_edge_data_vector.reserve(1024 * 1024);

    // Loop over all turns and generate new set of edges.
    // Three nested loop look super-linear, but we are dealing with a (kind of)
    // linear number of turns only.
    util::Percent progress(m_node_based_graph->GetNumberOfNodes());
    SuffixTable street_name_suffix_table(scripting_environment);
    guidance::TurnAnalysis turn_analysis(*m_node_based_graph,
                                         m_node_info_list,
                                         *m_restriction_map,
                                         m_barrier_nodes,
                                         m_compressed_edge_container,
                                         name_table,
                                         street_name_suffix_table);
    guidance::lanes::TurnLaneHandler turn_lane_handler(
        *m_node_based_graph, turn_lane_offsets, turn_lane_masks, m_node_info_list, turn_analysis);

    bearing_class_by_node_based_node.resize(m_node_based_graph->GetNumberOfNodes(),
                                            std::numeric_limits<std::uint32_t>::max());

    guidance::LaneDataIdMap lane_data_map;
    for (const auto node_u : util::irange(0u, m_node_based_graph->GetNumberOfNodes()))
    {
        progress.PrintStatus(node_u);
        for (const EdgeID edge_from_u : m_node_based_graph->GetAdjacentEdgeRange(node_u))
        {
            if (m_node_based_graph->GetEdgeData(edge_from_u).reversed)
            {
                continue;
            }

            const NodeID node_v = m_node_based_graph->GetTarget(edge_from_u);
            ++node_based_edge_counter;
            auto intersection = turn_analysis.getIntersection(node_u, edge_from_u);
            intersection =
                turn_analysis.assignTurnTypes(node_u, edge_from_u, std::move(intersection));

            intersection = turn_lane_handler.assignTurnLanes(
                node_u, edge_from_u, std::move(intersection), lane_data_map);
            const auto possible_turns = turn_analysis.transformIntersectionIntoTurns(intersection);

            // the entry class depends on the turn, so we have to classify the interesction for
            // every edge
            const auto turn_classification = classifyIntersection(node_v,
                                                                  intersection,
                                                                  *m_node_based_graph,
                                                                  m_compressed_edge_container,
                                                                  m_node_info_list);

            const auto entry_class_id = [&](const util::guidance::EntryClass entry_class) {
                if (0 == entry_class_hash.count(entry_class))
                {
                    const auto id = static_cast<std::uint16_t>(entry_class_hash.size());
                    entry_class_hash[entry_class] = id;
                    return id;
                }
                else
                {
                    return entry_class_hash.find(entry_class)->second;
                }
            }(turn_classification.first);

            const auto bearing_class_id = [&](const util::guidance::BearingClass bearing_class) {
                if (0 == bearing_class_hash.count(bearing_class))
                {
                    const auto id = static_cast<std::uint32_t>(bearing_class_hash.size());
                    bearing_class_hash[bearing_class] = id;
                    return id;
                }
                else
                {
                    return bearing_class_hash.find(bearing_class)->second;
                }
            }(turn_classification.second);
            bearing_class_by_node_based_node[node_v] = bearing_class_id;

            for (const auto turn : possible_turns)
            {
                // only add an edge if turn is not prohibited
                const EdgeData &edge_data1 = m_node_based_graph->GetEdgeData(edge_from_u);
                const EdgeData &edge_data2 = m_node_based_graph->GetEdgeData(turn.eid);

                BOOST_ASSERT(edge_data1.edge_id != edge_data2.edge_id);
                BOOST_ASSERT(!edge_data1.reversed);
                BOOST_ASSERT(!edge_data2.reversed);

                // the following is the core of the loop.
                unsigned distance = edge_data1.distance;
                if (m_traffic_lights.find(node_v) != m_traffic_lights.end())
                {
                    distance += profile_properties.traffic_signal_penalty;
                }

                const int32_t turn_penalty = scripting_environment.GetTurnPenalty(180. - turn.angle);
                const auto turn_instruction = turn.instruction;

                if (guidance::isUturn(turn_instruction))
                {
                    distance += profile_properties.u_turn_penalty;
                }

                distance += turn_penalty;

                BOOST_ASSERT(m_compressed_edge_container.HasEntryForID(edge_from_u));
                original_edge_data_vector.emplace_back(
                    m_compressed_edge_container.GetPositionForID(edge_from_u),
                    edge_data1.name_id,
                    turn.lane_data_id,
                    turn_instruction,
                    entry_class_id,
                    edge_data1.travel_mode);

                ++original_edges_counter;

                if (original_edge_data_vector.size() > 1024 * 1024 * 10)
                {
                    FlushVectorToStream(edge_data_file, original_edge_data_vector);
                }

                BOOST_ASSERT(SPECIAL_NODEID != edge_data1.edge_id);
                BOOST_ASSERT(SPECIAL_NODEID != edge_data2.edge_id);

                // NOTE: potential overflow here if we hit 2^32 routable edges
                BOOST_ASSERT(m_edge_based_edge_list.size() <= std::numeric_limits<NodeID>::max());
                m_edge_based_edge_list.emplace_back(edge_data1.edge_id,
                                                    edge_data2.edge_id,
                                                    m_edge_based_edge_list.size(),
                                                    distance,
                                                    true,
                                                    false);

                // Here is where we write out the mapping between the edge-expanded edges, and
                // the node-based edges that are originally used to calculate the `distance`
                // for the edge-expanded edges.  About 40 lines back, there is:
                //
                //                 unsigned distance = edge_data1.distance;
                //
                // This tells us that the weight for an edge-expanded-edge is based on the weight
                // of the *source* node-based edge.  Therefore, we will look up the individual
                // segments of the source node-based edge, and write out a mapping between
                // those and the edge-based-edge ID.
                // External programs can then use this mapping to quickly perform
                // updates to the edge-expanded-edge based directly on its ID.
                if (generate_edge_lookup)
                {
                    const auto node_based_edges =
                        m_compressed_edge_container.GetBucketReference(edge_from_u);
                    NodeID previous = node_u;

                    const unsigned node_count = node_based_edges.size() + 1;
                    const QueryNode &first_node = m_node_info_list[previous];

                    lookup::SegmentHeaderBlock header = {node_count, first_node.node_id};

                    edge_segment_file.write(reinterpret_cast<const char *>(&header),
                                            sizeof(header));

                    for (auto target_node : node_based_edges)
                    {
                        const QueryNode &from = m_node_info_list[previous];
                        const QueryNode &to = m_node_info_list[target_node.node_id];
                        const double segment_length =
                            util::coordinate_calculation::greatCircleDistance(from, to);

                        lookup::SegmentBlock nodeblock = {
                            to.node_id, segment_length, target_node.weight};

                        edge_segment_file.write(reinterpret_cast<const char *>(&nodeblock),
                                                sizeof(nodeblock));
                        previous = target_node.node_id;
                    }

                    // We also now write out the mapping between the edge-expanded edges and the
                    // original nodes. Since each edge represents a possible maneuver, external
                    // programs can use this to quickly perform updates to edge weights in order
                    // to penalize certain turns.

                    // If this edge is 'trivial' -- where the compressed edge corresponds
                    // exactly to an original OSM segment -- we can pull the turn's preceding
                    // node ID directly with `node_u`; otherwise, we need to look up the node
                    // immediately preceding the turn from the compressed edge container.
                    const bool isTrivial = m_compressed_edge_container.IsTrivial(edge_from_u);

                    const auto &from_node =
                        isTrivial
                            ? m_node_info_list[node_u]
                            : m_node_info_list[m_compressed_edge_container.GetLastEdgeSourceID(
                                  edge_from_u)];
                    const auto &via_node =
                        m_node_info_list[m_compressed_edge_container.GetLastEdgeTargetID(
                            edge_from_u)];
                    const auto &to_node =
                        m_node_info_list[m_compressed_edge_container.GetFirstEdgeTargetID(
                            turn.eid)];

                    const unsigned fixed_penalty = distance - edge_data1.distance;
                    lookup::PenaltyBlock penaltyblock = {
                        fixed_penalty, from_node.node_id, via_node.node_id, to_node.node_id};
                    edge_penalty_file.write(reinterpret_cast<const char *>(&penaltyblock),
                                            sizeof(penaltyblock));
                }
            }
        }
    }

    util::SimpleLogger().Write() << "Created " << entry_class_hash.size() << " entry classes and "
                                 << bearing_class_hash.size() << " Bearing Classes";

    util::SimpleLogger().Write() << "Writing Turn Lane Data to File...";
    std::ofstream turn_lane_data_file(turn_lane_data_filename.c_str(), std::ios::binary);
    std::vector<util::guidance::LaneTupelIdPair> lane_data(lane_data_map.size());
    // extract lane data sorted by ID
    for (auto itr : lane_data_map)
        lane_data[itr.second] = itr.first;

    std::uint64_t size = lane_data.size();
    turn_lane_data_file.write(reinterpret_cast<const char *>(&size), sizeof(size));

    if (!lane_data.empty())
        turn_lane_data_file.write(reinterpret_cast<const char *>(&lane_data[0]),
                                  sizeof(util::guidance::LaneTupelIdPair) * lane_data.size());

    util::SimpleLogger().Write() << "done.";

    FlushVectorToStream(edge_data_file, original_edge_data_vector);

    // Finally jump back to the empty space at the beginning and write length prefix
    edge_data_file.seekp(std::ios::beg);

    const auto length_prefix = boost::numeric_cast<unsigned>(original_edges_counter);
    static_assert(sizeof(length_prefix_empty_space) == sizeof(length_prefix), "type mismatch");

    edge_data_file.write(reinterpret_cast<const char *>(&length_prefix), sizeof(length_prefix));

    util::SimpleLogger().Write() << "Generated " << m_edge_based_node_list.size()
                                 << " edge based nodes";
    util::SimpleLogger().Write() << "Node-based graph contains " << node_based_edge_counter
                                 << " edges";
    util::SimpleLogger().Write() << "Edge-expanded graph ...";
    util::SimpleLogger().Write() << "  contains " << m_edge_based_edge_list.size() << " edges";
    util::SimpleLogger().Write() << "  skips " << restricted_turns_counter << " turns, "
                                                                              "defined by "
                                 << m_restriction_map->size() << " restrictions";
    util::SimpleLogger().Write() << "  skips " << skipped_uturns_counter << " U turns";
    util::SimpleLogger().Write() << "  skips " << skipped_barrier_turns_counter
                                 << " turns over barriers";
}