Merge pull request #1407 from Project-OSRM/feature/matching
Map Matching
This commit is contained in:
		
						commit
						272a1fda54
					
				| @ -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} $<TARGET_OBJECTS:COORDINATE> $<TARGET_OBJECTS:FINGERPRINT> $<TARGET_OBJECTS:GITDESCRIPTION> $<TARGET_OBJECTS:IMPORT> $<TARGET_OBJECTS:LOGGER> $<TARGET_OBJECTS:EXCEPTION> $<TARGET_OBJECTS:MERCATOR>) | ||||
| 
 | ||||
| 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} $<TARGET_OBJECTS:FINGERPRINT> $<TARGET_OBJECTS:GITDESCRIPTION> $<TARGET_OBJECTS:COORDINATE> $<TARGET_OBJECTS:IMPORT> $<TARGET_OBJECTS:LOGGER> $<TARGET_OBJECTS:RESTRICTION> $<TARGET_OBJECTS:EXCEPTION> $<TARGET_OBJECTS:MERCATOR>) | ||||
| add_executable(osrm-prepare ${PrepareSources} $<TARGET_OBJECTS:ANGLE> $<TARGET_OBJECTS:FINGERPRINT> $<TARGET_OBJECTS:GITDESCRIPTION> $<TARGET_OBJECTS:COORDINATE> $<TARGET_OBJECTS:IMPORT> $<TARGET_OBJECTS:LOGGER> $<TARGET_OBJECTS:RESTRICTION> $<TARGET_OBJECTS:EXCEPTION> $<TARGET_OBJECTS:MERCATOR>) | ||||
| 
 | ||||
| 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} $<TARGET_OBJECTS:GITDESCRIPTION> $<TARGET_OBJECTS:FINGERPRINT> $<TARGET_OBJECTS:COORDINATE> $<TARGET_OBJECTS:LOGGER> $<TARGET_OBJECTS:PHANTOMNODE> $<TARGET_OBJECTS:EXCEPTION> $<TARGET_OBJECTS:MERCATOR>) | ||||
| add_library(OSRM ${OSRMSources} $<TARGET_OBJECTS:ANGLE> $<TARGET_OBJECTS:COORDINATE> $<TARGET_OBJECTS:GITDESCRIPTION> $<TARGET_OBJECTS:FINGERPRINT> $<TARGET_OBJECTS:COORDINATE> $<TARGET_OBJECTS:LOGGER> $<TARGET_OBJECTS:PHANTOMNODE> $<TARGET_OBJECTS:EXCEPTION> $<TARGET_OBJECTS:MERCATOR>) | ||||
| add_dependencies(FINGERPRINT FingerPrintConfigure) | ||||
| 
 | ||||
| add_executable(osrm-routed routed.cpp ${ServerGlob} $<TARGET_OBJECTS:EXCEPTION>) | ||||
|  | ||||
							
								
								
									
										117
									
								
								algorithms/bayes_classifier.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								algorithms/bayes_classifier.hpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <cmath> | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| 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 <typename PositiveDistributionT, typename NegativeDistributionT, typename ValueT> | ||||
