Merge pull request #1407 from Project-OSRM/feature/matching

Map Matching
This commit is contained in:
Dennis Luxen 2015-03-10 06:08:11 -07:00
commit 272a1fda54
26 changed files with 1738 additions and 110 deletions

View File

@ -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>)

View 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

View 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

View File

@ -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 &parameter)
@ -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;

View File

@ -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,

View File

@ -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 &current_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 &current_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 &current_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();
}

View File

@ -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

View 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
View 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

View 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 |

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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
View 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

View File

@ -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;

View 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 &current_viterbi = model.viterbi[t];
auto &current_pruned = model.pruned[t];
auto &current_parents = model.parents[t];
auto &current_lengths = model.path_lengths[t];
const auto &current_timestamps_list = candidates_list[t];
const auto &current_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 */

View File

@ -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

View File

@ -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;
};

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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

View 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

View File

@ -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;