diff --git a/CMakeLists.txt b/CMakeLists.txt index b957d5dce..ee97ccfdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,15 +59,16 @@ add_library(LOGGER OBJECT util/simple_logger.cpp) add_library(PHANTOMNODE OBJECT data_structures/phantom_node.cpp) add_library(EXCEPTION OBJECT util/osrm_exception.cpp) add_library(MERCATOR OBJECT util/mercator.cpp) +add_library(ANGLE OBJECT util/compute_angle.cpp) set(ExtractorSources extract.cpp ${ExtractorGlob}) add_executable(osrm-extract ${ExtractorSources} $ $ $ $ $ $ $) add_library(RESTRICTION OBJECT data_structures/restriction_map.cpp) -file(GLOB PrepareGlob contractor/*.cpp data_structures/hilbert_value.cpp util/compute_angle.cpp {RestrictionMapGlob}) +file(GLOB PrepareGlob contractor/*.cpp data_structures/hilbert_value.cpp {RestrictionMapGlob}) set(PrepareSources prepare.cpp ${PrepareGlob}) -add_executable(osrm-prepare ${PrepareSources} $ $ $ $ $ $ $ $) +add_executable(osrm-prepare ${PrepareSources} $ $ $ $ $ $ $ $ $) file(GLOB ServerGlob server/*.cpp) file(GLOB DescriptorGlob descriptors/*.cpp) @@ -93,7 +94,7 @@ set( add_library(COORDINATE OBJECT ${CoordinateGlob}) add_library(FINGERPRINT OBJECT util/fingerprint.cpp) add_library(GITDESCRIPTION OBJECT util/git_sha.cpp) -add_library(OSRM ${OSRMSources} $ $ $ $ $ $ $) +add_library(OSRM ${OSRMSources} $ $ $ $ $ $ $ $ $) add_dependencies(FINGERPRINT FingerPrintConfigure) add_executable(osrm-routed routed.cpp ${ServerGlob} $) diff --git a/algorithms/bayes_classifier.hpp b/algorithms/bayes_classifier.hpp new file mode 100644 index 000000000..3358144c7 --- /dev/null +++ b/algorithms/bayes_classifier.hpp @@ -0,0 +1,117 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef BAYES_CLASSIFIER_HPP +#define BAYES_CLASSIFIER_HPP + +#include + +#include + +struct NormalDistribution +{ + NormalDistribution(const double mean, const double standard_deviation) + : mean(mean), standard_deviation(standard_deviation) + { + } + + // FIXME implement log-probability version since its faster + double density_function(const double val) const + { + const double x = val - mean; + return 1.0 / (std::sqrt(2. * M_PI) * standard_deviation) * + std::exp(-x * x / (standard_deviation * standard_deviation)); + } + + double mean; + double standard_deviation; +}; + +struct LaplaceDistribution +{ + LaplaceDistribution(const double location, const double scale) + : location(location), scale(scale) + { + } + + // FIXME implement log-probability version since its faster + double density_function(const double val) const + { + const double x = std::abs(val - location); + return 1.0 / (2. * scale) * std::exp(-x / scale); + } + + double location; + double scale; +}; + +template +class BayesClassifier +{ + public: + enum class ClassLabel : unsigned + { + NEGATIVE, + POSITIVE + }; + using ClassificationT = std::pair; + + BayesClassifier(const PositiveDistributionT &positive_distribution, + const NegativeDistributionT &negative_distribution, + const double positive_apriori_probability) + : positive_distribution(positive_distribution), + negative_distribution(negative_distribution), + positive_apriori_probability(positive_apriori_probability), + negative_apriori_probability(1. - positive_apriori_probability) + { + } + + // Returns label and the probability of the label. + ClassificationT classify(const ValueT &v) const + { + const double positive_postpriori = + positive_apriori_probability * positive_distribution.density_function(v); + const double negative_postpriori = + negative_apriori_probability * negative_distribution.density_function(v); + const double norm = positive_postpriori + negative_postpriori; + + if (positive_postpriori > negative_postpriori) + { + return std::make_pair(ClassLabel::POSITIVE, positive_postpriori / norm); + } + + return std::make_pair(ClassLabel::NEGATIVE, negative_postpriori / norm); + } + + private: + PositiveDistributionT positive_distribution; + NegativeDistributionT negative_distribution; + double positive_apriori_probability; + double negative_apriori_probability; +}; + +#endif // BAYES_CLASSIFIER_HPP diff --git a/data_structures/hidden_markov_model.hpp b/data_structures/hidden_markov_model.hpp new file mode 100644 index 000000000..403f76608 --- /dev/null +++ b/data_structures/hidden_markov_model.hpp @@ -0,0 +1,154 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef HIDDEN_MARKOV_MODEL +#define HIDDEN_MARKOV_MODEL + +#include "../util/integer_range.hpp" + +#include + +#include + +#include +#include + +namespace osrm +{ +namespace matching +{ +static const double log_2_pi = std::log(2. * M_PI); +static const double IMPOSSIBLE_LOG_PROB = -std::numeric_limits::infinity(); +static const double MINIMAL_LOG_PROB = std::numeric_limits::lowest(); +static const std::size_t INVALID_STATE = std::numeric_limits::max(); +} // namespace matching +} // namespace osrm + +// closures to precompute log -> only simple floating point operations +struct EmissionLogProbability +{ + double sigma_z; + double log_sigma_z; + + EmissionLogProbability(const double sigma_z) : sigma_z(sigma_z), log_sigma_z(std::log(sigma_z)) + { + } + + double operator()(const double distance) const + { + return -0.5 * (osrm::matching::log_2_pi + (distance / sigma_z) * (distance / sigma_z)) - + log_sigma_z; + } +}; + +struct TransitionLogProbability +{ + double beta; + double log_beta; + TransitionLogProbability(const double beta) : beta(beta), log_beta(std::log(beta)) {} + + double operator()(const double d_t) const { return -log_beta - d_t / beta; } +}; + +template struct HiddenMarkovModel +{ + std::vector> viterbi; + std::vector>> parents; + std::vector> path_lengths; + std::vector> pruned; + std::vector breakage; + + const CandidateLists &candidates_list; + const EmissionLogProbability &emission_log_probability; + + HiddenMarkovModel(const CandidateLists &candidates_list, + const EmissionLogProbability &emission_log_probability) + : breakage(candidates_list.size()), candidates_list(candidates_list), + emission_log_probability(emission_log_probability) + { + for (const auto &l : candidates_list) + { + viterbi.emplace_back(l.size()); + parents.emplace_back(l.size()); + path_lengths.emplace_back(l.size()); + pruned.emplace_back(l.size()); + } + + clear(0); + } + + void clear(std::size_t initial_timestamp) + { + BOOST_ASSERT(viterbi.size() == parents.size() && parents.size() == path_lengths.size() && + path_lengths.size() == pruned.size() && pruned.size() == breakage.size()); + + for (const auto t : osrm::irange(initial_timestamp, viterbi.size())) + { + std::fill(viterbi[t].begin(), viterbi[t].end(), osrm::matching::IMPOSSIBLE_LOG_PROB); + std::fill(parents[t].begin(), parents[t].end(), std::make_pair(0u, 0u)); + std::fill(path_lengths[t].begin(), path_lengths[t].end(), 0); + std::fill(pruned[t].begin(), pruned[t].end(), true); + } + std::fill(breakage.begin() + initial_timestamp, breakage.end(), true); + } + + std::size_t initialize(std::size_t initial_timestamp) + { + BOOST_ASSERT(initial_timestamp < candidates_list.size()); + + do + { + for (const auto s : osrm::irange(0u, viterbi[initial_timestamp].size())) + { + viterbi[initial_timestamp][s] = + emission_log_probability(candidates_list[initial_timestamp][s].second); + parents[initial_timestamp][s] = std::make_pair(initial_timestamp, s); + pruned[initial_timestamp][s] = + viterbi[initial_timestamp][s] < osrm::matching::MINIMAL_LOG_PROB; + + breakage[initial_timestamp] = + breakage[initial_timestamp] && pruned[initial_timestamp][s]; + } + + ++initial_timestamp; + } while (breakage[initial_timestamp - 1]); + + if (initial_timestamp >= viterbi.size()) + { + return osrm::matching::INVALID_STATE; + } + + BOOST_ASSERT(initial_timestamp > 0); + --initial_timestamp; + + BOOST_ASSERT(breakage[initial_timestamp] == false); + + return initial_timestamp; + } +}; + +#endif // HIDDEN_MARKOV_MODEL diff --git a/data_structures/route_parameters.cpp b/data_structures/route_parameters.cpp index 9416a6d69..3b615e2c4 100644 --- a/data_structures/route_parameters.cpp +++ b/data_structures/route_parameters.cpp @@ -33,7 +33,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. RouteParameters::RouteParameters() : zoom_level(18), print_instructions(false), alternate_route(true), geometry(true), - compression(true), deprecatedAPI(false), uturn_default(false), check_sum(-1), num_results(1) + compression(true), deprecatedAPI(false), uturn_default(false), classify(false), + matching_beta(-1.0), gps_precision(-1.0), check_sum(-1), num_results(1) { } @@ -83,6 +84,12 @@ void RouteParameters::setInstructionFlag(const bool flag) { print_instructions = void RouteParameters::setService(const std::string &service_string) { service = service_string; } +void RouteParameters::setClassify(const bool flag) { classify = flag; } + +void RouteParameters::setMatchingBeta(const double beta) { matching_beta = beta; } + +void RouteParameters::setGPSPrecision(const double precision) { gps_precision = precision; } + void RouteParameters::setOutputFormat(const std::string &format) { output_format = format; } void RouteParameters::setJSONpParameter(const std::string ¶meter) @@ -99,6 +106,15 @@ void RouteParameters::addHint(const std::string &hint) } } +void RouteParameters::addTimestamp(const unsigned timestamp) +{ + timestamps.resize(coordinates.size()); + if (!timestamps.empty()) + { + timestamps.back() = timestamp; + } +} + void RouteParameters::setLanguage(const std::string &language_string) { language = language_string; diff --git a/data_structures/search_engine.hpp b/data_structures/search_engine.hpp index 24e550767..7e47b1f5d 100644 --- a/data_structures/search_engine.hpp +++ b/data_structures/search_engine.hpp @@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "search_engine_data.hpp" #include "../routing_algorithms/alternative_path.hpp" #include "../routing_algorithms/many_to_many.hpp" +#include "../routing_algorithms/map_matching.hpp" #include "../routing_algorithms/shortest_path.hpp" #include @@ -45,10 +46,14 @@ template class SearchEngine ShortestPathRouting shortest_path; AlternativeRouting alternative_path; ManyToManyRouting distance_table; + MapMatching map_matching; explicit SearchEngine(DataFacadeT *facade) - : facade(facade), shortest_path(facade, engine_working_data), - alternative_path(facade, engine_working_data), distance_table(facade, engine_working_data) + : facade(facade), + shortest_path(facade, engine_working_data), + alternative_path(facade, engine_working_data), + distance_table(facade, engine_working_data), + map_matching(facade, engine_working_data) { static_assert(!std::is_pointer::value, "don't instantiate with ptr type"); static_assert(std::is_object::value, diff --git a/data_structures/static_rtree.hpp b/data_structures/static_rtree.hpp index a0ce38a1e..f219a6475 100644 --- a/data_structures/static_rtree.hpp +++ b/data_structures/static_rtree.hpp @@ -642,10 +642,6 @@ class StaticRTree return result_coordinate.is_valid(); } - // implementation of the Hjaltason/Samet query [3], a BFS traversal of the tree - // - searches for k elements nearest elements - // - continues to find the k+1st element from a big component if k elements - // come from tiny components bool IncrementalFindPhantomNodeForCoordinate( const FixedPointCoordinate &input_coordinate, std::vector &result_phantom_node_vector, @@ -745,6 +741,7 @@ class StaticRTree // check if it is smaller than what we had before float current_ratio = 0.f; FixedPointCoordinate foot_point_coordinate_on_segment; + // const float current_perpendicular_distance = coordinate_calculation::perpendicular_distance_from_projected_coordinate( m_coordinate_list->at(current_segment.u), @@ -796,20 +793,30 @@ class StaticRTree return !result_phantom_node_vector.empty(); } - // implementation of the Hjaltason/Samet query [3], a BFS traversal of the tree + // Returns elements within max_distance. + // If the minium of elements could not be found in the search radius, widen + // it until the minimum can be satisfied. + // At the number of returned nodes is capped at the given maximum. bool IncrementalFindPhantomNodeForCoordinateWithDistance( const FixedPointCoordinate &input_coordinate, std::vector> &result_phantom_node_vector, - const unsigned number_of_results, - const unsigned max_checked_segments = 4 * LEAF_NODE_SIZE) + const double max_distance, + const unsigned min_number_of_phantom_nodes, + const unsigned max_number_of_phantom_nodes, + const unsigned max_checked_elements = 4 * LEAF_NODE_SIZE) { - std::vector min_found_distances(number_of_results, - std::numeric_limits::max()); + unsigned inspected_elements = 0; + unsigned number_of_elements_from_big_cc = 0; + unsigned number_of_elements_from_tiny_cc = 0; - unsigned number_of_results_found_in_big_cc = 0; - unsigned number_of_results_found_in_tiny_cc = 0; + unsigned pruned_elements = 0; - unsigned inspected_segments = 0; + std::pair projected_coordinate = { + mercator::lat2y(input_coordinate.lat / COORDINATE_PRECISION), + input_coordinate.lon / COORDINATE_PRECISION}; + + // upper bound pruning technique + upper_bound pruning_bound(max_number_of_phantom_nodes); // initialize queue with root element std::priority_queue traversal_queue; @@ -820,42 +827,43 @@ class StaticRTree const IncrementalQueryCandidate current_query_node = traversal_queue.top(); traversal_queue.pop(); - const float current_min_dist = min_found_distances[number_of_results - 1]; - - if (current_query_node.min_dist > current_min_dist) - { - continue; - } - - if (current_query_node.RepresentsTreeNode()) - { + if (current_query_node.node.template is()) + { // current object is a tree node const TreeNode ¤t_tree_node = current_query_node.node.template get(); if (current_tree_node.child_is_on_disk) { LeafNode current_leaf_node; LoadLeafFromDisk(current_tree_node.children[0], current_leaf_node); - // Add all objects from leaf into queue - for (uint32_t i = 0; i < current_leaf_node.object_count; ++i) + + // current object represents a block on disk + for (const auto i : osrm::irange(0u, current_leaf_node.object_count)) { const auto ¤t_edge = current_leaf_node.objects[i]; - const float current_perpendicular_distance = - coordinate_calculation::perpendicular_distance( + const float current_perpendicular_distance = coordinate_calculation:: + perpendicular_distance_from_projected_coordinate( m_coordinate_list->at(current_edge.u), - m_coordinate_list->at(current_edge.v), input_coordinate); + m_coordinate_list->at(current_edge.v), input_coordinate, + projected_coordinate); // distance must be non-negative - BOOST_ASSERT(0. <= current_perpendicular_distance); + BOOST_ASSERT(0.f <= current_perpendicular_distance); - if (current_perpendicular_distance < current_min_dist) + if (pruning_bound.get() >= current_perpendicular_distance || + current_edge.is_in_tiny_cc()) { + pruning_bound.insert(current_perpendicular_distance); traversal_queue.emplace(current_perpendicular_distance, current_edge); } + else + { + ++pruned_elements; + } } } else { - // for each child mbr - for (uint32_t i = 0; i < current_tree_node.child_count; ++i) + // for each child mbr get a lower bound and enqueue it + for (const auto i : osrm::irange(0u, current_tree_node.child_count)) { const int32_t child_id = current_tree_node.children[i]; const TreeNode &child_tree_node = m_search_tree[child_id]; @@ -863,95 +871,93 @@ class StaticRTree child_tree_node.minimum_bounding_rectangle; const float lower_bound_to_element = child_rectangle.GetMinDist(input_coordinate); + BOOST_ASSERT(0.f <= lower_bound_to_element); - // TODO - enough elements found, i.e. nearest distance > maximum distance? - // ie. some measure of 'confidence of accuracy' - - // check if it needs to be explored by mindist - if (lower_bound_to_element < current_min_dist) - { - traversal_queue.emplace(lower_bound_to_element, child_tree_node); - } + traversal_queue.emplace(lower_bound_to_element, child_tree_node); } - // SimpleLogger().Write(logDEBUG) << "added " << current_tree_node.child_count - // << " mbrs into queue of " << traversal_queue.size(); } } else - { - ++inspected_segments; + { // current object is a leaf node + ++inspected_elements; // inspecting an actual road segment const EdgeDataT ¤t_segment = current_query_node.node.template get(); - // don't collect too many results from small components - if (number_of_results_found_in_big_cc == number_of_results && - !current_segment.is_in_tiny_cc) - { - continue; - } - - // don't collect too many results from big components - if (number_of_results_found_in_tiny_cc == number_of_results && - current_segment.is_in_tiny_cc) + // continue searching for the first segment from a big component + if (number_of_elements_from_big_cc == 0 && + number_of_elements_from_tiny_cc >= max_number_of_phantom_nodes - 1 && + current_segment.is_in_tiny_cc()) { continue; } // check if it is smaller than what we had before - float current_ratio = 0.; + float current_ratio = 0.f; FixedPointCoordinate foot_point_coordinate_on_segment; + const float current_perpendicular_distance = - coordinate_calculation::perpendicular_distance( + coordinate_calculation::perpendicular_distance_from_projected_coordinate( m_coordinate_list->at(current_segment.u), m_coordinate_list->at(current_segment.v), input_coordinate, - foot_point_coordinate_on_segment, current_ratio); + projected_coordinate, foot_point_coordinate_on_segment, current_ratio); - BOOST_ASSERT(0. <= current_perpendicular_distance); - - if ((current_perpendicular_distance < current_min_dist) && - !osrm::epsilon_compare(current_perpendicular_distance, current_min_dist)) + if (number_of_elements_from_big_cc > 0 && + result_phantom_node_vector.size() >= min_number_of_phantom_nodes && + current_perpendicular_distance >= max_distance) { - // store phantom node in result vector - result_phantom_node_vector.emplace_back(current_segment, - foot_point_coordinate_on_segment, - current_perpendicular_distance); + traversal_queue = std::priority_queue{}; + continue; + } - // Hack to fix rounding errors and wandering via nodes. - FixUpRoundingIssue(input_coordinate, result_phantom_node_vector.back()); + // store phantom node in result vector + result_phantom_node_vector.emplace_back( + PhantomNode( + current_segment.forward_edge_based_node_id, + current_segment.reverse_edge_based_node_id, current_segment.name_id, + current_segment.forward_weight, current_segment.reverse_weight, + current_segment.forward_offset, current_segment.reverse_offset, + current_segment.packed_geometry_id, current_segment.component_id, + foot_point_coordinate_on_segment, current_segment.fwd_segment_position, + current_segment.forward_travel_mode, current_segment.backward_travel_mode), + current_perpendicular_distance); - // set forward and reverse weights on the phantom node - SetForwardAndReverseWeightsOnPhantomNode(current_segment, - result_phantom_node_vector.back()); + // Hack to fix rounding errors and wandering via nodes. + FixUpRoundingIssue(input_coordinate, result_phantom_node_vector.back().first); - // do we have results only in a small scc - if (current_segment.is_in_tiny_cc) - { - ++number_of_results_found_in_tiny_cc; - } - else - { - // found an element in a large component - min_found_distances[number_of_results_found_in_big_cc] = - current_perpendicular_distance; - ++number_of_results_found_in_big_cc; - // SimpleLogger().Write(logDEBUG) << std::setprecision(8) << - // foot_point_coordinate_on_segment << " at " << - // current_perpendicular_distance; - } + // set forward and reverse weights on the phantom node + SetForwardAndReverseWeightsOnPhantomNode(current_segment, + result_phantom_node_vector.back().first); + + // update counts on what we found from which result class + if (current_segment.is_in_tiny_cc()) + { // found an element in tiny component + ++number_of_elements_from_tiny_cc; + } + else + { // found an element in a big component + ++number_of_elements_from_big_cc; } } - // TODO add indicator to prune if maxdist > threshold - if (number_of_results == number_of_results_found_in_big_cc || - inspected_segments >= max_checked_segments) + // stop the search by flushing the queue + if ((result_phantom_node_vector.size() >= max_number_of_phantom_nodes && + number_of_elements_from_big_cc > 0) || + inspected_elements >= max_checked_elements) { - // SimpleLogger().Write(logDEBUG) << "flushing queue of " << traversal_queue.size() - // << " elements"; - // work-around for traversal_queue.clear(); traversal_queue = std::priority_queue{}; } } + // SimpleLogger().Write() << "result_phantom_node_vector.size(): " << + // result_phantom_node_vector.size(); + // SimpleLogger().Write() << "max_number_of_phantom_nodes: " << max_number_of_phantom_nodes; + // SimpleLogger().Write() << "number_of_elements_from_big_cc: " << + // number_of_elements_from_big_cc; + // SimpleLogger().Write() << "number_of_elements_from_tiny_cc: " << + // number_of_elements_from_tiny_cc; + // SimpleLogger().Write() << "inspected_elements: " << inspected_elements; + // SimpleLogger().Write() << "max_checked_elements: " << max_checked_elements; + // SimpleLogger().Write() << "pruned_elements: " << pruned_elements; return !result_phantom_node_vector.empty(); } diff --git a/features/options/routed/help.feature b/features/options/routed/help.feature index 4b89dcb24..c0fca4717 100644 --- a/features/options/routed/help.feature +++ b/features/options/routed/help.feature @@ -26,7 +26,8 @@ Feature: osrm-routed command line options: help And stdout should contain "--threads" And stdout should contain "--shared-memory" And stdout should contain "--max-table-size" - And stdout should contain 24 lines + And stdout should contain "--max-matching-size" + And stdout should contain 26 lines And it should exit with code 0 Scenario: osrm-routed - Help, short @@ -51,7 +52,8 @@ Feature: osrm-routed command line options: help And stdout should contain "--threads" And stdout should contain "--shared-memory" And stdout should contain "--max-table-size" - And stdout should contain 24 lines + And stdout should contain "--max-matching-size" + And stdout should contain 26 lines And it should exit with code 0 Scenario: osrm-routed - Help, long @@ -76,5 +78,6 @@ Feature: osrm-routed command line options: help And stdout should contain "--threads" And stdout should contain "--shared-memory" And stdout should contain "--max-table-size" - And stdout should contain 24 lines + And stdout should contain "--max-matching-size" + And stdout should contain 26 lines And it should exit with code 0 diff --git a/features/step_definitions/matching.rb b/features/step_definitions/matching.rb new file mode 100644 index 000000000..3f3181f64 --- /dev/null +++ b/features/step_definitions/matching.rb @@ -0,0 +1,96 @@ +When /^I match I should get$/ do |table| + reprocess + actual = [] + OSRMLoader.load(self,"#{prepared_file}.osrm") do + table.hashes.each_with_index do |row,ri| + if row['request'] + got = {'request' => row['request'] } + response = request_url row['request'] + else + params = @query_params + trace = [] + timestamps = [] + if row['trace'] + row['trace'].each_char do |n| + node = find_node_by_name(n.strip) + raise "*** unknown waypoint node '#{n.strip}" unless node + trace << node + end + if row['timestamps'] + timestamps = row['timestamps'].split(" ").compact.map { |t| t.to_i} + end + got = {'trace' => row['trace'] } + response = request_matching trace, timestamps, params + else + raise "*** no trace" + end + end + + row.each_pair do |k,v| + if k =~ /param:(.*)/ + if v=='(nil)' + params[$1]=nil + elsif v!=nil + params[$1]=v + end + got[k]=v + end + end + + if response.body.empty? == false + json = JSON.parse response.body + end + + if table.headers.include? 'status' + got['status'] = json['status'].to_s + end + if table.headers.include? 'message' + got['message'] = json['status_message'] + end + if table.headers.include? '#' # comment column + got['#'] = row['#'] # copy value so it always match + end + + sub_matchings = [] + if response.code == "200" + if table.headers.include? 'matchings' + sub_matchings = json['matchings'].compact.map { |sub| sub['matched_points']} + end + end + + ok = true + encoded_result = "" + extended_target = "" + row['matchings'].split(',').each_with_index do |sub, sub_idx| + if sub_idx >= sub_matchings.length + ok = false + break + end + sub.length.times do |node_idx| + node = find_node_by_name(sub[node_idx]) + out_node = sub_matchings[sub_idx][node_idx] + if FuzzyMatch.match_location out_node, node + encoded_result += sub[node_idx] + extended_target += sub[node_idx] + else + encoded_result += "? [#{out_node[0]},#{out_node[1]}]" + extended_target += "#{sub[node_idx]} [#{node.lat},#{node.lon}]" + ok = false + end + end + end + if ok + got['matchings'] = row['matchings'] + got['timestamps'] = row['timestamps'] + else + got['matchings'] = encoded_result + row['matchings'] = extended_target + log_fail row,got, { 'matching' => {:query => @query, :response => response} } + end + + actual << got + end + end + table.routing_diff! actual +end + diff --git a/features/support/match.rb b/features/support/match.rb new file mode 100644 index 000000000..bf51189a4 --- /dev/null +++ b/features/support/match.rb @@ -0,0 +1,26 @@ +require 'net/http' + +HOST = "http://127.0.0.1:#{OSRM_PORT}" + +def request_matching trace=[], timestamps=[], options={} + defaults = { 'output' => 'json' } + locs = trace.compact.map { |w| "loc=#{w.lat},#{w.lon}" } + ts = timestamps.compact.map { |t| "t=#{t}" } + if ts.length > 0 + trace_params = locs.zip(ts).map { |a| a.join('&')} + else + trace_params = locs + end + params = (trace_params + defaults.merge(options).to_param).join('&') + params = nil if params=="" + uri = URI.parse ["#{HOST}/match", params].compact.join('?') + @query = uri.to_s + Timeout.timeout(OSRM_TIMEOUT) do + Net::HTTP.get_response uri + end +rescue Errno::ECONNREFUSED => e + raise "*** osrm-routed is not running." +rescue Timeout::Error + raise "*** osrm-routed did not respond." +end + diff --git a/features/testbot/matching.feature b/features/testbot/matching.feature new file mode 100644 index 000000000..47630e9b6 --- /dev/null +++ b/features/testbot/matching.feature @@ -0,0 +1,55 @@ +@match @testbot +Feature: Basic Map Matching + + Background: + Given the profile "testbot" + Given a grid size of 10 meters + + Scenario: Testbot - Map matching with trace splitting + Given the node map + | a | b | c | d | + | | | e | | + + And the ways + | nodes | oneway | + | abcd | no | + + When I match I should get + | trace | timestamps | matchings | + | abcd | 0 1 42 43 | ab,cd | + + Scenario: Testbot - Map matching with small distortion + Given the node map + | a | b | c | d | e | + | | f | | | | + | | | | | | + | | | | | | + | | | | | | + | | h | | | k | + + # The second way does not need to be a oneway + # but the grid spacing triggers the uturn + # detection on f + And the ways + | nodes | oneway | + | abcde | no | + | bfhke | yes | + + When I match I should get + | trace | matchings | + | afcde | abcde | + + Scenario: Testbot - Map matching with oneways + Given the node map + | a | b | c | d | + | e | f | g | h | + + And the ways + | nodes | oneway | + | abcd | yes | + | hgfe | yes | + + When I match I should get + | trace | matchings | + | dcba | hgfe | + diff --git a/include/osrm/libosrm_config.hpp b/include/osrm/libosrm_config.hpp index 777a0ccb9..500abf5bd 100644 --- a/include/osrm/libosrm_config.hpp +++ b/include/osrm/libosrm_config.hpp @@ -33,15 +33,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct libosrm_config { libosrm_config(const libosrm_config &) = delete; - libosrm_config() : max_locations_distance_table(100), use_shared_memory(false) {} + libosrm_config() + : max_locations_distance_table(100), max_locations_map_matching(-1), + use_shared_memory(false) + { + } - libosrm_config(const ServerPaths &paths, const bool flag, const int max) - : server_paths(paths), max_locations_distance_table(max), use_shared_memory(flag) + libosrm_config(const ServerPaths &paths, const bool flag, const int max_table, const int max_matching) + : server_paths(paths), max_locations_distance_table(max_table), + max_locations_map_matching(max_matching), use_shared_memory(flag) { } ServerPaths server_paths; int max_locations_distance_table; + int max_locations_map_matching; bool use_shared_memory; }; diff --git a/include/osrm/route_parameters.hpp b/include/osrm/route_parameters.hpp index 04538986e..9babbd763 100644 --- a/include/osrm/route_parameters.hpp +++ b/include/osrm/route_parameters.hpp @@ -49,6 +49,12 @@ struct RouteParameters void setAllUTurns(const bool flag); + void setClassify(const bool classify); + + void setMatchingBeta(const double beta); + + void setGPSPrecision(const double precision); + void setDeprecatedAPIFlag(const std::string &); void setChecksum(const unsigned check_sum); @@ -63,6 +69,8 @@ struct RouteParameters void addHint(const std::string &hint); + void addTimestamp(const unsigned timestamp); + void setLanguage(const std::string &language); void setGeometryFlag(const bool flag); @@ -78,6 +86,9 @@ struct RouteParameters bool compression; bool deprecatedAPI; bool uturn_default; + bool classify; + double matching_beta; + double gps_precision; unsigned check_sum; short num_results; std::string service; @@ -85,6 +96,7 @@ struct RouteParameters std::string jsonp_parameter; std::string language; std::vector hints; + std::vector timestamps; std::vector uturns; std::vector coordinates; }; diff --git a/library/osrm_impl.cpp b/library/osrm_impl.cpp index 71ea1fb1d..5bb58d174 100644 --- a/library/osrm_impl.cpp +++ b/library/osrm_impl.cpp @@ -42,6 +42,7 @@ class named_mutex; #include "../plugins/nearest.hpp" #include "../plugins/timestamp.hpp" #include "../plugins/viaroute.hpp" +#include "../plugins/match.hpp" #include "../server/data_structures/datafacade_base.hpp" #include "../server/data_structures/internal_datafacade.hpp" #include "../server/data_structures/shared_barriers.hpp" @@ -81,6 +82,8 @@ OSRM_impl::OSRM_impl(libosrm_config &lib_config) RegisterPlugin(new HelloWorldPlugin()); RegisterPlugin(new LocatePlugin>(query_data_facade)); RegisterPlugin(new NearestPlugin>(query_data_facade)); + RegisterPlugin(new MapMatchingPlugin>( + query_data_facade, lib_config.max_locations_map_matching)); RegisterPlugin(new TimestampPlugin>(query_data_facade)); RegisterPlugin(new ViaRoutePlugin>(query_data_facade)); } diff --git a/plugins/match.hpp b/plugins/match.hpp new file mode 100644 index 000000000..2a9fa043f --- /dev/null +++ b/plugins/match.hpp @@ -0,0 +1,315 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MATCH_HPP +#define MATCH_HPP + +#include "plugin_base.hpp" + +#include "../algorithms/bayes_classifier.hpp" +#include "../algorithms/object_encoder.hpp" +#include "../data_structures/search_engine.hpp" +#include "../descriptors/descriptor_base.hpp" +#include "../descriptors/json_descriptor.hpp" +#include "../routing_algorithms/map_matching.hpp" +#include "../util/compute_angle.hpp" +#include "../util/integer_range.hpp" +#include "../util/simple_logger.hpp" +#include "../util/json_logger.hpp" +#include "../util/json_util.hpp" +#include "../util/string_util.hpp" + +#include + +#include +#include +#include +#include + +template class MapMatchingPlugin : public BasePlugin +{ + constexpr static const unsigned max_number_of_candidates = 10; + + std::shared_ptr> search_engine_ptr; + + using ClassifierT = BayesClassifier; + using TraceClassification = ClassifierT::ClassificationT; + + public: + MapMatchingPlugin(DataFacadeT *facade, const int max_locations_map_matching) + : descriptor_string("match"), facade(facade), + max_locations_map_matching(max_locations_map_matching) + // the values where derived from fitting a laplace distribution + // to the values of manually classified traces + , + classifier(LaplaceDistribution(0.0057154021891018675, 0.020294704891166186), + LaplaceDistribution(0.11467696742821254, 0.49918444000368756), + 0.7977883096366508) // valid apriori probability + { + search_engine_ptr = std::make_shared>(facade); + } + + virtual ~MapMatchingPlugin() {} + + const std::string GetDescriptor() const final { return descriptor_string; } + + TraceClassification classify(float trace_length, float matched_length, int removed_points) const + { + double distance_feature = -std::log(trace_length) + std::log(matched_length); + + // matched to the same point + if (!std::isfinite(distance_feature)) + { + return std::make_pair(ClassifierT::ClassLabel::NEGATIVE, 1.0); + } + + auto label_with_confidence = classifier.classify(distance_feature); + + // "second stage classifier": if we need to remove points there is something fishy + if (removed_points > 0) + { + return std::make_pair(ClassifierT::ClassLabel::NEGATIVE, 1.0); + } + + return label_with_confidence; + } + + bool getCandiates(const std::vector &input_coords, + std::vector &sub_trace_lengths, + osrm::matching::CandidateLists &candidates_lists) + { + double last_distance = + coordinate_calculation::great_circle_distance(input_coords[0], input_coords[1]); + sub_trace_lengths.resize(input_coords.size()); + sub_trace_lengths[0] = 0; + for (const auto current_coordinate : osrm::irange(0, input_coords.size())) + { + bool allow_uturn = false; + if (0 < current_coordinate) + { + last_distance = coordinate_calculation::great_circle_distance( + input_coords[current_coordinate - 1], input_coords[current_coordinate]); + sub_trace_lengths[current_coordinate] += + sub_trace_lengths[current_coordinate - 1] + last_distance; + } + + if (input_coords.size() - 1 > current_coordinate && 0 < current_coordinate) + { + double turn_angle = ComputeAngle::OfThreeFixedPointCoordinates( + input_coords[current_coordinate - 1], input_coords[current_coordinate], + input_coords[current_coordinate + 1]); + + // sharp turns indicate a possible uturn + if (turn_angle <= 90.0 || turn_angle >= 270.0) + { + allow_uturn = true; + } + } + + std::vector> candidates; + if (!facade->IncrementalFindPhantomNodeForCoordinateWithMaxDistance( + input_coords[current_coordinate], candidates, last_distance / 2.0, 5, + max_number_of_candidates)) + { + return false; + } + + if (allow_uturn) + { + candidates_lists.push_back(candidates); + } + else + { + unsigned compact_size = candidates.size(); + for (const auto i : osrm::irange(0u, compact_size)) + { + // Split edge if it is bidirectional and append reverse direction to end of list + if (candidates[i].first.forward_node_id != SPECIAL_NODEID && + candidates[i].first.reverse_node_id != SPECIAL_NODEID) + { + PhantomNode reverse_node(candidates[i].first); + reverse_node.forward_node_id = SPECIAL_NODEID; + candidates.push_back(std::make_pair(reverse_node, candidates[i].second)); + + candidates[i].first.reverse_node_id = SPECIAL_NODEID; + } + } + candidates_lists.push_back(candidates); + } + } + + return true; + } + + osrm::json::Object submatchingToJSON(const osrm::matching::SubMatching &sub, + const RouteParameters &route_parameters, + const InternalRouteResult &raw_route) + { + osrm::json::Object subtrace; + + if (route_parameters.classify) + { + subtrace.values["confidence"] = sub.confidence; + } + + if (route_parameters.geometry) + { + DescriptionFactory factory; + FixedPointCoordinate current_coordinate; + factory.SetStartSegment(raw_route.segment_end_coordinates.front().source_phantom, + raw_route.source_traversed_in_reverse.front()); + for (const auto i : + osrm::irange(0, raw_route.unpacked_path_segments.size())) + { + for (const PathData &path_data : raw_route.unpacked_path_segments[i]) + { + current_coordinate = facade->GetCoordinateOfNode(path_data.node); + factory.AppendSegment(current_coordinate, path_data); + } + factory.SetEndSegment(raw_route.segment_end_coordinates[i].target_phantom, + raw_route.target_traversed_in_reverse[i], + raw_route.is_via_leg(i)); + } + subtrace.values["geometry"] = + factory.AppendGeometryString(route_parameters.compression); + } + + subtrace.values["indices"] = osrm::json::make_array(sub.indices); + + osrm::json::Array points; + for (const auto &node : sub.nodes) + { + points.values.emplace_back( + osrm::json::make_array(node.location.lat / COORDINATE_PRECISION, + node.location.lon / COORDINATE_PRECISION)); + } + subtrace.values["matched_points"] = points; + + return subtrace; + } + + int HandleRequest(const RouteParameters &route_parameters, + osrm::json::Object &json_result) final + { + // check number of parameters + if (!check_all_coordinates(route_parameters.coordinates)) + { + return 400; + } + + std::vector sub_trace_lengths; + osrm::matching::CandidateLists candidates_lists; + const auto &input_coords = route_parameters.coordinates; + const auto &input_timestamps = route_parameters.timestamps; + if (input_timestamps.size() > 0 && input_coords.size() != input_timestamps.size()) + { + return 400; + } + + // enforce maximum number of locations for performance reasons + if (max_locations_map_matching > 0 && + static_cast(input_coords.size()) < max_locations_map_matching) + { + return 400; + } + + bool found_candidates = getCandiates(input_coords, sub_trace_lengths, candidates_lists); + if (!found_candidates) + { + return 400; + } + + // setup logging if enabled + if (osrm::json::Logger::get()) + osrm::json::Logger::get()->initialize("matching"); + + // call the actual map matching + osrm::matching::SubMatchingList sub_matchings; + search_engine_ptr->map_matching(candidates_lists, input_coords, input_timestamps, + route_parameters.matching_beta, + route_parameters.gps_precision, sub_matchings); + + if (1 > sub_matchings.size()) + { + return 400; + } + + osrm::json::Array matchings; + for (auto &sub : sub_matchings) + { + // classify result + if (route_parameters.classify) + { + double trace_length = + sub_trace_lengths[sub.indices.back()] - sub_trace_lengths[sub.indices.front()]; + TraceClassification classification = + classify(trace_length, sub.length, + (sub.indices.back() - sub.indices.front() + 1) - sub.nodes.size()); + if (classification.first == ClassifierT::ClassLabel::POSITIVE) + { + sub.confidence = classification.second; + } + else + { + sub.confidence = 1 - classification.second; + } + } + + BOOST_ASSERT(sub.nodes.size() > 1); + + // FIXME we only run this to obtain the geometry + // The clean way would be to get this directly from the map matching plugin + InternalRouteResult raw_route; + PhantomNodes current_phantom_node_pair; + for (unsigned i = 0; i < sub.nodes.size() - 1; ++i) + { + current_phantom_node_pair.source_phantom = sub.nodes[i]; + current_phantom_node_pair.target_phantom = sub.nodes[i + 1]; + raw_route.segment_end_coordinates.emplace_back(current_phantom_node_pair); + } + search_engine_ptr->shortest_path( + raw_route.segment_end_coordinates, + std::vector(raw_route.segment_end_coordinates.size(), true), raw_route); + + matchings.values.emplace_back(submatchingToJSON(sub, route_parameters, raw_route)); + } + + if (osrm::json::Logger::get()) + osrm::json::Logger::get()->render("matching", json_result); + json_result.values["matchings"] = matchings; + + return 200; + } + + private: + std::string descriptor_string; + DataFacadeT *facade; + int max_locations_map_matching; + ClassifierT classifier; +}; + +#endif // MATCH_HPP diff --git a/routed.cpp b/routed.cpp index ce5691715..8b0e2e88b 100644 --- a/routed.cpp +++ b/routed.cpp @@ -77,7 +77,8 @@ int main(int argc, const char *argv[]) const unsigned init_result = GenerateServerProgramOptions( argc, argv, lib_config.server_paths, ip_address, ip_port, requested_thread_num, - lib_config.use_shared_memory, trial_run, lib_config.max_locations_distance_table); + lib_config.use_shared_memory, trial_run, lib_config.max_locations_distance_table, + lib_config.max_locations_map_matching); if (init_result == INIT_OK_DO_NOT_START_ENGINE) { return 0; diff --git a/routing_algorithms/map_matching.hpp b/routing_algorithms/map_matching.hpp new file mode 100644 index 000000000..80fe49cce --- /dev/null +++ b/routing_algorithms/map_matching.hpp @@ -0,0 +1,339 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MAP_MATCHING_HPP +#define MAP_MATCHING_HPP + +#include "routing_base.hpp" + +#include "../data_structures/coordinate_calculation.hpp" +#include "../data_structures/hidden_markov_model.hpp" +#include "../util/matching_debug_info.hpp" +#include "../util/json_logger.hpp" + +#include + +#include +#include +#include + +namespace osrm +{ +namespace matching +{ + +struct SubMatching +{ + std::vector nodes; + std::vector indices; + double length; + double confidence; +}; + +using CandidateList = std::vector>; +using CandidateLists = std::vector; +using HMM = HiddenMarkovModel; +using SubMatchingList = std::vector; + +constexpr static const unsigned MAX_BROKEN_STATES = 6; +constexpr static const unsigned MAX_BROKEN_TIME = 30; + +constexpr static const double default_beta = 10.0; +constexpr static const double default_sigma_z = 4.07; +} +} + +// implements a hidden markov model map matching algorithm +template +class MapMatching final : public BasicRoutingInterface> +{ + using super = BasicRoutingInterface>; + using QueryHeap = SearchEngineData::QueryHeap; + SearchEngineData &engine_working_data; + + public: + MapMatching(DataFacadeT *facade, SearchEngineData &engine_working_data) + : super(facade), engine_working_data(engine_working_data) + { + } + + void operator()(const osrm::matching::CandidateLists &candidates_list, + const std::vector &trace_coordinates, + const std::vector &trace_timestamps, + const double matching_beta, + const double gps_precision, + osrm::matching::SubMatchingList &sub_matchings) const + { + BOOST_ASSERT(!candidates_list.empty() && !trace_coordinates.empty()); + + // TODO replace default values with table lookup based on sampling frequency + EmissionLogProbability emission_log_probability( + gps_precision > 0. ? gps_precision : osrm::matching::default_sigma_z); + TransitionLogProbability transition_log_probability( + matching_beta > 0. ? matching_beta : osrm::matching::default_beta); + + osrm::matching::HMM model(candidates_list, emission_log_probability); + + std::size_t initial_timestamp = model.initialize(0); + if (initial_timestamp == osrm::matching::INVALID_STATE) + { + return; + } + + MatchingDebugInfo matching_debug(osrm::json::Logger::get()); + matching_debug.initialize(candidates_list); + + std::size_t breakage_begin = osrm::matching::INVALID_STATE; + std::vector split_points; + std::vector prev_unbroken_timestamps; + prev_unbroken_timestamps.reserve(candidates_list.size()); + prev_unbroken_timestamps.push_back(initial_timestamp); + for (auto t = initial_timestamp + 1; t < candidates_list.size(); ++t) + { + // breakage recover has removed all previous good points + bool trace_split = prev_unbroken_timestamps.empty(); + + // use temporal information if available to determine a split + if (!trace_timestamps.empty()) + { + trace_split = + trace_split || + (trace_timestamps[t] - trace_timestamps[prev_unbroken_timestamps.back()] > + osrm::matching::MAX_BROKEN_TIME); + } + else + { + trace_split = trace_split || (t - prev_unbroken_timestamps.back() > + osrm::matching::MAX_BROKEN_STATES); + } + + if (trace_split) + { + std::size_t split_index = t; + if (breakage_begin != osrm::matching::INVALID_STATE) + { + split_index = breakage_begin; + breakage_begin = osrm::matching::INVALID_STATE; + } + split_points.push_back(split_index); + + // note: this preserves everything before split_index + model.clear(split_index); + std::size_t new_start = model.initialize(split_index); + // no new start was found -> stop viterbi calculation + if (new_start == osrm::matching::INVALID_STATE) + { + break; + } + + prev_unbroken_timestamps.clear(); + prev_unbroken_timestamps.push_back(new_start); + // Important: We potentially go back here! + // However since t > new_start >= breakge_begin + // we can only reset trace_coordindates.size() times. + t = new_start + 1; + } + + BOOST_ASSERT(!prev_unbroken_timestamps.empty()); + const std::size_t prev_unbroken_timestamp = prev_unbroken_timestamps.back(); + + const auto &prev_viterbi = model.viterbi[prev_unbroken_timestamp]; + const auto &prev_pruned = model.pruned[prev_unbroken_timestamp]; + const auto &prev_unbroken_timestamps_list = candidates_list[prev_unbroken_timestamp]; + const auto &prev_coordinate = trace_coordinates[prev_unbroken_timestamp]; + + auto ¤t_viterbi = model.viterbi[t]; + auto ¤t_pruned = model.pruned[t]; + auto ¤t_parents = model.parents[t]; + auto ¤t_lengths = model.path_lengths[t]; + const auto ¤t_timestamps_list = candidates_list[t]; + const auto ¤t_coordinate = trace_coordinates[t]; + + engine_working_data.InitializeOrClearFirstThreadLocalStorage( + super::facade->GetNumberOfNodes()); + engine_working_data.InitializeOrClearSecondThreadLocalStorage( + super::facade->GetNumberOfNodes()); + + QueryHeap &forward_heap = *(engine_working_data.forward_heap_1); + QueryHeap &reverse_heap = *(engine_working_data.reverse_heap_1); + + // compute d_t for this timestamp and the next one + for (const auto s : osrm::irange(0u, prev_viterbi.size())) + { + if (prev_pruned[s]) + { + continue; + } + + for (const auto s_prime : osrm::irange(0u, current_viterbi.size())) + { + // how likely is candidate s_prime at time t to be emitted? + const double emission_pr = + emission_log_probability(candidates_list[t][s_prime].second); + double new_value = prev_viterbi[s] + emission_pr; + if (current_viterbi[s_prime] > new_value) + { + continue; + } + + forward_heap.Clear(); + reverse_heap.Clear(); + + // get distance diff between loc1/2 and locs/s_prime + const auto network_distance = super::get_network_distance( + forward_heap, reverse_heap, prev_unbroken_timestamps_list[s].first, + current_timestamps_list[s_prime].first); + const auto great_circle_distance = + coordinate_calculation::great_circle_distance(prev_coordinate, + current_coordinate); + + const auto d_t = std::abs(network_distance - great_circle_distance); + + // very low probability transition -> prune + if (d_t > 500) + { + continue; + } + + const double transition_pr = transition_log_probability(d_t); + new_value += transition_pr; + + matching_debug.add_transition_info(prev_unbroken_timestamp, t, s, s_prime, + prev_viterbi[s], emission_pr, transition_pr, + network_distance, great_circle_distance); + + if (new_value > current_viterbi[s_prime]) + { + current_viterbi[s_prime] = new_value; + current_parents[s_prime] = std::make_pair(prev_unbroken_timestamp, s); + current_lengths[s_prime] = network_distance; + current_pruned[s_prime] = false; + model.breakage[t] = false; + } + } + } + + if (model.breakage[t]) + { + // save start of breakage -> we need this as split point + if (t < breakage_begin) + { + breakage_begin = t; + } + + BOOST_ASSERT(prev_unbroken_timestamps.size() > 0); + // remove both ends of the breakage + prev_unbroken_timestamps.pop_back(); + } + else + { + prev_unbroken_timestamps.push_back(t); + } + } + + matching_debug.set_viterbi(model.viterbi, model.pruned); + + if (!prev_unbroken_timestamps.empty()) + { + split_points.push_back(prev_unbroken_timestamps.back() + 1); + } + + std::size_t sub_matching_begin = initial_timestamp; + for (const auto sub_matching_end : split_points) + { + osrm::matching::SubMatching matching; + + // find real end of trace + // not sure if this is really needed + std::size_t parent_timestamp_index = sub_matching_end - 1; + while (parent_timestamp_index >= sub_matching_begin && + model.breakage[parent_timestamp_index]) + { + --parent_timestamp_index; + } + + // matchings that only consist of one candidate are invalid + if (parent_timestamp_index - sub_matching_begin + 1 < 2) + { + sub_matching_begin = sub_matching_end; + continue; + } + + // loop through the columns, and only compare the last entry + const auto max_element_iter = + std::max_element(model.viterbi[parent_timestamp_index].begin(), + model.viterbi[parent_timestamp_index].end()); + + std::size_t parent_candidate_index = + std::distance(model.viterbi[parent_timestamp_index].begin(), max_element_iter); + + std::deque> reconstructed_indices; + while (parent_timestamp_index > sub_matching_begin) + { + if (model.breakage[parent_timestamp_index]) + { + continue; + } + + reconstructed_indices.emplace_front(parent_timestamp_index, parent_candidate_index); + const auto &next = model.parents[parent_timestamp_index][parent_candidate_index]; + parent_timestamp_index = next.first; + parent_candidate_index = next.second; + } + reconstructed_indices.emplace_front(parent_timestamp_index, parent_candidate_index); + if (reconstructed_indices.size() < 2) + { + sub_matching_begin = sub_matching_end; + continue; + } + + matching.length = 0.0f; + matching.nodes.resize(reconstructed_indices.size()); + matching.indices.resize(reconstructed_indices.size()); + for (const auto i : osrm::irange(0u, reconstructed_indices.size())) + { + auto timestamp_index = reconstructed_indices[i].first; + auto location_index = reconstructed_indices[i].second; + + matching.indices[i] = timestamp_index; + matching.nodes[i] = candidates_list[timestamp_index][location_index].first; + matching.length += model.path_lengths[timestamp_index][location_index]; + + matching_debug.add_chosen(timestamp_index, location_index); + } + + sub_matchings.push_back(matching); + sub_matching_begin = sub_matching_end; + } + matching_debug.add_breakage(model.breakage); + } +}; + +//[1] "Hidden Markov Map Matching Through Noise and Sparseness"; P. Newson and J. Krumm; 2009; ACM +// GIS + +#endif /* MAP_MATCHING_HPP */ diff --git a/routing_algorithms/routing_base.hpp b/routing_algorithms/routing_base.hpp index e10c86a59..52c16a77e 100644 --- a/routing_algorithms/routing_base.hpp +++ b/routing_algorithms/routing_base.hpp @@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ROUTING_BASE_HPP #define ROUTING_BASE_HPP +#include "../data_structures/coordinate_calculation.hpp" #include "../data_structures/internal_route_result.hpp" #include "../data_structures/search_engine_data.hpp" #include "../data_structures/turn_instructions.hpp" @@ -410,6 +411,84 @@ template class BasicRoutingInterface packed_path.emplace_back(current_node_id); } } + + double get_network_distance(SearchEngineData::QueryHeap &forward_heap, + SearchEngineData::QueryHeap &reverse_heap, + const PhantomNode &source_phantom, + const PhantomNode &target_phantom) const + { + EdgeWeight upper_bound = INVALID_EDGE_WEIGHT; + NodeID middle_node = SPECIAL_NODEID; + EdgeWeight edge_offset = std::min(0, -source_phantom.GetForwardWeightPlusOffset()); + edge_offset = std::min(edge_offset, -source_phantom.GetReverseWeightPlusOffset()); + + if (source_phantom.forward_node_id != SPECIAL_NODEID) + { + forward_heap.Insert(source_phantom.forward_node_id, + -source_phantom.GetForwardWeightPlusOffset(), + source_phantom.forward_node_id); + } + if (source_phantom.reverse_node_id != SPECIAL_NODEID) + { + forward_heap.Insert(source_phantom.reverse_node_id, + -source_phantom.GetReverseWeightPlusOffset(), + source_phantom.reverse_node_id); + } + + if (target_phantom.forward_node_id != SPECIAL_NODEID) + { + reverse_heap.Insert(target_phantom.forward_node_id, + target_phantom.GetForwardWeightPlusOffset(), + target_phantom.forward_node_id); + } + if (target_phantom.reverse_node_id != SPECIAL_NODEID) + { + reverse_heap.Insert(target_phantom.reverse_node_id, + target_phantom.GetReverseWeightPlusOffset(), + target_phantom.reverse_node_id); + } + + // search from s and t till new_min/(1+epsilon) > length_of_shortest_path + while (0 < (forward_heap.Size() + reverse_heap.Size())) + { + if (0 < forward_heap.Size()) + { + RoutingStep(forward_heap, reverse_heap, &middle_node, &upper_bound, edge_offset, + true); + } + if (0 < reverse_heap.Size()) + { + RoutingStep(reverse_heap, forward_heap, &middle_node, &upper_bound, edge_offset, + false); + } + } + + double distance = std::numeric_limits::max(); + if (upper_bound != INVALID_EDGE_WEIGHT) + { + std::vector packed_leg; + RetrievePackedPathFromHeap(forward_heap, reverse_heap, middle_node, packed_leg); + std::vector unpacked_path; + PhantomNodes nodes; + nodes.source_phantom = source_phantom; + nodes.target_phantom = target_phantom; + UnpackPath(packed_leg, nodes, unpacked_path); + + FixedPointCoordinate previous_coordinate = source_phantom.location; + FixedPointCoordinate current_coordinate; + distance = 0; + for (const auto &p : unpacked_path) + { + current_coordinate = facade->GetCoordinateOfNode(p.node); + distance += coordinate_calculation::great_circle_distance(previous_coordinate, + current_coordinate); + previous_coordinate = current_coordinate; + } + distance += coordinate_calculation::great_circle_distance(previous_coordinate, + target_phantom.location); + } + return distance; + } }; #endif // ROUTING_BASE_HPP diff --git a/server/api_grammar.hpp b/server/api_grammar.hpp index fecbb92cc..a629cfbf7 100644 --- a/server/api_grammar.hpp +++ b/server/api_grammar.hpp @@ -40,8 +40,9 @@ template struct APIGrammar : qi::grammar> string[boost::bind(&HandlerT::setService, handler, ::_1)] >> *(query) >> -(uturns); - query = ('?') >> (+(zoom | output | jsonp | checksum | location | hint | u | cmp | - language | instruction | geometry | alt_route | old_API | num_results)); + query = ('?') >> (+(zoom | output | jsonp | checksum | location | hint | timestamp | u | cmp | + language | instruction | geometry | alt_route | old_API | num_results | + matching_beta | gps_precision | classify)); zoom = (-qi::lit('&')) >> qi::lit('z') >> '=' >> qi::short_[boost::bind(&HandlerT::setZoomLevel, handler, ::_1)]; @@ -62,6 +63,8 @@ template struct APIGrammar : qi::grammar> qi::lit("hint") >> '=' >> stringwithDot[boost::bind(&HandlerT::addHint, handler, ::_1)]; + timestamp = (-qi::lit('&')) >> qi::lit("t") >> '=' >> + qi::uint_[boost::bind(&HandlerT::addTimestamp, handler, ::_1)]; u = (-qi::lit('&')) >> qi::lit("u") >> '=' >> qi::bool_[boost::bind(&HandlerT::setUTurn, handler, ::_1)]; uturns = (-qi::lit('&')) >> qi::lit("uturns") >> '=' >> @@ -74,6 +77,12 @@ template struct APIGrammar : qi::grammar> qi::lit("num_results") >> '=' >> qi::short_[boost::bind(&HandlerT::setNumberOfResults, handler, ::_1)]; + matching_beta = (-qi::lit('&')) >> qi::lit("matching_beta") >> '=' >> + qi::short_[boost::bind(&HandlerT::setMatchingBeta, handler, ::_1)]; + gps_precision = (-qi::lit('&')) >> qi::lit("gps_precision") >> '=' >> + qi::short_[boost::bind(&HandlerT::setGPSPrecision, handler, ::_1)]; + classify = (-qi::lit('&')) >> qi::lit("classify") >> '=' >> + qi::bool_[boost::bind(&HandlerT::setClassify, handler, ::_1)]; string = +(qi::char_("a-zA-Z")); stringwithDot = +(qi::char_("a-zA-Z0-9_.-")); @@ -83,8 +92,8 @@ template struct APIGrammar : qi::grammar api_call, query; qi::rule service, zoom, output, string, jsonp, checksum, location, - hint, stringwithDot, stringwithPercent, language, instruction, geometry, cmp, alt_route, u, - uturns, old_API, num_results; + hint, timestamp, stringwithDot, stringwithPercent, language, instruction, geometry, cmp, alt_route, u, + uturns, old_API, num_results, matching_beta, gps_precision, classify; HandlerT *handler; }; diff --git a/server/data_structures/datafacade_base.hpp b/server/data_structures/datafacade_base.hpp index 0a2d22025..04486532d 100644 --- a/server/data_structures/datafacade_base.hpp +++ b/server/data_structures/datafacade_base.hpp @@ -104,6 +104,12 @@ template class BaseDataFacade virtual bool IncrementalFindPhantomNodeForCoordinate(const FixedPointCoordinate &input_coordinate, PhantomNode &resulting_phantom_node) = 0; + virtual bool + IncrementalFindPhantomNodeForCoordinateWithMaxDistance(const FixedPointCoordinate &input_coordinate, + std::vector> &resulting_phantom_node_vector, + const double max_distance, + const unsigned min_number_of_phantom_nodes, + const unsigned max_number_of_phantom_nodes) = 0; virtual unsigned GetCheckSum() const = 0; diff --git a/server/data_structures/internal_datafacade.hpp b/server/data_structures/internal_datafacade.hpp index 746402418..640a5f974 100644 --- a/server/data_structures/internal_datafacade.hpp +++ b/server/data_structures/internal_datafacade.hpp @@ -398,6 +398,7 @@ template class InternalDataFacade final : public BaseDataFacad BOOST_ASSERT(!resulting_phantom_node_vector.empty()); resulting_phantom_node = resulting_phantom_node_vector.front(); } + return result; } @@ -415,6 +416,23 @@ template class InternalDataFacade final : public BaseDataFacad input_coordinate, resulting_phantom_node_vector, number_of_results); } + bool IncrementalFindPhantomNodeForCoordinateWithMaxDistance( + const FixedPointCoordinate &input_coordinate, + std::vector> &resulting_phantom_node_vector, + const double max_distance, + const unsigned min_number_of_phantom_nodes, + const unsigned max_number_of_phantom_nodes) override final + { + if (!m_static_rtree.get()) + { + LoadRTree(); + } + + return m_static_rtree->IncrementalFindPhantomNodeForCoordinateWithDistance( + input_coordinate, resulting_phantom_node_vector, max_distance, + min_number_of_phantom_nodes, max_number_of_phantom_nodes); + } + unsigned GetCheckSum() const override final { return m_check_sum; } unsigned GetNameIndexFromEdgeID(const unsigned id) const override final diff --git a/server/data_structures/shared_datafacade.hpp b/server/data_structures/shared_datafacade.hpp index fcec478b2..93dfdb95d 100644 --- a/server/data_structures/shared_datafacade.hpp +++ b/server/data_structures/shared_datafacade.hpp @@ -386,6 +386,7 @@ template class SharedDataFacade final : public BaseDataFacade< BOOST_ASSERT(!resulting_phantom_node_vector.empty()); resulting_phantom_node = resulting_phantom_node_vector.front(); } + return result; } @@ -403,6 +404,23 @@ template class SharedDataFacade final : public BaseDataFacade< input_coordinate, resulting_phantom_node_vector, number_of_results); } + bool IncrementalFindPhantomNodeForCoordinateWithMaxDistance( + const FixedPointCoordinate &input_coordinate, + std::vector> &resulting_phantom_node_vector, + const double max_distance, + const unsigned min_number_of_phantom_nodes, + const unsigned max_number_of_phantom_nodes) override final + { + if (!m_static_rtree.get() || CURRENT_TIMESTAMP != m_static_rtree->first) + { + LoadRTree(); + } + + return m_static_rtree->second->IncrementalFindPhantomNodeForCoordinateWithDistance( + input_coordinate, resulting_phantom_node_vector, max_distance, + min_number_of_phantom_nodes, max_number_of_phantom_nodes); + } + unsigned GetCheckSum() const override final { return m_check_sum; } unsigned GetNameIndexFromEdgeID(const unsigned id) const override final diff --git a/tools/simpleclient.cpp b/tools/simpleclient.cpp index 40407ced2..ce590ccd8 100644 --- a/tools/simpleclient.cpp +++ b/tools/simpleclient.cpp @@ -43,12 +43,13 @@ int main(int argc, const char *argv[]) try { std::string ip_address; - int ip_port, requested_thread_num; + int ip_port, requested_thread_num, max_locations_map_matching; bool trial_run = false; libosrm_config lib_config; const unsigned init_result = GenerateServerProgramOptions( argc, argv, lib_config.server_paths, ip_address, ip_port, requested_thread_num, - lib_config.use_shared_memory, trial_run, lib_config.max_locations_distance_table); + lib_config.use_shared_memory, trial_run, lib_config.max_locations_distance_table, + max_locations_map_matching); if (init_result == INIT_OK_DO_NOT_START_ENGINE) { diff --git a/util/json_logger.hpp b/util/json_logger.hpp new file mode 100644 index 000000000..03f8a171a --- /dev/null +++ b/util/json_logger.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef JSON_LOGGER_HPP +#define JSON_LOGGER_HPP + +#include + +#include + +namespace osrm +{ +namespace json +{ + +// Used to append additional debugging information to the JSON response in a +// thread safe manner. +class Logger +{ + using MapT = std::unordered_map; + + public: + static Logger* get() + { + static Logger logger; + +#ifdef NDEBUG + return nullptr; +#else + return &logger; +#endif + } + + void initialize(const std::string& name) + { + if (!map.get()) + { + map.reset(new MapT()); + } + (*map)[name] = Object(); + } + + void render(const std::string& name, Object& obj) const + { + obj.values["debug"] = map->at(name); + } + + boost::thread_specific_ptr map; +}; + + +} +} + +#endif /* JSON_LOGGER_HPP */ diff --git a/util/json_util.hpp b/util/json_util.hpp new file mode 100644 index 000000000..bfb8a8bc4 --- /dev/null +++ b/util/json_util.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef JSON_UTIL_HPP +#define JSON_UTIL_HPP + +#include + +#include +#include + +namespace osrm +{ +namespace json +{ + +// Make sure we don't have inf and NaN values +template T clamp_float(T d) +{ + if (std::isnan(d) || std::numeric_limits::infinity() == d) + { + return std::numeric_limits::max(); + } + if (-std::numeric_limits::infinity() == d) + { + return std::numeric_limits::lowest(); + } + + return d; +} + +template osrm::json::Array make_array(Args... args) +{ + osrm::json::Array a; + append_to_container(a.values, args...); + return a; +} + +template osrm::json::Array make_array(const std::vector &vector) +{ + osrm::json::Array a; + for (const auto &v : vector) + { + a.values.emplace_back(v); + } + return a; +} + +// template specialization needed as clang does not play nice +template <> osrm::json::Array make_array(const std::vector &vector) +{ + osrm::json::Array a; + for (const bool v : vector) + { + a.values.emplace_back(v); + } + return a; +} + +// Easy acces to object hierachies +osrm::json::Value &get(osrm::json::Value &value) { return value; } + +template +osrm::json::Value &get(osrm::json::Value &value, const char *key, Keys... keys) +{ + using recursive_object_t = mapbox::util::recursive_wrapper; + return get(value.get().get().values[key], keys...); +} + +template +osrm::json::Value &get(osrm::json::Value &value, unsigned key, Keys... keys) +{ + using recursive_array_t = mapbox::util::recursive_wrapper; + return get(value.get().get().values[key], keys...); +} + +} // namespace json +} // namespace osrm +#endif // JSON_UTIL_HPP diff --git a/util/matching_debug_info.hpp b/util/matching_debug_info.hpp new file mode 100644 index 000000000..284bd9f57 --- /dev/null +++ b/util/matching_debug_info.hpp @@ -0,0 +1,152 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MATCHING_DEBUG_INFO_HPP +#define MATCHING_DEBUG_INFO_HPP + +#include "json_logger.hpp" +#include "json_util.hpp" +#include "../data_structures/hidden_markov_model.hpp" + +#include + +// Provides the debug interface for introspection tools +struct MatchingDebugInfo +{ + MatchingDebugInfo(const osrm::json::Logger *logger) : logger(logger) + { + if (logger) + { + object = &logger->map->at("matching"); + } + } + + template void initialize(const CandidateLists &candidates_list) + { + // json logger not enabled + if (!logger) + { + return; + } + + osrm::json::Array states; + for (unsigned t = 0; t < candidates_list.size(); t++) + { + osrm::json::Array timestamps; + for (unsigned s = 0; s < candidates_list[t].size(); s++) + { + osrm::json::Object state; + state.values["transitions"] = osrm::json::Array(); + state.values["coordinate"] = osrm::json::make_array( + candidates_list[t][s].first.location.lat / COORDINATE_PRECISION, + candidates_list[t][s].first.location.lon / COORDINATE_PRECISION); + state.values["viterbi"] = + osrm::json::clamp_float(osrm::matching::IMPOSSIBLE_LOG_PROB); + state.values["pruned"] = 0u; + timestamps.values.push_back(state); + } + states.values.push_back(timestamps); + } + osrm::json::get(*object, "states") = states; + } + + void add_transition_info(const unsigned prev_t, + const unsigned current_t, + const unsigned prev_state, + const unsigned current_state, + const double prev_viterbi, + const double emission_pr, + const double transition_pr, + const double network_distance, + const double great_circle_distance) + { + // json logger not enabled + if (!logger) + { + return; + } + + osrm::json::Object transistion; + transistion.values["to"] = osrm::json::make_array(current_t, current_state); + transistion.values["properties"] = osrm::json::make_array( + osrm::json::clamp_float(prev_viterbi), osrm::json::clamp_float(emission_pr), + osrm::json::clamp_float(transition_pr), network_distance, great_circle_distance); + + osrm::json::get(*object, "states", prev_t, prev_state, "transitions") + .get>() + .get() + .values.push_back(transistion); + } + + void set_viterbi(const std::vector> &viterbi, + const std::vector> &pruned) + { + // json logger not enabled + if (!logger) + { + return; + } + + for (auto t = 0u; t < viterbi.size(); t++) + { + for (auto s_prime = 0u; s_prime < viterbi[t].size(); ++s_prime) + { + osrm::json::get(*object, "states", t, s_prime, "viterbi") = + osrm::json::clamp_float(viterbi[t][s_prime]); + osrm::json::get(*object, "states", t, s_prime, "pruned") = + static_cast(pruned[t][s_prime]); + } + } + } + + void add_chosen(const unsigned t, const unsigned s) + { + // json logger not enabled + if (!logger) + { + return; + } + + osrm::json::get(*object, "states", t, s, "chosen") = true; + } + + void add_breakage(const std::vector &breakage) + { + // json logger not enabled + if (!logger) + { + return; + } + + osrm::json::get(*object, "breakage") = osrm::json::make_array(breakage); + } + + const osrm::json::Logger *logger; + osrm::json::Value *object; +}; + +#endif // MATCHING_DEBUG_INFO_HPP diff --git a/util/routed_options.hpp b/util/routed_options.hpp index 322737d6d..046ab71b3 100644 --- a/util/routed_options.hpp +++ b/util/routed_options.hpp @@ -151,7 +151,8 @@ inline unsigned GenerateServerProgramOptions(const int argc, int &requested_num_threads, bool &use_shared_memory, bool &trial, - int &max_locations_distance_table) + int &max_locations_distance_table, + int &max_locations_map_matching) { // declare a group of options that will be allowed only on command line boost::program_options::options_description generic_options("Options"); @@ -192,7 +193,10 @@ inline unsigned GenerateServerProgramOptions(const int argc, "Load data from shared memory")( "max-table-size,m", boost::program_options::value(&max_locations_distance_table)->default_value(100), - "Max. locations supported in distance table query"); + "Max. locations supported in distance table query")( + "max-matching-size,m", + boost::program_options::value(&max_locations_map_matching)->default_value(2), + "Max. locations supported in map matching query"); // hidden options, will be allowed both on command line and in config // file, but will not be shown to the user @@ -269,6 +273,10 @@ inline unsigned GenerateServerProgramOptions(const int argc, { throw osrm::exception("Max location for distance table must be a positive number"); } + if (2 > max_locations_map_matching) + { + throw osrm::exception("Max location for map matching must be at least two"); + } SimpleLogger().Write() << visible_options; return INIT_OK_DO_NOT_START_ENGINE;