| class BayesClassifier | ||||
| { | ||||
|   public: | ||||
|     enum class ClassLabel : unsigned | ||||
|     { | ||||
|         NEGATIVE, | ||||
|         POSITIVE | ||||
|     }; | ||||
|     using ClassificationT = std::pair<ClassLabel, double>; | ||||
| 
 | ||||
|     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
 | ||||
							
								
								
									
										154
									
								
								data_structures/hidden_markov_model.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								data_structures/hidden_markov_model.hpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <boost/assert.hpp> | ||||
| 
 | ||||
| #include <cmath> | ||||
| 
 | ||||
| #include <limits> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace osrm | ||||
| { | ||||
| namespace matching | ||||
| { | ||||
| static const double log_2_pi = std::log(2. * M_PI); | ||||
| static const double IMPOSSIBLE_LOG_PROB = -std::numeric_limits<double>::infinity(); | ||||
| static const double MINIMAL_LOG_PROB = std::numeric_limits<double>::lowest(); | ||||
| static const std::size_t INVALID_STATE = std::numeric_limits<std::size_t>::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 <class CandidateLists> struct HiddenMarkovModel | ||||
| { | ||||
|     std::vector<std::vector<double>> viterbi; | ||||
|     std::vector<std::vector<std::pair<unsigned, unsigned>>> parents; | ||||
|     std::vector<std::vector<float>> path_lengths; | ||||
|     std::vector<std::vector<bool>> pruned; | ||||
|     std::vector<bool> 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<std::size_t>(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
 | ||||
| @ -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; | ||||
|  | ||||
| @ -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 <type_traits> | ||||
| @ -45,10 +46,14 @@ template <class DataFacadeT> class SearchEngine | ||||
|     ShortestPathRouting<DataFacadeT> shortest_path; | ||||
|     AlternativeRouting<DataFacadeT> alternative_path; | ||||
|     ManyToManyRouting<DataFacadeT> distance_table; | ||||
|     MapMatching<DataFacadeT> 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<DataFacadeT>::value, "don't instantiate with ptr type"); | ||||
|         static_assert(std::is_object<DataFacadeT>::value, | ||||
|  | ||||
| @ -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<PhantomNode> &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<std::pair<PhantomNode, double>> &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<float> min_found_distances(number_of_results, | ||||
|                                                std::numeric_limits<float>::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<double, double> projected_coordinate = { | ||||
|             mercator::lat2y(input_coordinate.lat / COORDINATE_PRECISION), | ||||
|             input_coordinate.lon / COORDINATE_PRECISION}; | ||||
| 
 | ||||
|         // upper bound pruning technique
 | ||||
|         upper_bound<float> pruning_bound(max_number_of_phantom_nodes); | ||||
| 
 | ||||
|         // initialize queue with root element
 | ||||
|         std::priority_queue<IncrementalQueryCandidate> 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<TreeNode>()) | ||||
|             { // current object is a tree node
 | ||||
|                 const TreeNode ¤t_tree_node = | ||||
|                     current_query_node.node.template get<TreeNode>(); | ||||
|                 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<EdgeDataT>(); | ||||
| 
 | ||||
|                 // 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<IncrementalQueryCandidate>{}; | ||||
|                     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<IncrementalQueryCandidate>{}; | ||||
|             } | ||||
|         } | ||||
|         // 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(); | ||||
|     } | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										96
									
								
								features/step_definitions/matching.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								features/step_definitions/matching.rb
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| 
 | ||||
							
								
								
									
										26
									
								
								features/support/match.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								features/support/match.rb
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| 
 | ||||
							
								
								
									
										55
									
								
								features/testbot/matching.feature
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								features/testbot/matching.feature
									
									
									
									
									
										Normal file
									
								
							| @ -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      | | ||||
| 
 | ||||
| @ -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; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -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<std::string> hints; | ||||
|     std::vector<unsigned> timestamps; | ||||
|     std::vector<bool> uturns; | ||||
|     std::vector<FixedPointCoordinate> coordinates; | ||||
| }; | ||||
|  | ||||
| @ -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<BaseDataFacade<QueryEdge::EdgeData>>(query_data_facade)); | ||||
|     RegisterPlugin(new NearestPlugin<BaseDataFacade<QueryEdge::EdgeData>>(query_data_facade)); | ||||
|     RegisterPlugin(new MapMatchingPlugin<BaseDataFacade<QueryEdge::EdgeData>>( | ||||
|         query_data_facade, lib_config.max_locations_map_matching)); | ||||
|     RegisterPlugin(new TimestampPlugin<BaseDataFacade<QueryEdge::EdgeData>>(query_data_facade)); | ||||
|     RegisterPlugin(new ViaRoutePlugin<BaseDataFacade<QueryEdge::EdgeData>>(query_data_facade)); | ||||
| } | ||||
|  | ||||
							
								
								
									
										315
									
								
								plugins/match.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								plugins/match.hpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <cstdlib> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| template <class DataFacadeT> class MapMatchingPlugin : public BasePlugin | ||||
