#include "extractor/extraction_containers.hpp" #include "extractor/extraction_segment.hpp" #include "extractor/extraction_way.hpp" #include "extractor/files.hpp" #include "extractor/name_table.hpp" #include "extractor/restriction.hpp" #include "extractor/serialization.hpp" #include "util/coordinate_calculation.hpp" #include "util/integer_range.hpp" #include "util/exception.hpp" #include "util/exception_utils.hpp" #include "util/for_each_indexed.hpp" #include "util/for_each_pair.hpp" #include "util/log.hpp" #include "util/timing_util.hpp" #include #include #include #include #include #include #include #include namespace { namespace oe = osrm::extractor; struct CmpEdgeByOSMStartID { using value_type = oe::InternalExtractorEdge; bool operator()(const value_type &lhs, const value_type &rhs) const { return lhs.result.osm_source_id < rhs.result.osm_source_id; } }; struct CmpEdgeByOSMTargetID { using value_type = oe::InternalExtractorEdge; bool operator()(const value_type &lhs, const value_type &rhs) const { return lhs.result.osm_target_id < rhs.result.osm_target_id; } }; struct CmpEdgeByInternalSourceTargetAndName { using value_type = oe::InternalExtractorEdge; bool operator()(const value_type &lhs, const value_type &rhs) const { if (lhs.result.source != rhs.result.source) return lhs.result.source < rhs.result.source; if (lhs.result.source == SPECIAL_NODEID) return false; if (lhs.result.target != rhs.result.target) return lhs.result.target < rhs.result.target; if (lhs.result.target == SPECIAL_NODEID) return false; auto const lhs_name_id = edge_annotation_data[lhs.result.annotation_data].name_id; auto const rhs_name_id = edge_annotation_data[rhs.result.annotation_data].name_id; if (lhs_name_id == rhs_name_id) return false; if (lhs_name_id == EMPTY_NAMEID) return false; if (rhs_name_id == EMPTY_NAMEID) return true; BOOST_ASSERT(!name_offsets.empty() && name_offsets.back() == name_data.size()); const oe::ExtractionContainers::NameCharData::const_iterator data = name_data.begin(); return std::lexicographical_compare(data + name_offsets[lhs_name_id], data + name_offsets[lhs_name_id + 1], data + name_offsets[rhs_name_id], data + name_offsets[rhs_name_id + 1]); } const oe::ExtractionContainers::AnnotationDataVector &edge_annotation_data; const oe::ExtractionContainers::NameCharData &name_data; const oe::ExtractionContainers::NameOffsets &name_offsets; }; template inline NodeID mapExternalToInternalNodeID(Iter first, Iter last, const OSMNodeID value) { const auto it = std::lower_bound(first, last, value); return (it == last || value < *it) ? SPECIAL_NODEID : static_cast(std::distance(first, it)); } /** * Here's what these properties represent on the node-based-graph * way "ABCD" way "AB" * ----------------------------------------------------------------- * ⬇ A first_segment_source_id * ⬇ | * ⬇︎ B first_segment_target_id A first_segment_source_id * ⬇︎ | ⬇ | last_segment_source_id * ⬇︎ | ⬇ | * ⬇︎ | B first_segment_target_id * ⬇︎ C last_segment_source_id last_segment_target_id * ⬇︎ | * ⬇︎ D last_segment_target_id * * Finds the point where two ways connect at the end, and returns the 3 * node-based nodes that describe the turn (the node just before, the * node at the turn, and the next node after the turn) **/ std::tuple find_turn_nodes(const oe::NodesOfWay &from, const oe::NodesOfWay &via, const OSMNodeID &intersection_node) { // connection node needed to choose orientation if from and via are the same way. E.g. u-turns if (intersection_node == SPECIAL_OSM_NODEID || intersection_node == from.first_segment_source_id()) { if (from.first_segment_source_id() == via.first_segment_source_id()) { return std::make_tuple(from.first_segment_target_id(), via.first_segment_source_id(), via.first_segment_target_id()); } if (from.first_segment_source_id() == via.last_segment_target_id()) { return std::make_tuple(from.first_segment_target_id(), via.last_segment_target_id(), via.last_segment_source_id()); } } if (intersection_node == SPECIAL_OSM_NODEID || intersection_node == from.last_segment_target_id()) { if (from.last_segment_target_id() == via.first_segment_source_id()) { return std::make_tuple(from.last_segment_source_id(), via.first_segment_source_id(), via.first_segment_target_id()); } if (from.last_segment_target_id() == via.last_segment_target_id()) { return std::make_tuple(from.last_segment_source_id(), via.last_segment_target_id(), via.last_segment_source_id()); } } return std::make_tuple(SPECIAL_OSM_NODEID, SPECIAL_OSM_NODEID, SPECIAL_OSM_NODEID); } // Via-node paths describe a relation between the two segments closest // to the shared via-node on the from and to ways. // from: [a, b, c, d, e] // to: [f, g, h, i, j] // // The via node establishes the orientation of the from/to intersection when choosing the // segments. // via | node path // a=f | b,a,g // a=j | b,a,i // e=f | d,e,g // e=j | d,e,i oe::ViaNodePath find_via_node_path(const std::string &turn_relation_type, const oe::NodesOfWay &from_segment, const oe::NodesOfWay &to_segment, const OSMNodeID via_node, const std::function &to_internal_node) { OSMNodeID from, via, to; std::tie(from, via, to) = find_turn_nodes(from_segment, to_segment, via_node); if (via == SPECIAL_OSM_NODEID) { // unconnected osrm::util::Log(logDEBUG) << turn_relation_type << " references unconnected way: " << from_segment.way_id; return oe::ViaNodePath{SPECIAL_NODEID, SPECIAL_NODEID, SPECIAL_NODEID}; } return oe::ViaNodePath{to_internal_node(from), to_internal_node(via), to_internal_node(to)}; } // Via way paths are comprised of: // 1. The segment in the from way that intersects with the via ways // 2. All segments that make up the via path // 3. The segment in the to way that intersects with the via path. // // from: [a, b, c, d, e] // via: [[f, g, h, i, j], [k, l], [m, n, o]] // to: [p, q, r, s] // // First establish the orientation of the from/via intersection by finding which end // nodes both ways share. From this we can select the from segment. // // intersect | from segment | next_connection // a=f | b,a | f // a=j | b,a | j // e=f | e,d | f // e=j | e,d | j // // Use the next connection to inform the orientation of the first via // way and the intersection between first and second via ways. // // next_connection | intersect | via result | next_next_connection // f | j=k | [f,g,h,i,j] | k // f | j=l | [f,g,h,i,j] | l // j | f=k | [j,i,h,g,f] | k // j | f=l | [j,i,h,g,f] | l // // This is continued for the remaining via ways, appending to the via result // // The final via/to intersection also uses the next_connection information in a similar fashion. // // next_connection | intersect | to_segment // m | o=p | p,q // m | o=s | s,r // o | m=p | p,q // o | m=s | s,r // // The final result is a list of nodes that represent a valid from->via->to path through the // ways. // // E.g. if intersection nodes are a=j, f=l, k=o, m=s // the result will be {e [d,c,b,a,i,h,g,f,k,n,m] r} oe::ViaWayPath find_via_way_path(const std::string &turn_relation_type, const oe::NodesOfWay &from_way, const std::vector &via_ways, const oe::NodesOfWay &to_way, const std::function &to_internal_node) { BOOST_ASSERT(!via_ways.empty()); oe::ViaWayPath way_path; // Find the orientation of the connected ways starting with the from-via intersection. OSMNodeID from, via; std::tie(from, via, std::ignore) = find_turn_nodes(from_way, via_ways.front(), SPECIAL_OSM_NODEID); if (via == SPECIAL_OSM_NODEID) { osrm::util::Log(logDEBUG) << turn_relation_type << " has unconnected from and via ways: " << from_way.way_id << ", " << via_ways.front().way_id; return oe::ViaWayPath{SPECIAL_NODEID, {}, SPECIAL_NODEID}; } way_path.from = to_internal_node(from); way_path.via.push_back(to_internal_node(via)); // Use the connection node from the previous intersection to inform our conversion of // via ways into internal nodes. OSMNodeID next_connection = via; for (const auto &via_way : via_ways) { if (next_connection == via_way.first_segment_source_id()) { std::transform(std::next(via_way.node_ids.begin()), via_way.node_ids.end(), std::back_inserter(way_path.via), to_internal_node); next_connection = via_way.last_segment_target_id(); } else if (next_connection == via_way.last_segment_target_id()) { std::transform(std::next(via_way.node_ids.rbegin()), via_way.node_ids.rend(), std::back_inserter(way_path.via), to_internal_node); next_connection = via_way.first_segment_source_id(); } else { osrm::util::Log(logDEBUG) << turn_relation_type << " has unconnected via way: " << via_way.way_id << " to node " << next_connection; return oe::ViaWayPath{SPECIAL_NODEID, {}, SPECIAL_NODEID}; } } // Add the final to node after the via-to intersection. if (next_connection == to_way.first_segment_source_id()) { way_path.to = to_internal_node(to_way.first_segment_target_id()); } else if (next_connection == to_way.last_segment_target_id()) { way_path.to = to_internal_node(to_way.last_segment_source_id()); } else { osrm::util::Log(logDEBUG) << turn_relation_type << " has unconnected via and to ways: " << via_ways.back().way_id << ", " << to_way.way_id; return oe::ViaWayPath{SPECIAL_NODEID, {}, SPECIAL_NODEID}; } return way_path; } // Check if we were able to resolve all the involved OSM elements before translating to an // internal via-way turn path oe::ViaWayPath get_via_way_path_from_OSM_ids(const std::string &turn_relation_type, const std::unordered_map &referenced_ways, const OSMWayID from_id, const OSMWayID to_id, const std::vector &via_ids, const std::function &to_internal_node) { auto const from_way_itr = referenced_ways.find(from_id); if (from_way_itr->second.way_id != from_id) { osrm::util::Log(logDEBUG) << turn_relation_type << " references invalid from way: " << from_id; return oe::ViaWayPath{SPECIAL_NODEID, {}, SPECIAL_NODEID}; } std::vector via_ways; for (const auto &via_id : via_ids) { auto const via_segment_itr = referenced_ways.find(via_id); if (via_segment_itr->second.way_id != via_id) { osrm::util::Log(logDEBUG) << turn_relation_type << " references invalid via way: " << via_id; return oe::ViaWayPath{SPECIAL_NODEID, {}, SPECIAL_NODEID}; } via_ways.push_back(via_segment_itr->second); } auto const to_way_itr = referenced_ways.find(to_id); if (to_way_itr->second.way_id != to_id) { osrm::util::Log(logDEBUG) << turn_relation_type << " references invalid to way: " << to_id; return oe::ViaWayPath{SPECIAL_NODEID, {}, SPECIAL_NODEID}; } return find_via_way_path( turn_relation_type, from_way_itr->second, via_ways, to_way_itr->second, to_internal_node); } // Check if we were able to resolve all the involved OSM elements before translating to an // internal via-node turn path oe::ViaNodePath get_via_node_path_from_OSM_ids(const std::string &turn_relation_type, const std::unordered_map &referenced_ways, const OSMWayID from_id, const OSMWayID to_id, const OSMNodeID via_node, const std::function &to_internal_node) { auto const from_segment_itr = referenced_ways.find(from_id); if (from_segment_itr->second.way_id != from_id) { osrm::util::Log(logDEBUG) << turn_relation_type << " references invalid way: " << from_id; return oe::ViaNodePath{SPECIAL_NODEID, SPECIAL_NODEID, SPECIAL_NODEID}; } auto const to_segment_itr = referenced_ways.find(to_id); if (to_segment_itr->second.way_id != to_id) { osrm::util::Log(logDEBUG) << turn_relation_type << " references invalid way: " << to_id; return oe::ViaNodePath{SPECIAL_NODEID, SPECIAL_NODEID, SPECIAL_NODEID}; } return find_via_node_path(turn_relation_type, from_segment_itr->second, to_segment_itr->second, via_node, to_internal_node); } } // namespace namespace osrm::extractor { ExtractionContainers::ExtractionContainers() { // Insert four empty strings offsets for name, ref, destination, pronunciation, and exits name_offsets.push_back(0); name_offsets.push_back(0); name_offsets.push_back(0); name_offsets.push_back(0); name_offsets.push_back(0); // Insert the total length sentinel (corresponds to the next name string offset) name_offsets.push_back(0); // Sentinel for offset into used_nodes way_node_id_offsets.push_back(0); } /** * Processes the collected data and serializes it. * At this point nodes are still referenced by their OSM id. * * - Identify nodes of ways used in restrictions and maneuver overrides * - Filter nodes list to nodes that are referenced by ways * - Prepare edges and compute routing properties * - Prepare and validate restrictions and maneuver overrides * */ void ExtractionContainers::PrepareData(ScriptingEnvironment &scripting_environment, const std::string &name_file_name) { const auto restriction_ways = IdentifyRestrictionWays(); const auto maneuver_override_ways = IdentifyManeuverOverrideWays(); const auto traffic_signals = IdentifyTrafficSignals(); PrepareNodes(); PrepareEdges(scripting_environment); PrepareTrafficSignals(traffic_signals); PrepareManeuverOverrides(maneuver_override_ways); PrepareRestrictions(restriction_ways); WriteCharData(name_file_name); } void ExtractionContainers::WriteCharData(const std::string &file_name) { util::UnbufferedLog log; log << "writing street name index ... "; TIMER_START(write_index); files::writeNames(file_name, NameTable{NameTable::IndexedData( name_offsets.begin(), name_offsets.end(), name_char_data.begin())}); TIMER_STOP(write_index); log << "ok, after " << TIMER_SEC(write_index) << "s"; } void ExtractionContainers::PrepareNodes() { { util::UnbufferedLog log; log << "Sorting used nodes ... " << std::flush; TIMER_START(sorting_used_nodes); tbb::parallel_sort(used_node_id_list.begin(), used_node_id_list.end()); TIMER_STOP(sorting_used_nodes); log << "ok, after " << TIMER_SEC(sorting_used_nodes) << "s"; } { util::UnbufferedLog log; log << "Erasing duplicate nodes ... " << std::flush; TIMER_START(erasing_dups); auto new_end = std::unique(used_node_id_list.begin(), used_node_id_list.end()); used_node_id_list.resize(new_end - used_node_id_list.begin()); TIMER_STOP(erasing_dups); log << "ok, after " << TIMER_SEC(erasing_dups) << "s"; } { util::UnbufferedLog log; log << "Sorting all nodes ... " << std::flush; TIMER_START(sorting_nodes); tbb::parallel_sort(all_nodes_list.begin(), all_nodes_list.end(), [](const auto &left, const auto &right) { return left.node_id < right.node_id; }); TIMER_STOP(sorting_nodes); log << "ok, after " << TIMER_SEC(sorting_nodes) << "s"; } { util::UnbufferedLog log; log << "Building node id map ... " << std::flush; TIMER_START(id_map); auto node_iter = all_nodes_list.begin(); auto ref_iter = used_node_id_list.begin(); auto used_nodes_iter = used_node_id_list.begin(); const auto all_nodes_list_end = all_nodes_list.end(); const auto used_node_id_list_end = used_node_id_list.end(); // compute the intersection of nodes that were referenced and nodes we actually have while (node_iter != all_nodes_list_end && ref_iter != used_node_id_list_end) { if (node_iter->node_id < *ref_iter) { node_iter++; continue; } if (node_iter->node_id > *ref_iter) { ref_iter++; continue; } BOOST_ASSERT(node_iter->node_id == *ref_iter); *used_nodes_iter = *ref_iter; used_nodes_iter++; node_iter++; ref_iter++; } // Remove unused nodes and check maximal internal node id used_node_id_list.resize(std::distance(used_node_id_list.begin(), used_nodes_iter)); if (used_node_id_list.size() > std::numeric_limits::max()) { throw util::exception("There are too many nodes remaining after filtering, OSRM only " "supports 2^32 unique nodes, but there were " + std::to_string(used_node_id_list.size()) + SOURCE_REF); } max_internal_node_id = boost::numeric_cast(used_node_id_list.size()); TIMER_STOP(id_map); log << "ok, after " << TIMER_SEC(id_map) << "s"; } { util::UnbufferedLog log; log << "Confirming/Writing used nodes ... "; TIMER_START(write_nodes); // identify all used nodes by a merging step of two sorted lists auto node_iterator = all_nodes_list.begin(); auto node_id_iterator = used_node_id_list.begin(); const auto all_nodes_list_end = all_nodes_list.end(); for (const auto index : util::irange(0, used_node_id_list.size())) { boost::ignore_unused(index); BOOST_ASSERT(node_id_iterator != used_node_id_list.end()); BOOST_ASSERT(node_iterator != all_nodes_list_end); BOOST_ASSERT(*node_id_iterator >= node_iterator->node_id); while (*node_id_iterator > node_iterator->node_id && node_iterator != all_nodes_list_end) { ++node_iterator; } if (node_iterator == all_nodes_list_end || *node_id_iterator < node_iterator->node_id) { throw util::exception( "Invalid OSM data: Referenced non-existing node with ID " + std::to_string(static_cast(*node_id_iterator))); } BOOST_ASSERT(*node_id_iterator == node_iterator->node_id); ++node_id_iterator; used_nodes.emplace_back(*node_iterator++); } TIMER_STOP(write_nodes); log << "ok, after " << TIMER_SEC(write_nodes) << "s"; } { util::UnbufferedLog log; log << "Writing barrier nodes ... "; TIMER_START(write_nodes); for (const auto osm_id : barrier_nodes) { const auto node_id = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), osm_id); if (node_id != SPECIAL_NODEID) { used_barrier_nodes.emplace(node_id); } } log << "ok, after " << TIMER_SEC(write_nodes) << "s"; } util::Log() << "Processed " << max_internal_node_id << " nodes"; } void ExtractionContainers::PrepareEdges(ScriptingEnvironment &scripting_environment) { // Sort edges by start. { util::UnbufferedLog log; log << "Sorting edges by start ... " << std::flush; TIMER_START(sort_edges_by_start); tbb::parallel_sort(all_edges_list.begin(), all_edges_list.end(), CmpEdgeByOSMStartID()); TIMER_STOP(sort_edges_by_start); log << "ok, after " << TIMER_SEC(sort_edges_by_start) << "s"; } { util::UnbufferedLog log; log << "Setting start coords ... " << std::flush; TIMER_START(set_start_coords); // Traverse list of edges and nodes in parallel and set start coord auto node_iterator = all_nodes_list.begin(); auto edge_iterator = all_edges_list.begin(); const auto all_edges_list_end = all_edges_list.end(); const auto all_nodes_list_end = all_nodes_list.end(); while (edge_iterator != all_edges_list_end && node_iterator != all_nodes_list_end) { if (edge_iterator->result.osm_source_id < node_iterator->node_id) { util::Log(logDEBUG) << "Found invalid node reference " << edge_iterator->result.source; edge_iterator->result.source = SPECIAL_NODEID; ++edge_iterator; continue; } if (edge_iterator->result.osm_source_id > node_iterator->node_id) { node_iterator++; continue; } // remove loops if (edge_iterator->result.osm_source_id == edge_iterator->result.osm_target_id) { edge_iterator->result.source = SPECIAL_NODEID; edge_iterator->result.target = SPECIAL_NODEID; ++edge_iterator; continue; } BOOST_ASSERT(edge_iterator->result.osm_source_id == node_iterator->node_id); // assign new node id const auto node_id = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), node_iterator->node_id); BOOST_ASSERT(node_id != SPECIAL_NODEID); edge_iterator->result.source = node_id; edge_iterator->source_coordinate.lat = node_iterator->lat; edge_iterator->source_coordinate.lon = node_iterator->lon; ++edge_iterator; } // Remove all remaining edges. They are invalid because there are no corresponding nodes for // them. This happens when using osmosis with bbox or polygon to extract smaller areas. auto markSourcesInvalid = [](InternalExtractorEdge &edge) { util::Log(logDEBUG) << "Found invalid node reference " << edge.result.source; edge.result.source = SPECIAL_NODEID; edge.result.osm_source_id = SPECIAL_OSM_NODEID; }; std::for_each(edge_iterator, all_edges_list_end, markSourcesInvalid); TIMER_STOP(set_start_coords); log << "ok, after " << TIMER_SEC(set_start_coords) << "s"; } { // Sort Edges by target util::UnbufferedLog log; log << "Sorting edges by target ... " << std::flush; TIMER_START(sort_edges_by_target); tbb::parallel_sort(all_edges_list.begin(), all_edges_list.end(), CmpEdgeByOSMTargetID()); TIMER_STOP(sort_edges_by_target); log << "ok, after " << TIMER_SEC(sort_edges_by_target) << "s"; } { // Compute edge weights util::UnbufferedLog log; log << "Computing edge weights ... " << std::flush; TIMER_START(compute_weights); auto node_iterator = all_nodes_list.begin(); auto edge_iterator = all_edges_list.begin(); const auto all_edges_list_end_ = all_edges_list.end(); const auto all_nodes_list_end_ = all_nodes_list.end(); const auto weight_multiplier = scripting_environment.GetProfileProperties().GetWeightMultiplier(); while (edge_iterator != all_edges_list_end_ && node_iterator != all_nodes_list_end_) { // skip all invalid edges if (edge_iterator->result.source == SPECIAL_NODEID) { ++edge_iterator; continue; } if (edge_iterator->result.osm_target_id < node_iterator->node_id) { util::Log(logDEBUG) << "Found invalid node reference " << static_cast(edge_iterator->result.osm_target_id); edge_iterator->result.target = SPECIAL_NODEID; ++edge_iterator; continue; } if (edge_iterator->result.osm_target_id > node_iterator->node_id) { ++node_iterator; continue; } BOOST_ASSERT(edge_iterator->result.osm_target_id == node_iterator->node_id); BOOST_ASSERT(edge_iterator->source_coordinate.lat != util::FixedLatitude{std::numeric_limits::min()}); BOOST_ASSERT(edge_iterator->source_coordinate.lon != util::FixedLongitude{std::numeric_limits::min()}); util::Coordinate source_coord(edge_iterator->source_coordinate); util::Coordinate target_coord{node_iterator->lon, node_iterator->lat}; // flip source and target coordinates if segment is in backward direction only if (!edge_iterator->result.flags.forward && edge_iterator->result.flags.backward) std::swap(source_coord, target_coord); const auto distance = util::coordinate_calculation::greatCircleDistance(source_coord, target_coord); const auto weight = edge_iterator->weight_data(distance); const auto duration = edge_iterator->duration_data(distance); const auto accurate_distance = util::coordinate_calculation::greatCircleDistance(source_coord, target_coord); ExtractionSegment segment(source_coord, target_coord, distance, weight, duration, edge_iterator->result.flags); scripting_environment.ProcessSegment(segment); auto &edge = edge_iterator->result; edge.weight = std::max( {1}, to_alias(std::round(segment.weight * weight_multiplier))); edge.duration = std::max( {1}, to_alias(std::round(segment.duration * 10.))); edge.distance = to_alias(accurate_distance); // assign new node id const auto node_id = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), node_iterator->node_id); BOOST_ASSERT(node_id != SPECIAL_NODEID); edge.target = node_id; // orient edges consistently: source id < target id // important for multi-edge removal if (edge.source > edge.target) { std::swap(edge.source, edge.target); // std::swap does not work with bit-fields bool temp = edge.flags.forward; edge.flags.forward = edge.flags.backward; edge.flags.backward = temp; } ++edge_iterator; } // Remove all remaining edges. They are invalid because there are no corresponding nodes for // them. This happens when using osmosis with bbox or polygon to extract smaller areas. auto markTargetsInvalid = [](InternalExtractorEdge &edge) { util::Log(logDEBUG) << "Found invalid node reference " << edge.result.target; edge.result.target = SPECIAL_NODEID; }; std::for_each(edge_iterator, all_edges_list_end_, markTargetsInvalid); TIMER_STOP(compute_weights); log << "ok, after " << TIMER_SEC(compute_weights) << "s"; } // Sort edges by start. { util::UnbufferedLog log; log << "Sorting edges by renumbered start ... "; TIMER_START(sort_edges_by_renumbered_start); tbb::parallel_sort(all_edges_list.begin(), all_edges_list.end(), CmpEdgeByInternalSourceTargetAndName{ all_edges_annotation_data_list, name_char_data, name_offsets}); TIMER_STOP(sort_edges_by_renumbered_start); log << "ok, after " << TIMER_SEC(sort_edges_by_renumbered_start) << "s"; } BOOST_ASSERT(all_edges_list.size() > 0); for (std::size_t i = 0; i < all_edges_list.size();) { // only invalid edges left if (all_edges_list[i].result.source == SPECIAL_NODEID) { break; } // skip invalid edges if (all_edges_list[i].result.target == SPECIAL_NODEID) { ++i; continue; } std::size_t start_idx = i; NodeID source = all_edges_list[i].result.source; NodeID target = all_edges_list[i].result.target; auto min_forward = std::make_pair(MAXIMAL_EDGE_WEIGHT, MAXIMAL_EDGE_DURATION); auto min_backward = std::make_pair(MAXIMAL_EDGE_WEIGHT, MAXIMAL_EDGE_DURATION); std::size_t min_forward_idx = std::numeric_limits::max(); std::size_t min_backward_idx = std::numeric_limits::max(); // find minimal edge in both directions while (i < all_edges_list.size() && all_edges_list[i].result.source == source && all_edges_list[i].result.target == target) { const auto &result = all_edges_list[i].result; const auto value = std::make_pair(result.weight, result.duration); if (result.flags.forward && value < min_forward) { min_forward_idx = i; min_forward = value; } if (result.flags.backward && value < min_backward) { min_backward_idx = i; min_backward = value; } // this also increments the outer loop counter! i++; } BOOST_ASSERT(min_forward_idx == std::numeric_limits::max() || min_forward_idx < i); BOOST_ASSERT(min_backward_idx == std::numeric_limits::max() || min_backward_idx < i); BOOST_ASSERT(min_backward_idx != std::numeric_limits::max() || min_forward_idx != std::numeric_limits::max()); if (min_backward_idx == min_forward_idx) { all_edges_list[min_forward_idx].result.flags.is_split = false; all_edges_list[min_forward_idx].result.flags.forward = true; all_edges_list[min_forward_idx].result.flags.backward = true; } else { bool has_forward = min_forward_idx != std::numeric_limits::max(); bool has_backward = min_backward_idx != std::numeric_limits::max(); if (has_forward) { all_edges_list[min_forward_idx].result.flags.forward = true; all_edges_list[min_forward_idx].result.flags.backward = false; all_edges_list[min_forward_idx].result.flags.is_split = has_backward; } if (has_backward) { std::swap(all_edges_list[min_backward_idx].result.source, all_edges_list[min_backward_idx].result.target); all_edges_list[min_backward_idx].result.flags.forward = true; all_edges_list[min_backward_idx].result.flags.backward = false; all_edges_list[min_backward_idx].result.flags.is_split = has_forward; } } // invalidate all unused edges for (std::size_t j = start_idx; j < i; j++) { if (j == min_forward_idx || j == min_backward_idx) { continue; } all_edges_list[j].result.source = SPECIAL_NODEID; all_edges_list[j].result.target = SPECIAL_NODEID; } } all_nodes_list.clear(); // free all_nodes_list before allocation of used_edges all_nodes_list.shrink_to_fit(); used_edges.reserve(all_edges_list.size()); { util::UnbufferedLog log; log << "Writing used edges ... " << std::flush; TIMER_START(write_edges); // Traverse list of edges and nodes in parallel and set target coord for (const auto &edge : all_edges_list) { if (edge.result.source == SPECIAL_NODEID || edge.result.target == SPECIAL_NODEID) { continue; } // IMPORTANT: here, we're using slicing to only write the data from the base // class of NodeBasedEdgeWithOSM used_edges.push_back(edge.result); } if (used_edges.size() > std::numeric_limits::max()) { throw util::exception("There are too many edges, OSRM only supports 2^32" + SOURCE_REF); } TIMER_STOP(write_edges); log << "ok, after " << TIMER_SEC(write_edges) << "s"; log << " -- Processed " << used_edges.size() << " edges"; } } ExtractionContainers::ReferencedWays ExtractionContainers::IdentifyManeuverOverrideWays() { ReferencedWays maneuver_override_ways; // prepare for extracting source/destination nodes for all maneuvers util::UnbufferedLog log; log << "Collecting way information on " << external_maneuver_overrides_list.size() << " maneuver overrides..."; TIMER_START(identify_maneuver_override_ways); const auto mark_ids = [&](auto const &external_maneuver_override) { NodesOfWay dummy_segment{MAX_OSM_WAYID, {MAX_OSM_NODEID, MAX_OSM_NODEID}}; const auto &turn_path = external_maneuver_override.turn_path; maneuver_override_ways[turn_path.From()] = dummy_segment; maneuver_override_ways[turn_path.To()] = dummy_segment; if (external_maneuver_override.turn_path.Type() == TurnPathType::VIA_WAY_TURN_PATH) { const auto &way = turn_path.AsViaWayPath(); for (const auto &via : way.via) { maneuver_override_ways[via] = dummy_segment; } } }; // First, make an empty hashtable keyed by the ways referenced // by the maneuver overrides std::for_each( external_maneuver_overrides_list.begin(), external_maneuver_overrides_list.end(), mark_ids); const auto set_ids = [&](size_t way_list_idx, auto const &way_id) { auto itr = maneuver_override_ways.find(way_id); if (itr != maneuver_override_ways.end()) { auto node_start_itr = used_node_id_list.begin() + way_node_id_offsets[way_list_idx]; auto node_end_itr = used_node_id_list.begin() + way_node_id_offsets[way_list_idx + 1]; itr->second = NodesOfWay(way_id, std::vector(node_start_itr, node_end_itr)); } }; // Then, populate the values in that hashtable for only the ways // referenced util::for_each_indexed(ways_list, set_ids); TIMER_STOP(identify_maneuver_override_ways); log << "ok, after " << TIMER_SEC(identify_maneuver_override_ways) << "s"; return maneuver_override_ways; } void ExtractionContainers::PrepareTrafficSignals( const ExtractionContainers::ReferencedTrafficSignals &referenced_traffic_signals) { const auto &bidirectional_signal_nodes = referenced_traffic_signals.first; const auto &unidirectional_signal_segments = referenced_traffic_signals.second; util::UnbufferedLog log; log << "Preparing traffic light signals for " << bidirectional_signal_nodes.size() << " bidirectional, " << unidirectional_signal_segments.size() << " unidirectional nodes ..."; TIMER_START(prepare_traffic_signals); std::unordered_set bidirectional; std::unordered_set, boost::hash>> unidirectional; for (const auto &osm_node : bidirectional_signal_nodes) { const auto node_id = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), osm_node); if (node_id != SPECIAL_NODEID) { bidirectional.insert(node_id); } } for (const auto &to_from : unidirectional_signal_segments) { const auto to_node_id = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), to_from.first); const auto from_node_id = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), to_from.second); if (from_node_id != SPECIAL_NODEID && to_node_id != SPECIAL_NODEID) { unidirectional.insert({from_node_id, to_node_id}); } } internal_traffic_signals.bidirectional_nodes = std::move(bidirectional); internal_traffic_signals.unidirectional_segments = std::move(unidirectional); TIMER_STOP(prepare_traffic_signals); log << "ok, after " << TIMER_SEC(prepare_traffic_signals) << "s"; } void ExtractionContainers::PrepareManeuverOverrides(const ReferencedWays &maneuver_override_ways) { auto const osm_node_to_internal_nbn = [&](auto const osm_node) { auto internal = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), osm_node); if (internal == SPECIAL_NODEID) { util::Log(logDEBUG) << "Maneuver override references invalid node: " << osm_node; } return internal; }; const auto strings_to_turn_type_and_direction = [](const std::string &turn_string, const std::string &direction_string) { auto result = std::make_pair(guidance::TurnType::MaxTurnType, guidance::DirectionModifier::MaxDirectionModifier); if (turn_string == "uturn") { result.first = guidance::TurnType::Turn; result.second = guidance::DirectionModifier::UTurn; } else if (turn_string == "continue") { result.first = guidance::TurnType::Continue; } else if (turn_string == "turn") { result.first = guidance::TurnType::Turn; } else if (turn_string == "fork") { result.first = guidance::TurnType::Fork; } else if (turn_string == "suppress") { result.first = guidance::TurnType::Suppressed; } // Directions if (direction_string == "left") { result.second = guidance::DirectionModifier::Left; } else if (direction_string == "slight_left") { result.second = guidance::DirectionModifier::SlightLeft; } else if (direction_string == "sharp_left") { result.second = guidance::DirectionModifier::SharpLeft; } else if (direction_string == "sharp_right") { result.second = guidance::DirectionModifier::SharpRight; } else if (direction_string == "slight_right") { result.second = guidance::DirectionModifier::SlightRight; } else if (direction_string == "right") { result.second = guidance::DirectionModifier::Right; } else if (direction_string == "straight") { result.second = guidance::DirectionModifier::Straight; } return result; }; // Transform an InternalManeuverOverride (based on WayIDs) into an OSRM override (base on // NodeIDs). // Returns true on successful transformation, false in case of invalid references. // Later, the UnresolvedManeuverOverride will be converted into a final ManeuverOverride // once the edge-based-node IDs are generated by the edge-based-graph-factory const auto transform = [&](const auto &external_type, auto &internal_type) { if (external_type.turn_path.Type() == TurnPathType::VIA_WAY_TURN_PATH) { auto const &external = external_type.turn_path.AsViaWayPath(); internal_type.turn_path.node_or_way = get_via_way_path_from_OSM_ids(internal_type.Name(), maneuver_override_ways, external.from, external.to, external.via, osm_node_to_internal_nbn); } else { BOOST_ASSERT(external_type.turn_path.Type() == TurnPathType::VIA_NODE_TURN_PATH); auto const &external = external_type.turn_path.AsViaNodePath(); internal_type.turn_path.node_or_way = get_via_node_path_from_OSM_ids(internal_type.Name(), maneuver_override_ways, external.from, external.to, external.via, osm_node_to_internal_nbn); } internal_type.instruction_node = osm_node_to_internal_nbn(external_type.via_node), std::tie(internal_type.override_type, internal_type.direction) = strings_to_turn_type_and_direction(external_type.maneuver, external_type.direction); return internal_type.Valid(); }; const auto transform_into_internal_types = [&](const InputManeuverOverride &external_maneuver_override) { UnresolvedManeuverOverride internal_maneuver_override; if (transform(external_maneuver_override, internal_maneuver_override)) internal_maneuver_overrides.push_back(std::move(internal_maneuver_override)); }; // Transforming the overrides into the dedicated internal types { util::UnbufferedLog log; log << "Collecting node information on " << external_maneuver_overrides_list.size() << " maneuver overrides..."; TIMER_START(transform); std::for_each(external_maneuver_overrides_list.begin(), external_maneuver_overrides_list.end(), transform_into_internal_types); TIMER_STOP(transform); log << "ok, after " << TIMER_SEC(transform) << "s"; } } ExtractionContainers::ReferencedWays ExtractionContainers::IdentifyRestrictionWays() { // Contains the nodes of each way that is part of a restriction ReferencedWays restriction_ways; // Prepare for extracting nodes for all restrictions util::UnbufferedLog log; log << "Collecting way information on " << restrictions_list.size() << " restrictions..."; TIMER_START(identify_restriction_ways); // Enter invalid IDs into the map to indicate that we want to find out about // nodes of these ways. const auto mark_ids = [&](auto const &turn_restriction) { NodesOfWay dummy_segment{MAX_OSM_WAYID, {MAX_OSM_NODEID, MAX_OSM_NODEID}}; const auto &turn_path = turn_restriction.turn_path; restriction_ways[turn_path.From()] = dummy_segment; restriction_ways[turn_path.To()] = dummy_segment; if (turn_path.Type() == TurnPathType::VIA_WAY_TURN_PATH) { const auto &way = turn_path.AsViaWayPath(); for (const auto &via : way.via) { restriction_ways[via] = dummy_segment; } } }; std::for_each(restrictions_list.begin(), restrictions_list.end(), mark_ids); // Update the values for all ways already sporting SPECIAL_NODEID const auto set_ids = [&](const size_t way_list_idx, auto const &way_id) { auto itr = restriction_ways.find(way_id); if (itr != restriction_ways.end()) { const auto node_start_offset = used_node_id_list.begin() + way_node_id_offsets[way_list_idx]; const auto node_end_offset = used_node_id_list.begin() + way_node_id_offsets[way_list_idx + 1]; itr->second = NodesOfWay(way_id, std::vector(node_start_offset, node_end_offset)); } }; util::for_each_indexed(ways_list, set_ids); TIMER_STOP(identify_restriction_ways); log << "ok, after " << TIMER_SEC(identify_restriction_ways) << "s"; return restriction_ways; } ExtractionContainers::ReferencedTrafficSignals ExtractionContainers::IdentifyTrafficSignals() { util::UnbufferedLog log; log << "Collecting traffic signal information on " << external_traffic_signals.size() << " signals..."; TIMER_START(identify_traffic_signals); // Temporary store for nodes containing a unidirectional signal. std::unordered_map unidirectional_signals; // For each node that has a unidirectional traffic signal, we store the node(s) // that lead up to the signal. std::unordered_multimap signal_segments; std::unordered_set bidirectional_signals; const auto mark_signals = [&](auto const &traffic_signal) { if (traffic_signal.second == TrafficLightClass::DIRECTION_FORWARD || traffic_signal.second == TrafficLightClass::DIRECTION_REVERSE) { unidirectional_signals.insert({traffic_signal.first, traffic_signal.second}); } else { BOOST_ASSERT(traffic_signal.second == TrafficLightClass::DIRECTION_ALL); bidirectional_signals.insert(traffic_signal.first); } }; std::for_each(external_traffic_signals.begin(), external_traffic_signals.end(), mark_signals); // Extract all the segments that lead up to unidirectional traffic signals. const auto set_segments = [&](const size_t way_list_idx, auto const & /*unused*/) { const auto node_start_offset = used_node_id_list.begin() + way_node_id_offsets[way_list_idx]; const auto node_end_offset = used_node_id_list.begin() + way_node_id_offsets[way_list_idx + 1]; for (auto node_it = node_start_offset; node_it < node_end_offset; node_it++) { const auto sig = unidirectional_signals.find(*node_it); if (sig != unidirectional_signals.end()) { if (sig->second == TrafficLightClass::DIRECTION_FORWARD) { if (node_it != node_start_offset) { // Previous node leads to signal signal_segments.insert({*node_it, *(node_it - 1)}); } } else { BOOST_ASSERT(sig->second == TrafficLightClass::DIRECTION_REVERSE); if (node_it + 1 != node_end_offset) { // Next node leads to signal signal_segments.insert({*node_it, *(node_it + 1)}); } } } } }; util::for_each_indexed(ways_list.cbegin(), ways_list.cend(), set_segments); util::for_each_pair( signal_segments, [](const auto pair_a, const auto pair_b) { if (pair_a.first == pair_b.first) { // If a node is appearing multiple times in this map, then it's ambiguous. // The node is an intersection and the traffic direction is being use for multiple // ways. We can't be certain of the original intent. See: // https://wiki.openstreetmap.org/wiki/Key:traffic_signals:direction // OSRM will include the signal for all intersecting ways in the specified // direction, but let's flag this as a concern. util::Log(logWARNING) << "OSM node " << pair_a.first << " has a unidirectional traffic signal ambiguously applied to multiple ways"; } }); TIMER_STOP(identify_traffic_signals); log << "ok, after " << TIMER_SEC(identify_traffic_signals) << "s"; return {std::move(bidirectional_signals), std::move(signal_segments)}; } void ExtractionContainers::PrepareRestrictions(const ReferencedWays &restriction_ways) { auto const to_internal = [&](auto const osm_node) { auto internal = mapExternalToInternalNodeID( used_node_id_list.begin(), used_node_id_list.end(), osm_node); if (internal == SPECIAL_NODEID) { util::Log(logDEBUG) << "Restriction references invalid node: " << osm_node; } return internal; }; // Transform an OSMRestriction (based on WayIDs) into an OSRM restriction (base on NodeIDs). // Returns true on successful transformation, false in case of invalid references. const auto transform = [&](const auto &external_type, auto &internal_type) { if (external_type.turn_path.Type() == TurnPathType::VIA_WAY_TURN_PATH) { auto const &external = external_type.turn_path.AsViaWayPath(); internal_type.turn_path.node_or_way = get_via_way_path_from_OSM_ids(internal_type.Name(), restriction_ways, external.from, external.to, external.via, to_internal); } else { BOOST_ASSERT(external_type.turn_path.Type() == TurnPathType::VIA_NODE_TURN_PATH); auto const &external = external_type.turn_path.AsViaNodePath(); internal_type.turn_path.node_or_way = get_via_node_path_from_OSM_ids(internal_type.Name(), restriction_ways, external.from, external.to, external.via, to_internal); } internal_type.is_only = external_type.is_only; internal_type.condition = std::move(external_type.condition); return internal_type.Valid(); }; const auto transform_into_internal_types = [&](InputTurnRestriction &external_restriction) { TurnRestriction restriction; if (transform(external_restriction, restriction)) { turn_restrictions.push_back(std::move(restriction)); } }; // Transforming the restrictions into the dedicated internal types { util::UnbufferedLog log; log << "Collecting node information on " << restrictions_list.size() << " restrictions..."; TIMER_START(transform); std::for_each( restrictions_list.begin(), restrictions_list.end(), transform_into_internal_types); TIMER_STOP(transform); log << "ok, after " << TIMER_SEC(transform) << "s"; } } } // namespace osrm::extractor