From b8f7569e93b3b2044260ccaec5a4402f9d0d2f3c Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Fri, 29 Apr 2016 00:48:13 -0700 Subject: [PATCH] Implement arbitrary turn penalty file IO and integration (#2306) Closes #1830 --- .gitignore | 1 + ...traffic.feature => traffic_speeds.feature} | 2 +- features/car/traffic_turn_penalties.feature | 97 +++++++++++ features/step_definitions/data.js | 10 +- features/support/config.js | 9 +- features/support/data.js | 6 +- features/support/hooks.js | 10 +- .../testbot/traffic_turn_penalties.feature | 37 +++++ include/contractor/contractor.hpp | 1 + include/contractor/contractor_config.hpp | 1 + .../routing_algorithms/routing_base.hpp | 18 ++- .../extractor/compressed_edge_container.hpp | 2 + include/extractor/guidance/toolkit.hpp | 2 +- src/contractor/contractor.cpp | 152 +++++++++++++----- src/extractor/compressed_edge_container.cpp | 20 ++- src/extractor/edge_based_graph_factory.cpp | 34 +++- src/tools/contract.cpp | 4 + 17 files changed, 346 insertions(+), 60 deletions(-) rename features/car/{traffic.feature => traffic_speeds.feature} (96%) create mode 100644 features/car/traffic_turn_penalties.feature create mode 100644 features/testbot/traffic_turn_penalties.feature diff --git a/.gitignore b/.gitignore index dd46b9f0e..17e0757e9 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,7 @@ stxxl.errlog /test/profile.lua /test/cache /test/speeds.csv +/test/penalties.csv /test/data/monaco.* node_modules diff --git a/features/car/traffic.feature b/features/car/traffic_speeds.feature similarity index 96% rename from features/car/traffic.feature rename to features/car/traffic_speeds.feature index 6d0c62d6b..84335d0f8 100644 --- a/features/car/traffic.feature +++ b/features/car/traffic_speeds.feature @@ -31,7 +31,7 @@ Feature: Traffic - speeds 4,1,27 """ - Scenario: Weighting not based on raster sources + Scenario: Weighting based on speed file Given the profile "testbot" Given the extract extra arguments "--generate-edge-lookup" Given the contract extra arguments "--segment-speed-file speeds.csv" diff --git a/features/car/traffic_turn_penalties.feature b/features/car/traffic_turn_penalties.feature new file mode 100644 index 000000000..4e977a45c --- /dev/null +++ b/features/car/traffic_turn_penalties.feature @@ -0,0 +1,97 @@ +@routing @speed @traffic +Feature: Traffic - turn penalties + + Background: Evenly spaced grid with multiple intersections + Given the node map + | | a | | b | | + | c | d | e | f | g | + | | h | | i | | + | j | k | l | m | n | + | | o | | p | | + And the ways + | nodes | highway | + | ad | primary | + | cd | primary | + | de | primary | + | dhk | primary | + + | bf | primary | + | ef | primary | + | fg | primary | + | fim | primary | + + | jk | primary | + | kl | primary | + | ko | primary | + + | lm | primary | + | mn | primary | + | mp | primary | + And the profile "car" + And the extract extra arguments "--generate-edge-lookup" + + Scenario: Weighting not based on turn penalty file + When I route I should get + | from | to | route | speed | time | + | a | h | ad,dhk,dhk | 63 km/h | 11.5s +-1 | + # straight + | i | g | fim,fg,fg | 59 km/h | 12s +-1 | + # right + | a | e | ad,de,de | 57 km/h | 12.5s +-1 | + # left + | c | g | cd,de,ef,fg,fg | 63 km/h | 23s +-1 | + # double straight + | p | g | mp,fim,fg,fg | 61 km/h | 23.5s +-1 | + # straight-right + | a | l | ad,dhk,kl,kl | 60 km/h | 24s +-1 | + # straight-left + | l | e | kl,dhk,de,de | 59 km/h | 24.5s +-1 | + # double right + | g | n | fg,fim,mn,mn | 57 km/h | 25s +-1 | + # double left + + Scenario: Weighting based on turn penalty file + Given the turn penalty file + """ + 9,6,7,1.8 + 9,13,14,24.5 + 8,4,3,26 + 12,11,8,9 + 8,11,12,13 + 1,4,5,-0.2 + """ + And the contract extra arguments "--turn-penalty-file penalties.csv" + When I route I should get + | from | to | route | speed | time | + | a | h | ad,dhk,dhk | 63 km/h | 11.5s +-1 | + # straight + | i | g | fim,fg,fg | 55 km/h | 13s +-1 | + # right - ifg penalty + | a | e | ad,de,de | 64 km/h | 11s +-1 | + # left - faster because of negative ade penalty + | c | g | cd,de,ef,fg,fg | 63 km/h | 23s +-1 | + # double straight + | p | g | mp,fim,fg,fg | 59 km/h | 24.5s +-1 | + # straight-right - ifg penalty + | a | l | ad,de,ef,fim,lm,lm | 61 km/h | 35.5s +-1 | + # was straight-left - forced around by hkl penalty + | l | e | lm,fim,ef,ef | 57 km/h | 25s +-1 | + # double right - forced left by lkh penalty + | g | n | fg,fim,mn,mn | 30 km/h | 47.5s +-1 | + # double left - imn penalty + | j | c | jk,kl,lm,fim,ef,de,cd,cd | 60 km/h | 48s +-1 | + # double left - hdc penalty ever so slightly higher than imn; forces all the way around + + Scenario: Too-negative penalty clamps, but does not fail + Given the contract extra arguments "--turn-penalty-file penalties.csv" + And the profile "testbot" + And the turn penalty file + """ + 1,4,5,-10 + """ + When I route I should get + | from | to | route | time | + | a | d | ad,ad | 10s +-1 | + | a | e | ad,de,de | 10s +-1 | + | b | f | bf,bf | 10s +-1 | + | b | g | bf,fg,fg | 20s +-1 | diff --git a/features/step_definitions/data.js b/features/step_definitions/data.js index db0071cb6..c430a0274 100644 --- a/features/step_definitions/data.js +++ b/features/step_definitions/data.js @@ -10,13 +10,11 @@ module.exports = function () { }); this.Given(/^the extract extra arguments "(.*?)"$/, (args, callback) => { - this.setExtractArgs(args); - callback(); + this.setExtractArgs(args, callback); }); this.Given(/^the contract extra arguments "(.*?)"$/, (args, callback) => { - this.setContractArgs(args); - callback(); + this.setContractArgs(args, callback); }); this.Given(/^a grid size of (\d+) meters$/, (meters, callback) => { @@ -228,6 +226,10 @@ module.exports = function () { fs.writeFile(path.resolve(this.TEST_FOLDER, 'speeds.csv'), data, callback); }); + this.Given(/^the turn penalty file$/, (data, callback) => { + fs.writeFile(path.resolve(this.TEST_FOLDER, 'penalties.csv'), data, callback); + }); + this.Given(/^the data has been saved to disk$/, (callback) => { try { this.reprocess(callback); diff --git a/features/support/config.js b/features/support/config.js index cae73981c..794eedd68 100644 --- a/features/support/config.js +++ b/features/support/config.js @@ -105,11 +105,16 @@ module.exports = function () { } else cb(); }; - this.setExtractArgs = (args) => { + this.setExtractArgs = (args, callback) => { this.extractArgs = args; + this.forceExtract = true; + this.forceContract = true; + callback(); }; - this.setContractArgs = (args) => { + this.setContractArgs = (args, callback) => { this.contractArgs = args; + this.forceContract = true; + callback(); }; }; diff --git a/features/support/data.js b/features/support/data.js index 29e517093..d37e555d1 100644 --- a/features/support/data.js +++ b/features/support/data.js @@ -295,9 +295,10 @@ module.exports = function () { this.writeAndExtract((e) => { if (e) return callback(e); this.isContracted((isContracted) => { - var contractFn = isContracted ? noop : this.contractData; + var contractFn = (isContracted && !this.forceContract) ? noop : this.contractData; if (isContracted) this.log('Already contracted ' + this.osmData.contractedFile, 'preprocess'); contractFn((e) => { + this.forceContract = false; if (e) return callback(e); this.logPreprocessDone(); callback(); @@ -311,9 +312,10 @@ module.exports = function () { this.writeInputData((e) => { if (e) return callback(e); this.isExtracted((isExtracted) => { - var extractFn = isExtracted ? noop : this.extractData; + var extractFn = (isExtracted && !this.forceExtract) ? noop : this.extractData; if (isExtracted) this.log('Already extracted ' + this.osmData.extractedFile, 'preprocess'); extractFn((e) => { + this.forceExtract = false; callback(e); }); }); diff --git a/features/support/hooks.js b/features/support/hooks.js index f8c7a2f65..509026408 100644 --- a/features/support/hooks.js +++ b/features/support/hooks.js @@ -24,10 +24,12 @@ module.exports = function () { }); this.After((scenario, callback) => { - this.setExtractArgs(''); - this.setContractArgs(''); - if (this.loadMethod === 'directly' && !!this.OSRMLoader.loader) this.OSRMLoader.shutdown(callback); - else callback(); + this.setExtractArgs('', () => { + this.setContractArgs('', () => { + if (this.loadMethod === 'directly' && !!this.OSRMLoader.loader) this.OSRMLoader.shutdown(callback); + else callback(); + }); + }); }); this.Around('@stress', (scenario, callback) => { diff --git a/features/testbot/traffic_turn_penalties.feature b/features/testbot/traffic_turn_penalties.feature new file mode 100644 index 000000000..2d94122a4 --- /dev/null +++ b/features/testbot/traffic_turn_penalties.feature @@ -0,0 +1,37 @@ +@routing @speed @traffic +Feature: Traffic - turn penalties applied to turn onto which a phantom node snaps + + Background: Simple map with phantom nodes + Given the node map + | | 1 | | 2 | | 3 | | + | a | | b | | c | | d | + | | | | | | | | + | | | e | | f | | g | + And the ways + | nodes | highway | + | ab | primary | + | bc | primary | + | cd | primary | + + | be | primary | + | cf | primary | + | dg | primary | + And the profile "testbot" + # Since testbot doesn't have turn penalties, a penalty from file of 0 should produce a neutral effect + And the extract extra arguments "--generate-edge-lookup" + + Scenario: Weighting based on turn penalty file, with an extreme negative value -- clamps and does not fail + Given the turn penalty file + """ + 1,2,5,0 + 3,4,7,-20 + """ + And the contract extra arguments "--turn-penalty-file penalties.csv" + When I route I should get + | from | to | route | speed | time | + | a | e | ab,be,be | 36 km/h | 40s +-1 | + | 1 | e | ab,be,be | 36 km/h | 30s +-1 | + | b | f | bc,cf,cf | 36 km/h | 40s +-1 | + | 2 | f | bc,cf,cf | 36 km/h | 30s +-1 | + | c | g | cd,dg,dg | 71 km/h | 20s +-1 | + | 3 | g | cd,dg,dg | 54 km/h | 20s +-1 | diff --git a/include/contractor/contractor.hpp b/include/contractor/contractor.hpp index 7cfc3dc66..a188a86f9 100644 --- a/include/contractor/contractor.hpp +++ b/include/contractor/contractor.hpp @@ -84,6 +84,7 @@ class Contractor const std::string &edge_segment_lookup_path, const std::string &edge_penalty_path, const std::vector &segment_speed_path, + const std::vector &turn_penalty_path, const std::string &nodes_filename, const std::string &geometry_filename, const std::string &datasource_names_filename, diff --git a/include/contractor/contractor_config.hpp b/include/contractor/contractor_config.hpp index f557279ca..e820b3f09 100644 --- a/include/contractor/contractor_config.hpp +++ b/include/contractor/contractor_config.hpp @@ -81,6 +81,7 @@ struct ContractorConfig double core_factor; std::vector segment_speed_lookup_paths; + std::vector turn_penalty_lookup_paths; std::string datasource_indexes_path; std::string datasource_names_path; }; diff --git a/include/engine/routing_algorithms/routing_base.hpp b/include/engine/routing_algorithms/routing_base.hpp index 360eb987c..5dbb74b5e 100644 --- a/include/engine/routing_algorithms/routing_base.hpp +++ b/include/engine/routing_algorithms/routing_base.hpp @@ -301,8 +301,6 @@ template class BasicRoutingInterface auto total_weight = std::accumulate(weight_vector.begin(), weight_vector.end(), 0); BOOST_ASSERT(weight_vector.size() == id_vector.size()); - // ed.distance should be total_weight + penalties (turn, stop, etc) - BOOST_ASSERT(ed.distance >= total_weight); const bool is_first_segment = unpacked_path.empty(); const std::size_t start_index = @@ -350,7 +348,8 @@ template class BasicRoutingInterface start_index = id_vector.size() - phantom_node_pair.source_phantom.fwd_segment_position - 1; } - end_index = id_vector.size() - phantom_node_pair.target_phantom.fwd_segment_position - 1; + end_index = + id_vector.size() - phantom_node_pair.target_phantom.fwd_segment_position - 1; } else { @@ -396,8 +395,17 @@ template class BasicRoutingInterface // However the first segment duration needs to be adjusted to the fact that the source // phantom is in the middle of the segment. We do this by subtracting v--s from the // duration. - BOOST_ASSERT(unpacked_path.front().duration_until_turn >= source_weight); - unpacked_path.front().duration_until_turn -= source_weight; + + // Since it's possible duration_until_turn can be less than source_weight here if + // a negative enough turn penalty is used to modify this edge weight during + // osrm-contract, we clamp to 1 here so as not to return a negative duration + // for this segment. + + // TODO this creates a scenario where it's possible the duration from a phantom + // node to the first turn would be the same as from end to end of a segment, + // which is obviously incorrect and not ideal... + unpacked_path.front().duration_until_turn = + std::max(unpacked_path.front().duration_until_turn - source_weight, 0); } // there is no equivalent to a node-based node in an edge-expanded graph. diff --git a/include/extractor/compressed_edge_container.hpp b/include/extractor/compressed_edge_container.hpp index 662c18e5d..9623d539a 100644 --- a/include/extractor/compressed_edge_container.hpp +++ b/include/extractor/compressed_edge_container.hpp @@ -40,7 +40,9 @@ class CompressedEdgeContainer void SerializeInternalVector(const std::string &path) const; unsigned GetPositionForID(const EdgeID edge_id) const; const EdgeBucket &GetBucketReference(const EdgeID edge_id) const; + bool IsTrivial(const EdgeID edge_id) const; NodeID GetFirstEdgeTargetID(const EdgeID edge_id) const; + NodeID GetLastEdgeTargetID(const EdgeID edge_id) const; NodeID GetLastEdgeSourceID(const EdgeID edge_id) const; private: diff --git a/include/extractor/guidance/toolkit.hpp b/include/extractor/guidance/toolkit.hpp index a2d4520f9..26573a04d 100644 --- a/include/extractor/guidance/toolkit.hpp +++ b/include/extractor/guidance/toolkit.hpp @@ -103,7 +103,7 @@ getCoordinateFromCompressedRange(util::Coordinate current_coordinate, } } // namespace detail -// Finds a (potentially inteprolated) coordinate that is DESIRED_SEGMENT_LENGTH away +// Finds a (potentially interpolated) coordinate that is DESIRED_SEGMENT_LENGTH away // from the start of an edge inline util::Coordinate getRepresentativeCoordinate(const NodeID from_node, diff --git a/src/contractor/contractor.cpp b/src/contractor/contractor.cpp index 72b2d34d4..4ad69d83c 100644 --- a/src/contractor/contractor.cpp +++ b/src/contractor/contractor.cpp @@ -20,6 +20,7 @@ #include #include +#include #include @@ -29,6 +30,7 @@ #include #include #include +#include namespace std { @@ -40,6 +42,18 @@ template <> struct hash> return static_cast(k.first) ^ (static_cast(k.second) << 12); } }; + +template <> struct hash> +{ + std::size_t operator()(const std::tuple &k) const + { + std::size_t seed = 0; + boost::hash_combine(seed, static_cast(std::get<0>(k))); + boost::hash_combine(seed, static_cast(std::get<1>(k))); + boost::hash_combine(seed, static_cast(std::get<2>(k))); + return seed; + } +}; } namespace osrm @@ -71,9 +85,9 @@ int Contractor::Run() std::size_t max_edge_id = LoadEdgeExpandedGraph( config.edge_based_graph_path, edge_based_edge_list, config.edge_segment_lookup_path, - config.edge_penalty_path, config.segment_speed_lookup_paths, config.node_based_graph_path, - config.geometry_path, config.datasource_names_path, config.datasource_indexes_path, - config.rtree_leaf_path); + config.edge_penalty_path, config.segment_speed_lookup_paths, + config.turn_penalty_lookup_paths, config.node_based_graph_path, config.geometry_path, + config.datasource_names_path, config.datasource_indexes_path, config.rtree_leaf_path); // Contracting the edge-expanded graph @@ -129,6 +143,7 @@ std::size_t Contractor::LoadEdgeExpandedGraph( const std::string &edge_segment_lookup_filename, const std::string &edge_penalty_filename, const std::vector &segment_speed_filenames, + const std::vector &turn_penalty_filenames, const std::string &nodes_filename, const std::string &geometry_filename, const std::string &datasource_names_filename, @@ -139,11 +154,12 @@ std::size_t Contractor::LoadEdgeExpandedGraph( boost::filesystem::ifstream input_stream(edge_based_graph_filename, std::ios::binary); const bool update_edge_weights = !segment_speed_filenames.empty(); + const bool update_turn_penalties = !turn_penalty_filenames.empty(); boost::filesystem::ifstream edge_segment_input_stream; boost::filesystem::ifstream edge_fixed_penalties_input_stream; - if (update_edge_weights) + if (update_edge_weights || update_turn_penalties) { edge_segment_input_stream.open(edge_segment_lookup_filename, std::ios::binary); edge_fixed_penalties_input_stream.open(edge_penalty_filename, std::ios::binary); @@ -172,37 +188,74 @@ std::size_t Contractor::LoadEdgeExpandedGraph( std::unordered_map, std::pair> segment_speed_lookup; + std::unordered_map, std::pair> + turn_penalty_lookup; // If we update the edge weights, this file will hold the datasource information // for each segment std::vector m_geometry_datasource; - if (update_edge_weights) + if (update_edge_weights || update_turn_penalties) { - uint8_t file_id = 1; - for (auto segment_speed_filename : segment_speed_filenames) - { - util::SimpleLogger().Write() - << "Segment speed data supplied, will update edge weights from " - << segment_speed_filename; - io::CSVReader<3> csv_in(segment_speed_filename); - csv_in.set_header("from_node", "to_node", "speed"); - uint64_t from_node_id{}; - uint64_t to_node_id{}; - unsigned speed{}; - while (csv_in.read_row(from_node_id, to_node_id, speed)) - { - segment_speed_lookup[std::make_pair(OSMNodeID(from_node_id), - OSMNodeID(to_node_id))] = - std::make_pair(speed, file_id); - } - ++file_id; + std::uint8_t segment_file_id = 1; + std::uint8_t turn_file_id = 1; - // Check for overflow - if (file_id == 0) + if (update_edge_weights) + { + for (auto segment_speed_filename : segment_speed_filenames) { - throw util::exception( - "Sorry, there's a limit of 254 segment speed files, you supplied too many"); + util::SimpleLogger().Write() + << "Segment speed data supplied, will update edge weights from " + << segment_speed_filename; + io::CSVReader<3> csv_in(segment_speed_filename); + csv_in.set_header("from_node", "to_node", "speed"); + std::uint64_t from_node_id{}; + std::uint64_t to_node_id{}; + unsigned speed{}; + while (csv_in.read_row(from_node_id, to_node_id, speed)) + { + segment_speed_lookup[std::make_pair(OSMNodeID(from_node_id), + OSMNodeID(to_node_id))] = + std::make_pair(speed, segment_file_id); + } + ++segment_file_id; + + // Check for overflow + if (segment_file_id == 0) + { + throw util::exception( + "Sorry, there's a limit of 255 segment speed files; you supplied too many"); + } + } + } + + if (update_turn_penalties) + { + for (auto turn_penalty_filename : turn_penalty_filenames) + { + util::SimpleLogger().Write() + << "Turn penalty data supplied, will update turn penalties from " + << turn_penalty_filename; + io::CSVReader<4> csv_in(turn_penalty_filename); + csv_in.set_header("from_node", "via_node", "to_node", "penalty"); + uint64_t from_node_id{}; + uint64_t via_node_id{}; + uint64_t to_node_id{}; + double penalty{}; + while (csv_in.read_row(from_node_id, via_node_id, to_node_id, penalty)) + { + turn_penalty_lookup[std::make_tuple( + OSMNodeID(from_node_id), OSMNodeID(via_node_id), OSMNodeID(to_node_id))] = + std::make_pair(penalty, turn_file_id); + } + ++turn_file_id; + + // Check for overflow + if (turn_file_id == 0) + { + throw util::exception( + "Sorry, there's a limit of 255 turn penalty files; you supplied too many"); + } } } @@ -358,10 +411,9 @@ std::size_t Contractor::LoadEdgeExpandedGraph( u = &(internal_to_external_node_map [m_geometry_list[reverse_begin + rev_segment_position - 1] .node_id]); - v = &( - internal_to_external_node_map[m_geometry_list[reverse_begin + - rev_segment_position] - .node_id]); + v = &(internal_to_external_node_map + [m_geometry_list[reverse_begin + rev_segment_position] + .node_id]); } const double segment_length = util::coordinate_calculation::greatCircleDistance( @@ -412,8 +464,7 @@ std::size_t Contractor::LoadEdgeExpandedGraph( std::ofstream datasource_stream(datasource_indexes_filename, std::ios::binary); if (!datasource_stream) { - throw util::exception("Failed to open " + datasource_indexes_filename + - " for writing"); + throw util::exception("Failed to open " + datasource_indexes_filename + " for writing"); } auto number_of_datasource_entries = m_geometry_datasource.size(); datasource_stream.write(reinterpret_cast(&number_of_datasource_entries), @@ -429,8 +480,7 @@ std::size_t Contractor::LoadEdgeExpandedGraph( std::ofstream datasource_stream(datasource_names_filename, std::ios::binary); if (!datasource_stream) { - throw util::exception("Failed to open " + datasource_names_filename + - " for writing"); + throw util::exception("Failed to open " + datasource_names_filename + " for writing"); } datasource_stream << "lua profile" << std::endl; for (auto const &name : segment_speed_filenames) @@ -445,7 +495,7 @@ std::size_t Contractor::LoadEdgeExpandedGraph( { extractor::EdgeBasedEdge inbuffer; input_stream.read((char *)&inbuffer, sizeof(extractor::EdgeBasedEdge)); - if (update_edge_weights) + if (update_edge_weights || update_turn_penalties) { // Processing-time edge updates unsigned fixed_penalty; @@ -463,6 +513,7 @@ std::size_t Contractor::LoadEdgeExpandedGraph( OSMNodeID this_osm_node_id; double segment_length; int segment_weight; + int compressed_edge_nodes = static_cast(num_osm_nodes); --num_osm_nodes; for (; num_osm_nodes != 0; --num_osm_nodes) { @@ -494,7 +545,34 @@ std::size_t Contractor::LoadEdgeExpandedGraph( previous_osm_node_id = this_osm_node_id; } - inbuffer.weight = fixed_penalty + new_weight; + OSMNodeID from_id; + OSMNodeID via_id; + OSMNodeID to_id; + edge_fixed_penalties_input_stream.read(reinterpret_cast(&from_id), + sizeof(from_id)); + edge_fixed_penalties_input_stream.read(reinterpret_cast(&via_id), + sizeof(via_id)); + edge_fixed_penalties_input_stream.read(reinterpret_cast(&to_id), sizeof(to_id)); + + auto turn_iter = turn_penalty_lookup.find(std::make_tuple(from_id, via_id, to_id)); + if (turn_iter != turn_penalty_lookup.end()) + { + int new_turn_weight = static_cast(turn_iter->second.first * 10); + + if (new_turn_weight + new_weight < compressed_edge_nodes) + { + util::SimpleLogger().Write(logWARNING) + << "turn penalty " << turn_iter->second.first << " for turn " << from_id + << ", " << via_id << ", " << to_id + << " is too negative: clamping turn weight to " << compressed_edge_nodes; + } + + inbuffer.weight = std::max(new_turn_weight + new_weight, compressed_edge_nodes); + } + else + { + inbuffer.weight = fixed_penalty + new_weight; + } } edge_based_edge_list.emplace_back(std::move(inbuffer)); diff --git a/src/extractor/compressed_edge_container.cpp b/src/extractor/compressed_edge_container.cpp index 70b1f77cb..dff8eb285 100644 --- a/src/extractor/compressed_edge_container.cpp +++ b/src/extractor/compressed_edge_container.cpp @@ -250,16 +250,32 @@ CompressedEdgeContainer::GetBucketReference(const EdgeID edge_id) const return m_compressed_geometries.at(index); } +// Since all edges are technically in the compressed geometry container, +// regardless of whether a compressed edge actually contains multiple +// original segments, we use 'Trivial' here to describe compressed edges +// that only contain one original segment +bool CompressedEdgeContainer::IsTrivial(const EdgeID edge_id) const +{ + const auto &bucket = GetBucketReference(edge_id); + return bucket.size() == 1; +} + NodeID CompressedEdgeContainer::GetFirstEdgeTargetID(const EdgeID edge_id) const { const auto &bucket = GetBucketReference(edge_id); - BOOST_ASSERT(bucket.size() >= 2); + BOOST_ASSERT(bucket.size() >= 1); return bucket.front().node_id; } +NodeID CompressedEdgeContainer::GetLastEdgeTargetID(const EdgeID edge_id) const +{ + const auto &bucket = GetBucketReference(edge_id); + BOOST_ASSERT(bucket.size() >= 1); + return bucket.back().node_id; +} NodeID CompressedEdgeContainer::GetLastEdgeSourceID(const EdgeID edge_id) const { const auto &bucket = GetBucketReference(edge_id); - BOOST_ASSERT(bucket.size() >= 2); + BOOST_ASSERT(bucket.size() >= 1); return bucket[bucket.size() - 2].node_id; } } diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index f6193e617..42dca52d6 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -123,7 +123,8 @@ void EdgeBasedGraphFactory::InsertEdgeBasedNode(const NodeID node_u, const NodeI NodeID current_edge_source_coordinate_id = node_u; - const auto edge_id_to_segment_id = [](const NodeID edge_based_node_id) { + const auto edge_id_to_segment_id = [](const NodeID edge_based_node_id) + { if (edge_based_node_id == SPECIAL_NODEID) { return SegmentID{SPECIAL_SEGMENTID, false}; @@ -339,7 +340,6 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( auto possible_turns = turn_analysis.getTurns(node_u, edge_from_u); const NodeID node_v = m_node_based_graph->GetTarget(edge_from_u); - for (const auto turn : possible_turns) { const double turn_angle = turn.angle; @@ -434,6 +434,36 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( sizeof(target_node.weight)); 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)]; + + edge_penalty_file.write(reinterpret_cast(&from_node.node_id), + sizeof(from_node.node_id)); + edge_penalty_file.write(reinterpret_cast(&via_node.node_id), + sizeof(via_node.node_id)); + edge_penalty_file.write(reinterpret_cast(&to_node.node_id), + sizeof(to_node.node_id)); } } } diff --git a/src/tools/contract.cpp b/src/tools/contract.cpp index c9460db18..f743bec14 100644 --- a/src/tools/contract.cpp +++ b/src/tools/contract.cpp @@ -43,6 +43,10 @@ return_code parseArguments(int argc, char *argv[], contractor::ContractorConfig &contractor_config.segment_speed_lookup_paths) ->composing(), "Lookup files containing nodeA, nodeB, speed data to adjust edge weights")( + "turn-penalty-file", boost::program_options::value>( + &contractor_config.turn_penalty_lookup_paths) + ->composing(), + "Lookup files containing from_, to_, via_nodes, and turn penalties to adjust turn weights")( "level-cache,o", boost::program_options::value(&contractor_config.use_cached_priority) ->default_value(false), "Use .level file to retain the contaction level for each node from the last run.");