| { | ||||
|     constexpr static const unsigned max_number_of_candidates = 10; | ||||
| 
 | ||||
|     std::shared_ptr<SearchEngine<DataFacadeT>> search_engine_ptr; | ||||
| 
 | ||||
|     using ClassifierT = BayesClassifier<LaplaceDistribution, LaplaceDistribution, double>; | ||||
|     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<SearchEngine<DataFacadeT>>(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<FixedPointCoordinate> &input_coords, | ||||
|                       std::vector<double> &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<std::size_t>(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<std::pair<PhantomNode, double>> 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<std::size_t>(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<double> 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<int>(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<bool>(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
 | ||||
| @ -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; | ||||
|  | ||||
							
								
								
									
										339
									
								
								routing_algorithms/map_matching.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								routing_algorithms/map_matching.hpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <variant/variant.hpp> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <iomanip> | ||||
| #include <numeric> | ||||
| 
 | ||||
| namespace osrm | ||||
| { | ||||
| namespace matching | ||||
| { | ||||
| 
 | ||||
| struct SubMatching | ||||
| { | ||||
|     std::vector<PhantomNode> nodes; | ||||
|     std::vector<unsigned> indices; | ||||
|     double length; | ||||
|     double confidence; | ||||
| }; | ||||
| 
 | ||||
| using CandidateList = std::vector<std::pair<PhantomNode, double>>; | ||||
| using CandidateLists = std::vector<CandidateList>; | ||||
| using HMM = HiddenMarkovModel<CandidateLists>; | ||||
| using SubMatchingList = std::vector<SubMatching>; | ||||
| 
 | ||||
| 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 DataFacadeT> | ||||
| class MapMatching final : public BasicRoutingInterface<DataFacadeT, MapMatching<DataFacadeT>> | ||||
| { | ||||
|     using super = BasicRoutingInterface<DataFacadeT, MapMatching<DataFacadeT>>; | ||||
|     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<FixedPointCoordinate> &trace_coordinates, | ||||
|                     const std::vector<unsigned> &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<std::size_t> split_points; | ||||
|         std::vector<std::size_t> 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<std::size_t>(0u, prev_viterbi.size())) | ||||
|             { | ||||
|                 if (prev_pruned[s]) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 for (const auto s_prime : osrm::irange<std::size_t>(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<std::pair<std::size_t, std::size_t>> 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<std::size_t>(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 */ | ||||
| @ -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 DataFacadeT, class Derived> 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<double>::max(); | ||||
|         if (upper_bound != INVALID_EDGE_WEIGHT) | ||||
|         { | ||||
|             std::vector<NodeID> packed_leg; | ||||
|             RetrievePackedPathFromHeap(forward_heap, reverse_heap, middle_node, packed_leg); | ||||
|             std::vector<PathData> 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
 | ||||
|  | ||||
| @ -40,8 +40,9 @@ template <typename Iterator, class HandlerT> struct APIGrammar : qi::grammar<Ite | ||||
|     { | ||||
|         api_call = qi::lit('/') >> 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 <typename Iterator, class HandlerT> struct APIGrammar : qi::grammar<Ite | ||||
|                     qi::double_)[boost::bind(&HandlerT::addCoordinate, handler, ::_1)]; | ||||
|         hint = (-qi::lit('&')) >> 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 <typename Iterator, class HandlerT> struct APIGrammar : qi::grammar<Ite | ||||
|                   string[boost::bind(&HandlerT::setDeprecatedAPIFlag, handler, ::_1)]; | ||||
|         num_results = (-qi::lit('&')) >> 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 <typename Iterator, class HandlerT> struct APIGrammar : qi::grammar<Ite | ||||
| 
 | ||||
|     qi::rule<Iterator> api_call, query; | ||||
|     qi::rule<Iterator, std::string()> 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; | ||||
| }; | ||||
|  | ||||
| @ -104,6 +104,12 @@ template <class EdgeDataT> class BaseDataFacade | ||||
|     virtual bool | ||||
|     IncrementalFindPhantomNodeForCoordinate(const FixedPointCoordinate &input_coordinate, | ||||
|                                             PhantomNode &resulting_phantom_node) = 0; | ||||
|     virtual bool | ||||
|     IncrementalFindPhantomNodeForCoordinateWithMaxDistance(const FixedPointCoordinate &input_coordinate, | ||||
|                                                         std::vector<std::pair<PhantomNode, double>> &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; | ||||
| 
 | ||||
|  | ||||
| @ -398,6 +398,7 @@ template <class EdgeDataT> 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 EdgeDataT> class InternalDataFacade final : public BaseDataFacad | ||||
|             input_coordinate, resulting_phantom_node_vector, number_of_results); | ||||
|     } | ||||
| 
 | ||||
|     bool IncrementalFindPhantomNodeForCoordinateWithMaxDistance( | ||||
|         const FixedPointCoordinate &input_coordinate, | ||||
|         std::vector<std::pair<PhantomNode, double>> &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 | ||||
|  | ||||
| @ -386,6 +386,7 @@ template <class EdgeDataT> 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 EdgeDataT> class SharedDataFacade final : public BaseDataFacade< | ||||
|             input_coordinate, resulting_phantom_node_vector, number_of_results); | ||||
|     } | ||||
| 
 | ||||
|     bool IncrementalFindPhantomNodeForCoordinateWithMaxDistance( | ||||
|         const FixedPointCoordinate &input_coordinate, | ||||
|         std::vector<std::pair<PhantomNode, double>> &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 | ||||
|  | ||||
| @ -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) | ||||
|         { | ||||
|  | ||||
							
								
								
									
										79
									
								
								util/json_logger.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								util/json_logger.hpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <osrm/json_container.hpp> | ||||
| 
 | ||||
| #include <boost/thread.hpp> | ||||
| 
 | ||||
| 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<std::string, osrm::json::Value>; | ||||
| 
 | ||||
|   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<MapT> map; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| #endif /* JSON_LOGGER_HPP */ | ||||
							
								
								
									
										103
									
								
								util/json_util.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								util/json_util.hpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <osrm/json_container.hpp> | ||||
| 
 | ||||
| #include <cmath> | ||||
| #include <limits> | ||||
| 
 | ||||
| namespace osrm | ||||
| { | ||||
| namespace json | ||||
| { | ||||
| 
 | ||||
| // Make sure we don't have inf and NaN values
 | ||||
| template <typename T> T clamp_float(T d) | ||||
| { | ||||
|     if (std::isnan(d) || std::numeric_limits<T>::infinity() == d) | ||||
|     { | ||||
|         return std::numeric_limits<T>::max(); | ||||
|     } | ||||
|     if (-std::numeric_limits<T>::infinity() == d) | ||||
|     { | ||||
|         return std::numeric_limits<T>::lowest(); | ||||
|     } | ||||
| 
 | ||||
|     return d; | ||||
| } | ||||
| 
 | ||||
| template <typename... Args> osrm::json::Array make_array(Args... args) | ||||
| { | ||||
|     osrm::json::Array a; | ||||
|     append_to_container(a.values, args...); | ||||
|     return a; | ||||
| } | ||||
| 
 | ||||
| template <typename T> osrm::json::Array make_array(const std::vector<T> &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<bool> &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 <typename... Keys> | ||||
| osrm::json::Value &get(osrm::json::Value &value, const char *key, Keys... keys) | ||||
| { | ||||
|     using recursive_object_t = mapbox::util::recursive_wrapper<osrm::json::Object>; | ||||
|     return get(value.get<recursive_object_t>().get().values[key], keys...); | ||||
| } | ||||
| 
 | ||||
| template <typename... Keys> | ||||
| osrm::json::Value &get(osrm::json::Value &value, unsigned key, Keys... keys) | ||||
| { | ||||
|     using recursive_array_t = mapbox::util::recursive_wrapper<osrm::json::Array>; | ||||
|     return get(value.get<recursive_array_t>().get().values[key], keys...); | ||||
| } | ||||
| 
 | ||||
| } // namespace json
 | ||||
| } // namespace osrm
 | ||||
| #endif // JSON_UTIL_HPP
 | ||||
							
								
								
									
										152
									
								
								util/matching_debug_info.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								util/matching_debug_info.hpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <osrm/coordinate.hpp> | ||||
| 
 | ||||
| // 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 <class CandidateLists> 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<mapbox::util::recursive_wrapper<osrm::json::Array>>() | ||||
|             .get() | ||||
|             .values.push_back(transistion); | ||||
|     } | ||||
| 
 | ||||
|     void set_viterbi(const std::vector<std::vector<double>> &viterbi, | ||||
|                      const std::vector<std::vector<bool>> &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<unsigned>(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<bool> &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
 | ||||
| @ -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<int>(&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<int>(&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; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user