diff --git a/data_structures/route_parameters.cpp b/data_structures/route_parameters.cpp index 9416a6d69..7429e4902 100644 --- a/data_structures/route_parameters.cpp +++ b/data_structures/route_parameters.cpp @@ -99,6 +99,15 @@ void RouteParameters::addHint(const std::string &hint) } } +void RouteParameters::addTimestamp(const unsigned timestamp) +{ + timestamps.resize(coordinates.size()); + if (!timestamps.empty()) + { + timestamps.back() = timestamp; + } +} + void RouteParameters::setLanguage(const std::string &language_string) { language = language_string; diff --git a/include/osrm/route_parameters.hpp b/include/osrm/route_parameters.hpp index 04538986e..5fe454cee 100644 --- a/include/osrm/route_parameters.hpp +++ b/include/osrm/route_parameters.hpp @@ -63,6 +63,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); @@ -85,6 +87,7 @@ struct RouteParameters std::string jsonp_parameter; std::string language; std::vector hints; + std::vector timestamps; std::vector uturns; std::vector coordinates; }; diff --git a/plugins/map_matching.hpp b/plugins/map_matching.hpp index 57717581c..03ada65bc 100644 --- a/plugins/map_matching.hpp +++ b/plugins/map_matching.hpp @@ -172,6 +172,11 @@ template class MapMatchingPlugin : public BasePlugin std::vector sub_trace_lengths; 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; + } bool found_candidates = get_candiates(input_coords, sub_trace_lengths, candidates_lists); if (!found_candidates) { @@ -181,7 +186,7 @@ template class MapMatchingPlugin : public BasePlugin // call the actual map matching JSON::Object debug_info; Matching::SubMatchingList sub_matchings; - search_engine_ptr->map_matching(candidates_lists, input_coords, sub_matchings, debug_info); + search_engine_ptr->map_matching(candidates_lists, input_coords, input_timestamps, sub_matchings, debug_info); if (1 > sub_matchings.size()) { diff --git a/routing_algorithms/map_matching.hpp b/routing_algorithms/map_matching.hpp index ceca02214..03ec5d097 100644 --- a/routing_algorithms/map_matching.hpp +++ b/routing_algorithms/map_matching.hpp @@ -86,8 +86,8 @@ constexpr static const unsigned max_number_of_candidates = 10; constexpr static const double IMPOSSIBLE_LOG_PROB = -std::numeric_limits::infinity(); constexpr static const double MINIMAL_LOG_PROB = -std::numeric_limits::max(); constexpr static const unsigned INVALID_STATE = std::numeric_limits::max(); -// FIXME that should be a time threshold. constexpr static const unsigned MAX_BROKEN_STATES = 6; +constexpr static const unsigned MAX_BROKEN_TIME = 180; } // implements a hidden markov model map matching algorithm @@ -252,14 +252,14 @@ template class MapMatching final std::vector> pruned; std::vector breakage; - const Matching::CandidateLists& timestamp_list; + const Matching::CandidateLists& candidates_list; - HiddenMarkovModel(const Matching::CandidateLists& timestamp_list) - : breakage(timestamp_list.size()) - , timestamp_list(timestamp_list) + HiddenMarkovModel(const Matching::CandidateLists& candidates_list) + : breakage(candidates_list.size()) + , candidates_list(candidates_list) { - for (const auto& l : timestamp_list) + for (const auto& l : candidates_list) { viterbi.emplace_back(l.size()); parents.emplace_back(l.size()); @@ -289,13 +289,13 @@ template class MapMatching final unsigned initialize(unsigned initial_timestamp) { - BOOST_ASSERT(initial_timestamp < timestamp_list.size()); + BOOST_ASSERT(initial_timestamp < candidates_list.size()); do { for (auto s = 0u; s < viterbi[initial_timestamp].size(); ++s) { - viterbi[initial_timestamp][s] = log_emission_probability(timestamp_list[initial_timestamp][s].second); + viterbi[initial_timestamp][s] = log_emission_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] < Matching::MINIMAL_LOG_PROB; @@ -328,14 +328,15 @@ template class MapMatching final } - void operator()(const Matching::CandidateLists ×tamp_list, - const std::vector coordinate_list, + void operator()(const Matching::CandidateLists &candidates_list, + const std::vector& trace_coordinates, + const std::vector& trace_timestamps, Matching::SubMatchingList& sub_matchings, JSON::Object& _debug_info) const { - BOOST_ASSERT(timestamp_list.size() > 0); + BOOST_ASSERT(candidates_list.size() > 0); - HiddenMarkovModel model(timestamp_list); + HiddenMarkovModel model(candidates_list); unsigned initial_timestamp = model.initialize(0); if (initial_timestamp == Matching::INVALID_STATE) @@ -344,15 +345,15 @@ template class MapMatching final } JSON::Array _debug_states; - for (unsigned t = 0; t < timestamp_list.size(); t++) + for (unsigned t = 0; t < candidates_list.size(); t++) { JSON::Array _debug_timestamps; - for (unsigned s = 0; s < timestamp_list[t].size(); s++) + for (unsigned s = 0; s < candidates_list[t].size(); s++) { JSON::Object _debug_state; _debug_state.values["transitions"] = JSON::Array(); - _debug_state.values["coordinate"] = makeJSONArray(timestamp_list[t][s].first.location.lat / COORDINATE_PRECISION, - timestamp_list[t][s].first.location.lon / COORDINATE_PRECISION); + _debug_state.values["coordinate"] = makeJSONArray(candidates_list[t][s].first.location.lat / COORDINATE_PRECISION, + candidates_list[t][s].first.location.lon / COORDINATE_PRECISION); if (t < initial_timestamp) { _debug_state.values["viterbi"] = makeJSONSafe(Matching::IMPOSSIBLE_LOG_PROB); @@ -368,24 +369,25 @@ template class MapMatching final _debug_states.values.push_back(_debug_timestamps); } + unsigned breakage_begin = std::numeric_limits::max(); std::vector split_points; std::vector prev_unbroken_timestamps; - prev_unbroken_timestamps.reserve(timestamp_list.size()); + prev_unbroken_timestamps.reserve(candidates_list.size()); prev_unbroken_timestamps.push_back(initial_timestamp); - for (auto t = initial_timestamp + 1; t < timestamp_list.size(); ++t) + for (auto t = initial_timestamp + 1; t < candidates_list.size(); ++t) { unsigned 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 = timestamp_list[prev_unbroken_timestamp]; - const auto& prev_coordinate = coordinate_list[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 = timestamp_list[t]; - const auto& current_coordinate = coordinate_list[t]; + const auto& current_timestamps_list = candidates_list[t]; + const auto& current_coordinate = trace_coordinates[t]; // compute d_t for this timestamp and the next one for (auto s = 0u; s < prev_viterbi.size(); ++s) @@ -396,7 +398,7 @@ template class MapMatching final for (auto s_prime = 0u; s_prime < current_viterbi.size(); ++s_prime) { // how likely is candidate s_prime at time t to be emitted? - const double emission_pr = log_emission_probability(timestamp_list[t][s_prime].second); + const double emission_pr = log_emission_probability(candidates_list[t][s_prime].second); double new_value = prev_viterbi[s] + emission_pr; if (current_viterbi[s_prime] > new_value) continue; @@ -456,23 +458,47 @@ template class MapMatching final { BOOST_ASSERT(prev_unbroken_timestamps.size() > 0); + // save start of breakage -> we need this as split point + if (t < breakage_begin) + { + breakage_begin = t; + } + // remove both ends of the breakage prev_unbroken_timestamps.pop_back(); + bool trace_split = prev_unbroken_timestamps.size() < 1; + + // use temporal information to determine a split if available + if (trace_timestamps.size() > 0) + { + trace_split = trace_split || (trace_timestamps[t] - trace_timestamps[prev_unbroken_timestamps.back()] > Matching::MAX_BROKEN_TIME); + } + else + { + trace_split = trace_split || (t - prev_unbroken_timestamps.back() > Matching::MAX_BROKEN_STATES); + } + // we reached the beginning of the trace and it is still broken // -> split the trace here - if (prev_unbroken_timestamps.size() < 1 || t - prev_unbroken_timestamps.back() > Matching::MAX_BROKEN_STATES) + if (trace_split) { - split_points.push_back(t); + split_points.push_back(breakage_begin); // note this preserves everything before t - model.clear(t); - unsigned new_start = model.initialize(t); + model.clear(breakage_begin); + unsigned new_start = model.initialize(breakage_begin); // no new start was found -> stop viterbi calculation if (new_start == Matching::INVALID_STATE) { break; } + prev_unbroken_timestamps.clear(); prev_unbroken_timestamps.push_back(new_start); + // Important: We potentially go back here! + // However since t+1 > new_start >= breakge_begin + // we can only reset trace_coordindates.size() times. + t = new_start; + breakage_begin = std::numeric_limits::max(); } } else @@ -540,7 +566,7 @@ template class MapMatching final auto location_index = reconstructed_indices[i].second; matching.indices[i] = timestamp_index; - matching.nodes[i] = timestamp_list[timestamp_index][location_index].first; + matching.nodes[i] = candidates_list[timestamp_index][location_index].first; matching.length += model.path_lengths[timestamp_index][location_index]; _debug_states.values[timestamp_index] diff --git a/server/api_grammar.hpp b/server/api_grammar.hpp index fecbb92cc..3d7f4ea4b 100644 --- a/server/api_grammar.hpp +++ b/server/api_grammar.hpp @@ -40,7 +40,7 @@ template struct APIGrammar : qi::grammar> string[boost::bind(&HandlerT::setService, handler, ::_1)] >> *(query) >> -(uturns); - query = ('?') >> (+(zoom | output | jsonp | checksum | location | hint | u | cmp | + query = ('?') >> (+(zoom | output | jsonp | checksum | location | hint | timestamp | u | cmp | language | instruction | geometry | alt_route | old_API | num_results)); zoom = (-qi::lit('&')) >> qi::lit('z') >> '=' >> @@ -62,6 +62,8 @@ template struct APIGrammar : qi::grammar> qi::lit("hint") >> '=' >> stringwithDot[boost::bind(&HandlerT::addHint, handler, ::_1)]; + timestamp = (-qi::lit('&')) >> qi::lit("t") >> '=' >> + qi::uint_[boost::bind(&HandlerT::addTimestamp, handler, ::_1)]; u = (-qi::lit('&')) >> qi::lit("u") >> '=' >> qi::bool_[boost::bind(&HandlerT::setUTurn, handler, ::_1)]; uturns = (-qi::lit('&')) >> qi::lit("uturns") >> '=' >> @@ -83,7 +85,7 @@ template struct APIGrammar : qi::grammar api_call, query; qi::rule service, zoom, output, string, jsonp, checksum, location, - hint, stringwithDot, stringwithPercent, language, instruction, geometry, cmp, alt_route, u, + hint, timestamp, stringwithDot, stringwithPercent, language, instruction, geometry, cmp, alt_route, u, uturns, old_API, num_results; HandlerT *handler;