From a46d09e17265affebea052b7d8dde07fded0eabe Mon Sep 17 00:00:00 2001 From: Darafei Praliaskouski Date: Fri, 3 Jul 2026 13:10:45 +0400 Subject: [PATCH] fix: account for road width in map matching --- features/testbot/matching.feature | 30 +++++- .../contiguous_internalmem_datafacade.hpp | 14 +++ include/engine/datafacade/datafacade_base.hpp | 6 ++ include/engine/geospatial_query.hpp | 44 ++++++++- include/extractor/extraction_way.hpp | 2 + include/extractor/graph_compressor.hpp | 2 +- include/extractor/intersection/constants.hpp | 2 + include/extractor/node_based_edge.hpp | 28 ++++-- include/extractor/node_data_container.hpp | 29 ++++++ profiles/lib/guidance.lua | 70 ++++++++++++++ profiles/lib/measure.lua | 1 + profiles/testbot.lua | 11 +++ .../routing_algorithms/map_matching.cpp | 53 +++++++++-- src/extractor/extractor_callbacks.cpp | 43 +++++++-- src/extractor/graph_compressor.cpp | 69 +++++++++++--- src/extractor/scripting_environment_lua.cpp | 2 + src/util/fingerprint.cpp | 4 +- taginfo.json | 9 +- unit_tests/extractor/graph_compressor.cpp | 38 +++++++- .../extractor/intersection_analysis_tests.cpp | 4 +- unit_tests/util/static_rtree.cpp | 94 +++++++++++++++++++ 21 files changed, 507 insertions(+), 48 deletions(-) diff --git a/features/testbot/matching.feature b/features/testbot/matching.feature index 133bbab73a2..f87af429ad6 100644 --- a/features/testbot/matching.feature +++ b/features/testbot/matching.feature @@ -159,6 +159,35 @@ Feature: Basic Map Matching | trace | matchings | | afcde | abcde | + Scenario Outline: Testbot - Map matching accounts for road surface width from + Given a grid size of 4 meters + Given the node map + """ + a b c d + + + 1 2 3 4 + e f g h + """ + + And the ways + | nodes | oneway | lanes | width | width:carriageway | width:forward | width:backward | width:lanes | width:lanes:forward | width:lanes:backward | + | abcd | no | | | | | | | | | + | efgh | no | | | | | | | | | + + When I match I should get + | trace | matchings | + | 1234 | abcd | + + Examples: + | source | lanes | width | width_carriageway | width_forward | width_backward | width_lanes | width_lanes_forward | width_lanes_backward | + | width | | 26 | | | | | | | + | width:carriageway | | | 26 | | | | | | + | width:forward/backward | | | | 13 | 13 | | | | + | width:lanes | | | | | | 6.5\|6.5\|6.5\|6.5 | | | + | width:lanes directions | | | | | | | 13 | 13 | + | lanes fallback | 24 | | | | | | | | + Scenario: Testbot - Map matching with oneways Given a grid size of 10 meters Given the node map @@ -826,4 +855,3 @@ Feature: Basic Map Matching When I match I should get | trace | matchings | confidence | | abcd | abcd | 1 ~5% | - diff --git a/include/engine/datafacade/contiguous_internalmem_datafacade.hpp b/include/engine/datafacade/contiguous_internalmem_datafacade.hpp index 137beaafc6c..102f52a88c7 100644 --- a/include/engine/datafacade/contiguous_internalmem_datafacade.hpp +++ b/include/engine/datafacade/contiguous_internalmem_datafacade.hpp @@ -167,6 +167,7 @@ class ContiguousInternalMemoryDataFacadeBase : public BaseDataFacade util::vector_view m_turn_duration_penalties; extractor::SegmentDataView segment_data; extractor::EdgeBasedNodeDataView edge_based_node_data; + double m_max_road_half_width = 0.; std::optional turn_data; std::optional> m_lane_tuple_id_pairs; @@ -230,6 +231,7 @@ class ContiguousInternalMemoryDataFacadeBase : public BaseDataFacade new SharedGeospatialQuery(m_static_rtree, m_coordinate_list, *this)); edge_based_node_data = make_ebn_data_view(index, "/common/ebg_node_data"); + m_max_road_half_width = edge_based_node_data.ComputeMaxRoadHalfWidth(); if (isIndexed(index, "/common/turn_data")) { @@ -431,6 +433,18 @@ class ContiguousInternalMemoryDataFacadeBase : public BaseDataFacade return edge_based_node_data.GetClassData(edge_based_node_id); } + std::uint8_t GetNumberOfLanes(const NodeID edge_based_node_id) const override final + { + return edge_based_node_data.GetNumberOfLanes(edge_based_node_id); + } + + double GetRoadWidth(const NodeID edge_based_node_id) const override final + { + return edge_based_node_data.GetRoadWidth(edge_based_node_id); + } + + double GetMaxRoadHalfWidth() const override final { return m_max_road_half_width; } + bool ExcludeNode(const NodeID edge_based_node_id) const override final { return (edge_based_node_data.GetClassData(edge_based_node_id) & exclude_mask) > 0; diff --git a/include/engine/datafacade/datafacade_base.hpp b/include/engine/datafacade/datafacade_base.hpp index 00d9f9cf576..b476183c396 100644 --- a/include/engine/datafacade/datafacade_base.hpp +++ b/include/engine/datafacade/datafacade_base.hpp @@ -114,6 +114,12 @@ class BaseDataFacade virtual extractor::ClassData GetClassData(const NodeID edge_based_node_id) const = 0; + virtual std::uint8_t GetNumberOfLanes(const NodeID /*edge_based_node_id*/) const { return 0; } + + virtual double GetRoadWidth(const NodeID /*edge_based_node_id*/) const { return 0.; } + + virtual double GetMaxRoadHalfWidth() const { return 0.; } + virtual bool ExcludeNode(const NodeID edge_based_node_id) const = 0; virtual std::vector GetClasses(const extractor::ClassData class_data) const = 0; diff --git a/include/engine/geospatial_query.hpp b/include/engine/geospatial_query.hpp index a3d5a1de613..5adfe36d926 100644 --- a/include/engine/geospatial_query.hpp +++ b/include/engine/geospatial_query.hpp @@ -1,6 +1,7 @@ #ifndef GEOSPATIAL_QUERY_HPP #define GEOSPATIAL_QUERY_HPP +#include "extractor/intersection/constants.hpp" #include "engine/approach.hpp" #include "engine/bearing.hpp" #include "engine/phantom_node.hpp" @@ -57,12 +58,12 @@ template class GeospatialQuery { auto results = rtree.SearchInRange( input_coordinate, - max_distance, + max_distance + datafacade.GetMaxRoadHalfWidth(), [this, approach, &input_coordinate, &bearing_with_range, &use_all_edges, max_distance]( const CandidateSegment &segment) { auto invalidDistance = - CheckSegmentDistance(input_coordinate, segment, max_distance); + CheckSegmentDistance(input_coordinate, segment, max_distance, true); if (invalidDistance) { return std::make_pair(false, false); @@ -106,7 +107,7 @@ template class GeospatialQuery { return (num_results >= max_results) || (max_distance && max_distance != -1.0 && - CheckSegmentDistance(input_coordinate, segment, *max_distance)); + CheckSegmentDistance(input_coordinate, segment, *max_distance, false)); }); return MakePhantomNodes(input_coordinate, results); @@ -495,11 +496,44 @@ template class GeospatialQuery wsg84_coordinate); } + double GetRoadHalfWidth(const NodeID node_id) const + { + const double road_width = std::min(datafacade.GetRoadWidth(node_id), + extractor::intersection::MAX_ROAD_SURFACE_WIDTH); + if (road_width > 0.) + { + return 0.5 * road_width; + } + + return 0.5 * std::min(datafacade.GetNumberOfLanes(node_id) * + extractor::intersection::ASSUMED_LANE_WIDTH, + extractor::intersection::MAX_ROAD_SURFACE_WIDTH); + } + + double GetRoadHalfWidth(const CandidateSegment &segment) const + { + if (segment.data.forward_segment_id.enabled && + segment.data.forward_segment_id.id != SPECIAL_SEGMENTID) + { + return GetRoadHalfWidth(segment.data.forward_segment_id.id); + } + + if (segment.data.reverse_segment_id.enabled && + segment.data.reverse_segment_id.id != SPECIAL_SEGMENTID) + { + return GetRoadHalfWidth(segment.data.reverse_segment_id.id); + } + + return 0.; + } + bool CheckSegmentDistance(const Coordinate input_coordinate, const CandidateSegment &segment, - const double max_distance) const + const double max_distance, + const bool account_road_surface) const { - return GetSegmentDistance(input_coordinate, segment) > max_distance; + return GetSegmentDistance(input_coordinate, segment) > + max_distance + (account_road_surface ? GetRoadHalfWidth(segment) : 0.); } std::pair CheckSegmentExclude(const CandidateSegment &segment) const diff --git a/include/extractor/extraction_way.hpp b/include/extractor/extraction_way.hpp index 2f24bd0420b..baa1736fa97 100644 --- a/include/extractor/extraction_way.hpp +++ b/include/extractor/extraction_way.hpp @@ -51,6 +51,7 @@ struct ExtractionWay exits.clear(); turn_lanes_forward.clear(); turn_lanes_backward.clear(); + road_width = 0.; road_classification = RoadClassification(); forward_travel_mode = TRAVEL_MODE_INACCESSIBLE; backward_travel_mode = TRAVEL_MODE_INACCESSIBLE; @@ -110,6 +111,7 @@ struct ExtractionWay std::string exits; std::string turn_lanes_forward; std::string turn_lanes_backward; + double road_width; RoadClassification road_classification; TravelMode forward_travel_mode : 4; TravelMode backward_travel_mode : 4; diff --git a/include/extractor/graph_compressor.hpp b/include/extractor/graph_compressor.hpp index 3af6662779d..b43e25aab0d 100644 --- a/include/extractor/graph_compressor.hpp +++ b/include/extractor/graph_compressor.hpp @@ -24,7 +24,7 @@ class GraphCompressor std::vector &turn_restrictions, std::vector &maneuver_overrides, util::NodeBasedDynamicGraph &graph, - const std::vector &node_data_container, + std::vector &node_data_container, CompressedEdgeContainer &geometry_compressor); private: diff --git a/include/extractor/intersection/constants.hpp b/include/extractor/intersection/constants.hpp index 5e4ca33b20a..c23dab52466 100644 --- a/include/extractor/intersection/constants.hpp +++ b/include/extractor/intersection/constants.hpp @@ -22,6 +22,8 @@ const double constexpr PRIORITY_DISTINCTION_FACTOR = 1.75; // the lane width we assume for a single lane const auto constexpr ASSUMED_LANE_WIDTH = 3.25; +// cap pathological width tags and lane-derived widths when approximating a road surface +const auto constexpr MAX_ROAD_SURFACE_WIDTH = 80.; // how far apart can roads be at the most, when thinking about merging them? const auto constexpr MERGABLE_ANGLE_DIFFERENCE = 95.0; diff --git a/include/extractor/node_based_edge.hpp b/include/extractor/node_based_edge.hpp index f6388b4f841..748a987adb7 100644 --- a/include/extractor/node_based_edge.hpp +++ b/include/extractor/node_based_edge.hpp @@ -68,6 +68,8 @@ struct NodeBasedEdgeAnnotation StringViewID string_view_id; // 32 4 LaneDescriptionID lane_description_id; // 16 2 ClassData classes; // 8 1 + std::uint8_t number_of_lanes; // 8 1 + std::uint16_t road_width; // 16 2, centimeters TravelMode travel_mode : 4; // 4 bool is_left_hand_driving : 1; // 1 @@ -80,16 +82,26 @@ struct NodeBasedEdgeAnnotation other.is_left_hand_driving)); } + bool CanCompressWith(const NodeBasedEdgeAnnotation &other) const + { + return CanCombineWith(other) && road_width == other.road_width; + } + bool operator<(const NodeBasedEdgeAnnotation &other) const { - return ( - std::tie( - string_view_id, lane_description_id, classes, travel_mode, is_left_hand_driving) < - std::tie(other.string_view_id, - other.lane_description_id, - other.classes, - other.travel_mode, - other.is_left_hand_driving)); + return (std::tie(string_view_id, + lane_description_id, + classes, + number_of_lanes, + road_width, + travel_mode, + is_left_hand_driving) < std::tie(other.string_view_id, + other.lane_description_id, + other.classes, + other.number_of_lanes, + other.road_width, + other.travel_mode, + other.is_left_hand_driving)); } }; diff --git a/include/extractor/node_data_container.hpp b/include/extractor/node_data_container.hpp index b4a50efde24..e6a19d88942 100644 --- a/include/extractor/node_data_container.hpp +++ b/include/extractor/node_data_container.hpp @@ -3,6 +3,7 @@ #include "extractor/class_data.hpp" #include "extractor/edge_based_node.hpp" +#include "extractor/intersection/constants.hpp" #include "extractor/node_based_edge.hpp" #include "extractor/travel_mode.hpp" @@ -13,6 +14,8 @@ #include "util/typedefs.hpp" #include "util/vector_view.hpp" +#include + namespace osrm::extractor { @@ -89,6 +92,32 @@ template class EdgeBasedNodeDataContainerImpl return annotation_data[nodes[node_id].annotation_id].classes; } + std::uint8_t GetNumberOfLanes(const NodeID node_id) const + { + return annotation_data[nodes[node_id].annotation_id].number_of_lanes; + } + + double GetRoadWidth(const NodeID node_id) const + { + return annotation_data[nodes[node_id].annotation_id].road_width / 100.; + } + + double ComputeMaxRoadHalfWidth() const + { + double max_road_half_width = 0.; + for (const auto &annotation : annotation_data) + { + const double road_width = std::min(annotation.road_width / 100., + intersection::MAX_ROAD_SURFACE_WIDTH); + const double fallback_width = + std::min(annotation.number_of_lanes * intersection::ASSUMED_LANE_WIDTH, + intersection::MAX_ROAD_SURFACE_WIDTH); + max_road_half_width = + std::max(max_road_half_width, 0.5 * std::max(road_width, fallback_width)); + } + return max_road_half_width; + } + friend void serialization::read(storage::tar::FileReader &reader, const std::string &name, EdgeBasedNodeDataContainerImpl &ebn_data_container); diff --git a/profiles/lib/guidance.lua b/profiles/lib/guidance.lua index f0d43411a92..dc905cd1af9 100644 --- a/profiles/lib/guidance.lua +++ b/profiles/lib/guidance.lua @@ -1,5 +1,6 @@ local Tags = require('lib/tags') local Set = require('lib/set') +local Measure = require('lib/measure') local Guidance = {} @@ -77,6 +78,70 @@ local function to_number_uint(s) return nil end +local function first_width(way, keys) + for _, key in ipairs(keys) do + local width = Measure.get_max_width(way:get_value_by_key(key)) + if width and width > 0 then + return width + end + end +end + +local function sum_width_lanes(value) + if not value then + return nil + end + + local total = 0 + local found = false + for lane_width in (value .. '|'):gmatch("([^|]*)|") do + local width = Measure.get_max_width(lane_width) + if width and width > 0 then + total = total + width + found = true + end + end + + if found then + return total + end +end + +local function first_width_lanes(way, keys) + for _, key in ipairs(keys) do + local width = sum_width_lanes(way:get_value_by_key(key)) + if width and width > 0 then + return width + end + end +end + +function Guidance.get_road_width(way) + local width = first_width(way, { 'width:carriageway', 'width', 'est_width' }) + if width then + return width + end + + local forward_width = first_width(way, { 'width:forward' }) + local backward_width = first_width(way, { 'width:backward' }) + + if forward_width or backward_width then + return (forward_width or 0) + (backward_width or 0) + end + + forward_width = first_width_lanes(way, { 'width:lanes:forward' }) + backward_width = first_width_lanes(way, { 'width:lanes:backward' }) + + if forward_width or backward_width then + return (forward_width or 0) + (backward_width or 0) + end + + width = first_width_lanes(way, { 'width:lanes' }) + if width then + return width + end +end + function Guidance.set_classification (highway, result, input_way) if motorway_types[highway] then result.road_classification.motorway_class = true @@ -140,6 +205,11 @@ function Guidance.set_classification (highway, result, input_way) result.road_classification.num_lanes = total_count end end + + local road_width = Guidance.get_road_width(input_way) + if road_width then + result.road_width = road_width + end end -- returns forward,backward psv lane count diff --git a/profiles/lib/measure.lua b/profiles/lib/measure.lua index aad7035a67c..384f67aa306 100644 --- a/profiles/lib/measure.lua +++ b/profiles/lib/measure.lua @@ -1,4 +1,5 @@ local Sequence = require('lib/sequence') +local Set = require('lib/set') Measure = {} diff --git a/profiles/testbot.lua b/profiles/testbot.lua index aabc028a46c..c35d342662b 100644 --- a/profiles/testbot.lua +++ b/profiles/testbot.lua @@ -5,6 +5,7 @@ -- Secondary road: 18km/h = 18000m/3600s = 100m/20s -- Tertiary road: 12km/h = 12000m/3600s = 100m/30s TrafficSignal = require("lib/traffic_signal") +Guidance = require("lib/guidance") api_version = 4 @@ -55,6 +56,7 @@ function process_way (profile, way, result) local maxspeed_forward = tonumber(way:get_value_by_key( "maxspeed:forward")) local maxspeed_backward = tonumber(way:get_value_by_key( "maxspeed:backward")) local junction = way:get_value_by_key("junction") + local lanes = tonumber(way:get_value_by_key("lanes")) if name then result.name = name @@ -120,6 +122,15 @@ function process_way (profile, way, result) result.backward_classes["toll"] = true end + if lanes and lanes > 0 then + result.road_classification.num_lanes = lanes + end + + local road_width = Guidance.get_road_width(way) + if road_width then + result.road_width = road_width + end + if junction == 'roundabout' then result.roundabout = true end diff --git a/src/engine/routing_algorithms/map_matching.cpp b/src/engine/routing_algorithms/map_matching.cpp index 7253354c914..29792d67092 100644 --- a/src/engine/routing_algorithms/map_matching.cpp +++ b/src/engine/routing_algorithms/map_matching.cpp @@ -6,6 +6,8 @@ #include "engine/map_matching/matching_confidence.hpp" #include "engine/map_matching/sub_matching.hpp" +#include "extractor/intersection/constants.hpp" + #include "util/coordinate_calculation.hpp" #include "util/for_each_pair.hpp" @@ -61,6 +63,39 @@ inline void initializeHeap(SearchEngineData &eng engine_working_data.InitializeOrClearMapMatchingThreadLocalStorage(nodes_number, border_nodes_number); } + +template +double getDistanceOutsideRoadSurface(const DataFacade &facade, + const PhantomNodeWithDistance &candidate) +{ + const auto get_road_half_width = [&facade](const NodeID node_id) + { + const double road_width = + std::min(facade.GetRoadWidth(node_id), extractor::intersection::MAX_ROAD_SURFACE_WIDTH); + if (road_width > 0.) + { + return 0.5 * road_width; + } + + return 0.5 * std::min(facade.GetNumberOfLanes(node_id) * + extractor::intersection::ASSUMED_LANE_WIDTH, + extractor::intersection::MAX_ROAD_SURFACE_WIDTH); + }; + + const auto &phantom = candidate.phantom_node; + double road_half_width = 0.; + if (phantom.forward_segment_id.enabled && phantom.forward_segment_id.id != SPECIAL_SEGMENTID) + { + road_half_width = get_road_half_width(phantom.forward_segment_id.id); + } + else if (phantom.reverse_segment_id.enabled && + phantom.reverse_segment_id.id != SPECIAL_SEGMENTID) + { + road_half_width = get_road_half_width(phantom.reverse_segment_id.id); + } + + return std::max(0., candidate.distance - road_half_width); +} } // namespace template @@ -105,8 +140,10 @@ SubMatchingList mapMatching(SearchEngineData &engine_working_data, std::transform(candidates_list[t].begin(), candidates_list[t].end(), emission_log_probabilities[t].begin(), - [&](const PhantomNodeWithDistance &candidate) - { return default_emission_log_probability(candidate.distance); }); + [&](const PhantomNodeWithDistance &candidate) { + return default_emission_log_probability( + getDistanceOutsideRoadSurface(facade, candidate)); + }); } } else @@ -121,16 +158,20 @@ SubMatchingList mapMatching(SearchEngineData &engine_working_data, std::transform(candidates_list[t].begin(), candidates_list[t].end(), emission_log_probabilities[t].begin(), - [&emission_log_probability](const PhantomNodeWithDistance &candidate) - { return emission_log_probability(candidate.distance); }); + [&](const PhantomNodeWithDistance &candidate) { + return emission_log_probability( + getDistanceOutsideRoadSurface(facade, candidate)); + }); } else { std::transform(candidates_list[t].begin(), candidates_list[t].end(), emission_log_probabilities[t].begin(), - [&](const PhantomNodeWithDistance &candidate) - { return default_emission_log_probability(candidate.distance); }); + [&](const PhantomNodeWithDistance &candidate) { + return default_emission_log_probability( + getDistanceOutsideRoadSurface(facade, candidate)); + }); } } } diff --git a/src/extractor/extractor_callbacks.cpp b/src/extractor/extractor_callbacks.cpp index 79fc524c6dc..c5b18bcd516 100644 --- a/src/extractor/extractor_callbacks.cpp +++ b/src/extractor/extractor_callbacks.cpp @@ -17,6 +17,9 @@ #include "osrm/coordinate.hpp" +#include +#include +#include #include #include @@ -36,6 +39,20 @@ const ByEdgeOrByMeterValue::ValueByMeter ByEdgeOrByMeterValue::by_meter; namespace osrm::extractor { +namespace +{ +std::uint16_t encodeRoadWidth(const double road_width) +{ + if (!(road_width > 0.)) + { + return 0; + } + + return static_cast( + std::min(std::round(road_width * 100.), std::numeric_limits::max())); +} +} // namespace + ExtractorCallbacks::ExtractorCallbacks(ExtractionContainers &extraction_containers_, std::unordered_map &classes_map, LaneDescriptionMap &lane_description_map, @@ -400,11 +417,14 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti if (in_forward_direction) { // add (forward) segments or (forward,backward) for non-split edges in backward direction const auto annotation_data_id = external_memory.all_edges_annotation_data_list.size(); - external_memory.all_edges_annotation_data_list.push_back({forward_name_id, - turn_lane_id_forward, - forward_classes, - parsed_way.forward_travel_mode, - parsed_way.is_left_hand_driving}); + external_memory.all_edges_annotation_data_list.push_back( + {forward_name_id, + turn_lane_id_forward, + forward_classes, + road_classification.GetNumberOfLanes(), + encodeRoadWidth(parsed_way.road_width), + parsed_way.forward_travel_mode, + parsed_way.is_left_hand_driving}); util::for_each_pair(nodes, [&](const osmium::NodeRef &first_node, const osmium::NodeRef &last_node) { @@ -435,11 +455,14 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti if (in_backward_direction && (!in_forward_direction || split_edge)) { // add (backward) segments for split edges or not in forward direction const auto annotation_data_id = external_memory.all_edges_annotation_data_list.size(); - external_memory.all_edges_annotation_data_list.push_back({backward_name_id, - turn_lane_id_backward, - backward_classes, - parsed_way.backward_travel_mode, - parsed_way.is_left_hand_driving}); + external_memory.all_edges_annotation_data_list.push_back( + {backward_name_id, + turn_lane_id_backward, + backward_classes, + road_classification.GetNumberOfLanes(), + encodeRoadWidth(parsed_way.road_width), + parsed_way.backward_travel_mode, + parsed_way.is_left_hand_driving}); util::for_each_pair(nodes, [&](const osmium::NodeRef &first_node, const osmium::NodeRef &last_node) { diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp index 46708e106fb..be7a0a8df2f 100644 --- a/src/extractor/graph_compressor.cpp +++ b/src/extractor/graph_compressor.cpp @@ -2,6 +2,7 @@ #include "extractor/compressed_edge_container.hpp" #include "extractor/extraction_turn.hpp" +#include "extractor/intersection/constants.hpp" #include "extractor/restriction.hpp" #include "extractor/turn_path_compressor.hpp" @@ -11,7 +12,10 @@ #include "util/log.hpp" +#include #include +#include +#include #include namespace osrm::extractor @@ -19,11 +23,20 @@ namespace osrm::extractor static constexpr int SECOND_TO_DECISECOND = 10; +namespace +{ +std::uint16_t encodeRoadWidth(const double road_width) +{ + return static_cast( + std::min(std::round(road_width * 100.), std::numeric_limits::max())); +} +} // namespace + void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, std::vector &turn_restrictions, std::vector &maneuver_overrides, util::NodeBasedDynamicGraph &graph, - const std::vector &node_data_container, + std::vector &node_data_container, CompressedEdgeContainer &geometry_compressor) { const unsigned original_number_of_nodes = graph.GetNumberOfNodes(); @@ -156,11 +169,16 @@ void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, (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)) + fwd_annotation_data1.CanCompressWith(fwd_annotation_data2) && + rev_annotation_data1.CanCompressWith(rev_annotation_data2)) { BOOST_ASSERT(!(graph.GetEdgeData(forward_e1).reversed && graph.GetEdgeData(reverse_e1).reversed)); + + // 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; + /* * Remember Lane Data for compressed parts. This handles scenarios where lane-data * is only kept up until a traffic light. @@ -192,10 +210,43 @@ void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, // 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; + const auto selected_annotation = + node_data_container[back_annotation].lane_description_id == + INVALID_LANE_DESCRIPTIONID + ? front_annotation + : back_annotation; + + const auto front_number_of_lanes = + node_data_container[front_annotation].number_of_lanes; + const auto back_number_of_lanes = + node_data_container[back_annotation].number_of_lanes; + const auto number_of_lanes = + front_number_of_lanes == 0 + ? back_number_of_lanes + : (back_number_of_lanes == 0 + ? front_number_of_lanes + : std::min(front_number_of_lanes, back_number_of_lanes)); + const auto road_width = + front_number_of_lanes != back_number_of_lanes && + node_data_container[front_annotation].road_width == 0 && + node_data_container[back_annotation].road_width == 0 + ? encodeRoadWidth( + std::max(front_number_of_lanes, back_number_of_lanes) * + intersection::ASSUMED_LANE_WIDTH) + : node_data_container[selected_annotation].road_width; + + if (node_data_container[selected_annotation].number_of_lanes == + number_of_lanes && + node_data_container[selected_annotation].road_width == road_width) + { + return selected_annotation; + } + + auto combined_annotation = node_data_container[selected_annotation]; + combined_annotation.number_of_lanes = number_of_lanes; + combined_annotation.road_width = road_width; + node_data_container.push_back(combined_annotation); + return static_cast(node_data_container.size() - 1); }; graph.GetEdgeData(forward_e1).annotation_data = selectAnnotation( @@ -207,10 +258,6 @@ void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, graph.GetEdgeData(reverse_e2).annotation_data = selectAnnotation( rev_edge_data2.annotation_data, rev_edge_data1.annotation_data); - // 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; - // Get weights before graph is modified const auto forward_weight1 = fwd_edge_data1.weight; const auto forward_weight2 = fwd_edge_data2.weight; diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index f4f90d2ba7e..6c8bd94c41d 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -450,6 +450,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) &ExtractionWay::duration, "weight", &ExtractionWay::weight, + "road_width", + &ExtractionWay::road_width, "road_classification", &ExtractionWay::road_classification, "forward_classes", diff --git a/src/util/fingerprint.cpp b/src/util/fingerprint.cpp index 3b6853d244b..36fe2d4ef88 100644 --- a/src/util/fingerprint.cpp +++ b/src/util/fingerprint.cpp @@ -24,8 +24,8 @@ FingerPrint FingerPrint::GetValid() // 4 chars, 'O','S','R','N' - note the N instead of M, v1 of the fingerprint // used M, so we add one and use N to indicate the newer fingerprint magic number. // Bump this value if the fingerprint format ever changes. - // Changed to force incompatibility for updated packed storage layout (packed_osm_ids.hpp). - fingerprint.magic_number = {{'O', 'S', 'R', 'O'}}; + // Changed to force incompatibility for updated edge annotation layout. + fingerprint.magic_number = {{'O', 'S', 'R', 'P'}}; fingerprint.major_version = OSRM_VERSION_MAJOR; fingerprint.minor_version = OSRM_VERSION_MINOR; fingerprint.patch_version = OSRM_VERSION_PATCH; diff --git a/taginfo.json b/taginfo.json index 59ac8bcb119..44beebe3be6 100644 --- a/taginfo.json +++ b/taginfo.json @@ -259,7 +259,14 @@ {"key": "barrier", "value": "fence", "description": "Fences are barriers unless sensory=audible or sensory=audio"}, {"key": "sensory", "value": "audible", "object_types": ["node"], "description": "Audible fences deter livestock but do not block vehicles, bicycles, or pedestrians"}, {"key": "sensory", "value": "audio", "object_types": ["node"], "description": "Audible fences deter livestock but do not block vehicles, bicycles, or pedestrians"}, - {"key": "width", "description": "Penalties for narrow streets"}, + {"key": "width", "description": "Penalties for narrow streets and map matching on wide roads"}, + {"key": "est_width", "description": "Estimated road width for map matching on wide roads"}, + {"key": "width:carriageway", "description": "Carriageway width for map matching on wide roads"}, + {"key": "width:forward", "description": "Forward road width for map matching on wide roads"}, + {"key": "width:backward", "description": "Backward road width for map matching on wide roads"}, + {"key": "width:lanes", "description": "Lane widths for map matching on wide roads"}, + {"key": "width:lanes:forward", "description": "Forward lane widths for map matching on wide roads"}, + {"key": "width:lanes:backward", "description": "Backward lane widths for map matching on wide roads"}, {"key": "lanes", "description": "Penalties for shared single lane streets"}, {"key": "lanes:forward", "description": "Lanes in forward direction"}, {"key": "lanes:backward", "description": "Lanes in backward direction"}, diff --git a/unit_tests/extractor/graph_compressor.cpp b/unit_tests/extractor/graph_compressor.cpp index 783856f2d85..efca151814b 100644 --- a/unit_tests/extractor/graph_compressor.cpp +++ b/unit_tests/extractor/graph_compressor.cpp @@ -1,5 +1,6 @@ #include "extractor/graph_compressor.hpp" #include "extractor/compressed_edge_container.hpp" +#include "extractor/intersection/constants.hpp" #include "extractor/maneuver_override.hpp" #include "extractor/restriction.hpp" #include "util/node_based_graph.hpp" @@ -52,7 +53,7 @@ bool compatible(Graph const &graph, auto const &first_annotation = node_data_container[graph.GetEdgeData(first).annotation_data]; auto const &second_annotation = node_data_container[graph.GetEdgeData(second).annotation_data]; - return first_annotation.CanCombineWith(second_annotation); + return first_annotation.CanCompressWith(second_annotation); } } // namespace @@ -241,4 +242,39 @@ BOOST_AUTO_TEST_CASE(direction_changes) BOOST_CHECK(graph.FindEdge(1, 2) != SPECIAL_EDGEID); } +BOOST_AUTO_TEST_CASE(lane_count_changes_do_not_block_compression) +{ + // + // 0---1---2 + // + GraphCompressor compressor; + + std::vector restrictions; + CompressedEdgeContainer container; + std::vector annotations(2); + test::MockScriptingEnvironment scripting_environment; + std::vector maneuver_overrides; + + std::vector edges = { + MakeUnitEdge(0, 1), MakeUnitEdge(1, 0), MakeUnitEdge(1, 2), MakeUnitEdge(2, 1)}; + + annotations[0].number_of_lanes = 3; + annotations[1].number_of_lanes = 2; + edges[2].data.annotation_data = edges[3].data.annotation_data = 1; + + Graph graph(3, edges); + BOOST_CHECK(compatible(graph, annotations, 0, 2)); + + compressor.Compress( + scripting_environment, restrictions, maneuver_overrides, graph, annotations, container); + + BOOST_CHECK_EQUAL(graph.FindEdge(0, 1), SPECIAL_EDGEID); + BOOST_CHECK_EQUAL(graph.FindEdge(1, 2), SPECIAL_EDGEID); + const auto edge = graph.FindEdge(0, 2); + BOOST_REQUIRE(edge != SPECIAL_EDGEID); + BOOST_CHECK_EQUAL(annotations[graph.GetEdgeData(edge).annotation_data].number_of_lanes, 2); + BOOST_CHECK_EQUAL(annotations[graph.GetEdgeData(edge).annotation_data].road_width, + static_cast(3 * intersection::ASSUMED_LANE_WIDTH * 100.)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/unit_tests/extractor/intersection_analysis_tests.cpp b/unit_tests/extractor/intersection_analysis_tests.cpp index ba9a5bf1721..c2604d4a175 100644 --- a/unit_tests/extractor/intersection_analysis_tests.cpp +++ b/unit_tests/extractor/intersection_analysis_tests.cpp @@ -19,8 +19,8 @@ using Graph = util::NodeBasedDynamicGraph; BOOST_AUTO_TEST_CASE(simple_intersection_connectivity) { std::vector annotations{ - {EMPTY_STRINGVIEWID, 0, INVALID_CLASS_DATA, TRAVEL_MODE_DRIVING, false}, - {EMPTY_STRINGVIEWID, 1, INVALID_CLASS_DATA, TRAVEL_MODE_DRIVING, false}}; + {EMPTY_STRINGVIEWID, 0, INVALID_CLASS_DATA, 0, 0, TRAVEL_MODE_DRIVING, false}, + {EMPTY_STRINGVIEWID, 1, INVALID_CLASS_DATA, 0, 0, TRAVEL_MODE_DRIVING, false}}; std::vector restrictions{TurnRestriction{{ViaNodePath{0, 2, 1}}, false}}; CompressedEdgeContainer container; test::MockScriptingEnvironment scripting_environment; diff --git a/unit_tests/util/static_rtree.cpp b/unit_tests/util/static_rtree.cpp index 6803bb918d2..c3c4ad1565f 100644 --- a/unit_tests/util/static_rtree.cpp +++ b/unit_tests/util/static_rtree.cpp @@ -41,6 +41,40 @@ using TestStaticRTree = StaticRTree; using TestDataFacade = MockDataFacade; +struct RoadSurfaceDataFacade final + : MockBaseDataFacade, + MockAlgorithmDataFacade +{ + double GetRoadWidth(const NodeID id) const override final + { + return id < road_widths.size() ? road_widths[id] : 0.; + } + + std::uint8_t GetNumberOfLanes(const NodeID id) const override final + { + return id < lane_counts.size() ? lane_counts[id] : 0; + } + + double GetMaxRoadHalfWidth() const override final + { + double max_road_half_width = 0.; + for (const auto road_width : road_widths) + { + max_road_half_width = std::max(max_road_half_width, 0.5 * road_width); + } + for (const auto lane_count : lane_counts) + { + max_road_half_width = + std::max(max_road_half_width, + 0.5 * lane_count * extractor::intersection::ASSUMED_LANE_WIDTH); + } + return max_road_half_width; + } + + std::vector road_widths; + std::vector lane_counts; +}; + // Chosen by a fair W20 dice roll (this value is completely arbitrary) static const int32_t WORLD_MIN_LAT = -85 * COORDINATE_PRECISION; static const int32_t WORLD_MAX_LAT = 85 * COORDINATE_PRECISION; @@ -343,6 +377,66 @@ BOOST_AUTO_TEST_CASE(radius_regression_test) } } +BOOST_AUTO_TEST_CASE(radius_search_accounts_for_road_width) +{ + using Coord = std::pair; + using Edge = std::tuple; + GraphFixture fixture( + { + Coord(FloatLongitude{0.0}, FloatLatitude{0.0}), + Coord(FloatLongitude{0.001}, FloatLatitude{0.0}), + }, + {Edge(0, 1, true), Edge(1, 0, true)}); + + TemporaryFile tmp; + auto rtree = make_rtree(tmp.path, fixture); + RoadSurfaceDataFacade facade; + facade.road_widths.resize(2); + facade.road_widths[1] = 50.; + engine::GeospatialQuery query( + rtree, fixture.coords, facade); + + Coordinate input(FloatLongitude{0.0005}, FloatLatitude{0.0002}); + + { + auto results = query.NearestPhantomNodes( + input, osrm::engine::Approach::UNRESTRICTED, 0.01, std::nullopt, true); + BOOST_CHECK_EQUAL(results.size(), 1); + } + + { + auto results = query.NearestPhantomNodes( + input, osrm::engine::Approach::UNRESTRICTED, 1, 0.01, std::nullopt, true); + BOOST_CHECK_EQUAL(results.size(), 0); + } +} + +BOOST_AUTO_TEST_CASE(radius_search_falls_back_to_lane_count) +{ + using Coord = std::pair; + using Edge = std::tuple; + GraphFixture fixture( + { + Coord(FloatLongitude{0.0}, FloatLatitude{0.0}), + Coord(FloatLongitude{0.001}, FloatLatitude{0.0}), + }, + {Edge(0, 1, true), Edge(1, 0, true)}); + + TemporaryFile tmp; + auto rtree = make_rtree(tmp.path, fixture); + RoadSurfaceDataFacade facade; + facade.lane_counts.resize(2); + facade.lane_counts[1] = 16; + engine::GeospatialQuery query( + rtree, fixture.coords, facade); + + Coordinate input(FloatLongitude{0.0005}, FloatLatitude{0.0002}); + + auto results = query.NearestPhantomNodes( + input, osrm::engine::Approach::UNRESTRICTED, 0.01, std::nullopt, true); + BOOST_CHECK_EQUAL(results.size(), 1); +} + BOOST_AUTO_TEST_CASE(permissive_edge_snapping) { using Coord = std::pair;