2014-12-08 17:46:31 -05:00
|
|
|
/*
|
|
|
|
open source routing machine
|
2014-09-23 12:46:14 -04:00
|
|
|
Copyright (C) Dennis Luxen, others 2010
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU AFFERO General Public License as published by
|
|
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
|
|
any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
or see http://www.gnu.org/licenses/agpl.txt.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef MAP_MATCHING_PLUGIN_H
|
|
|
|
#define MAP_MATCHING_PLUGIN_H
|
|
|
|
|
|
|
|
#include "plugin_base.hpp"
|
|
|
|
|
2015-02-05 20:15:51 -05:00
|
|
|
#include "../algorithms/bayes_classifier.hpp"
|
2014-09-23 12:46:14 -04:00
|
|
|
#include "../algorithms/object_encoder.hpp"
|
|
|
|
#include "../util/integer_range.hpp"
|
|
|
|
#include "../data_structures/search_engine.hpp"
|
|
|
|
#include "../routing_algorithms/map_matching.hpp"
|
2015-01-22 17:53:35 -05:00
|
|
|
#include "../util/compute_angle.hpp"
|
2014-09-23 12:46:14 -04:00
|
|
|
#include "../util/simple_logger.hpp"
|
|
|
|
#include "../util/string_util.hpp"
|
2014-12-08 17:46:31 -05:00
|
|
|
#include "../descriptors/descriptor_base.hpp"
|
|
|
|
#include "../descriptors/gpx_descriptor.hpp"
|
|
|
|
#include "../descriptors/json_descriptor.hpp"
|
2014-09-23 12:46:14 -04:00
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <memory>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
template <class DataFacadeT> class MapMatchingPlugin : public BasePlugin
|
|
|
|
{
|
|
|
|
private:
|
2014-12-08 17:46:31 -05:00
|
|
|
std::unordered_map<std::string, unsigned> descriptor_table;
|
2014-09-23 12:46:14 -04:00
|
|
|
std::shared_ptr<SearchEngine<DataFacadeT>> search_engine_ptr;
|
|
|
|
|
2015-02-07 09:39:07 -05:00
|
|
|
using ClassifierT = BayesClassifier<LaplaceDistribution, LaplaceDistribution, double>;
|
|
|
|
using TraceClassification = ClassifierT::ClassificationT;
|
|
|
|
|
2014-09-23 12:46:14 -04:00
|
|
|
public:
|
2015-02-05 20:15:51 -05:00
|
|
|
MapMatchingPlugin(DataFacadeT *facade)
|
|
|
|
: descriptor_string("match")
|
|
|
|
, facade(facade)
|
|
|
|
// 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
|
2014-09-23 12:46:14 -04:00
|
|
|
{
|
2014-12-08 17:46:31 -05:00
|
|
|
descriptor_table.emplace("json", 0);
|
2014-09-23 12:46:14 -04:00
|
|
|
search_engine_ptr = std::make_shared<SearchEngine<DataFacadeT>>(facade);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual ~MapMatchingPlugin() { search_engine_ptr.reset(); }
|
|
|
|
|
|
|
|
const std::string GetDescriptor() const final { return descriptor_string; }
|
|
|
|
|
2015-02-12 05:44:52 -05:00
|
|
|
TraceClassification classify(float trace_length, float matched_length, int removed_points) const
|
2015-02-07 09:39:07 -05:00
|
|
|
{
|
2015-02-12 05:44:52 -05:00
|
|
|
double distance_feature = -std::log(trace_length) + std::log(matched_length);
|
2015-02-09 06:41:30 -05:00
|
|
|
|
|
|
|
// matched to the same point
|
|
|
|
if (!std::isfinite(distance_feature))
|
|
|
|
{
|
|
|
|
return std::make_pair(ClassifierT::ClassLabel::NEGATIVE, 1.0);
|
|
|
|
}
|
2015-02-07 09:39:07 -05:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2015-02-09 06:41:30 -05:00
|
|
|
return std::make_pair(ClassifierT::ClassLabel::NEGATIVE, 1.0);
|
2015-02-07 09:39:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return label_with_confidence;
|
|
|
|
}
|
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
bool get_candiates(const std::vector<FixedPointCoordinate>& input_coords, std::vector<double>& sub_trace_lengths, Matching::CandidateLists& candidates_lists)
|
2014-09-23 12:46:14 -04:00
|
|
|
{
|
2014-12-08 17:46:31 -05:00
|
|
|
double last_distance = coordinate_calculation::great_circle_distance(
|
2015-01-22 17:53:35 -05:00
|
|
|
input_coords[0],
|
|
|
|
input_coords[1]);
|
2015-02-17 06:22:11 -05:00
|
|
|
sub_trace_lengths.resize(input_coords.size());
|
|
|
|
sub_trace_lengths[0] = 0;
|
2015-01-22 17:53:35 -05:00
|
|
|
for (const auto current_coordinate : osrm::irange<std::size_t>(0, input_coords.size()))
|
2014-09-23 12:46:14 -04:00
|
|
|
{
|
2015-02-08 18:55:12 -05:00
|
|
|
bool allow_uturn = false;
|
2014-12-08 17:46:31 -05:00
|
|
|
if (0 < current_coordinate)
|
2015-01-22 17:53:35 -05:00
|
|
|
{
|
2014-12-08 17:46:31 -05:00
|
|
|
last_distance = coordinate_calculation::great_circle_distance(
|
2015-01-22 17:53:35 -05:00
|
|
|
input_coords[current_coordinate - 1],
|
|
|
|
input_coords[current_coordinate]);
|
2015-02-17 06:22:11 -05:00
|
|
|
sub_trace_lengths[current_coordinate] += sub_trace_lengths[current_coordinate-1] + last_distance;
|
2015-01-22 17:53:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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 < 100.0 || turn_angle > 260.0)
|
|
|
|
{
|
2015-02-08 18:55:12 -05:00
|
|
|
allow_uturn = true;
|
2015-01-22 17:53:35 -05:00
|
|
|
}
|
|
|
|
}
|
2014-12-08 17:46:31 -05:00
|
|
|
|
|
|
|
std::vector<std::pair<PhantomNode, double>> candidates;
|
2015-01-15 18:17:40 -05:00
|
|
|
if (!facade->IncrementalFindPhantomNodeForCoordinateWithMaxDistance(
|
2015-01-22 17:53:35 -05:00
|
|
|
input_coords[current_coordinate],
|
2014-12-08 17:46:31 -05:00
|
|
|
candidates,
|
2015-01-15 18:17:40 -05:00
|
|
|
last_distance/2.0,
|
2014-12-08 17:46:31 -05:00
|
|
|
5,
|
2015-02-13 05:01:03 -05:00
|
|
|
Matching::max_number_of_candidates))
|
2014-09-23 12:46:14 -04:00
|
|
|
{
|
2015-02-08 18:55:12 -05:00
|
|
|
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);
|
2014-09-23 12:46:14 -04:00
|
|
|
}
|
|
|
|
|
2015-02-08 18:55:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-02-22 11:13:08 -05:00
|
|
|
int HandleRequest(const RouteParameters &route_parameters, osrm::json::Object &json_result) final
|
2015-02-08 18:55:12 -05:00
|
|
|
{
|
|
|
|
// check number of parameters
|
|
|
|
if (!check_all_coordinates(route_parameters.coordinates))
|
|
|
|
{
|
|
|
|
return 400;
|
|
|
|
}
|
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
std::vector<double> sub_trace_lengths;
|
2015-02-08 18:55:12 -05:00
|
|
|
Matching::CandidateLists candidates_lists;
|
|
|
|
const auto& input_coords = route_parameters.coordinates;
|
2015-02-20 09:21:50 -05:00
|
|
|
const auto& input_timestamps = route_parameters.timestamps;
|
|
|
|
if (input_timestamps.size() > 0 && input_coords.size() != input_timestamps.size())
|
|
|
|
{
|
|
|
|
return 400;
|
|
|
|
}
|
2015-02-17 06:22:11 -05:00
|
|
|
bool found_candidates = get_candiates(input_coords, sub_trace_lengths, candidates_lists);
|
2015-02-08 18:55:12 -05:00
|
|
|
if (!found_candidates)
|
|
|
|
{
|
|
|
|
return 400;
|
2014-09-23 12:46:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// call the actual map matching
|
2015-02-22 11:13:08 -05:00
|
|
|
osrm::json::Object debug_info;
|
2015-02-17 06:22:11 -05:00
|
|
|
Matching::SubMatchingList sub_matchings;
|
2015-02-20 09:21:50 -05:00
|
|
|
search_engine_ptr->map_matching(candidates_lists, input_coords, input_timestamps, sub_matchings, debug_info);
|
2015-01-22 17:53:35 -05:00
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
if (1 > sub_matchings.size())
|
2015-02-07 09:39:07 -05:00
|
|
|
{
|
2015-02-17 06:22:11 -05:00
|
|
|
return 400;
|
2015-02-07 09:39:07 -05:00
|
|
|
}
|
|
|
|
|
2015-02-22 17:21:37 -05:00
|
|
|
osrm::json::Array matchings;
|
2015-02-17 06:22:11 -05:00
|
|
|
for (auto& sub : sub_matchings)
|
2014-12-08 17:46:31 -05:00
|
|
|
{
|
2015-02-17 06:22:11 -05:00
|
|
|
// classify result
|
2015-02-19 19:13:44 -05:00
|
|
|
double trace_length = sub_trace_lengths[sub.indices.back()] - sub_trace_lengths[sub.indices.front()];
|
2015-02-17 06:22:11 -05:00
|
|
|
TraceClassification classification = classify(trace_length,
|
|
|
|
sub.length,
|
2015-02-19 19:13:44 -05:00
|
|
|
(sub.indices.back() - sub.indices.front() + 1) - sub.nodes.size());
|
2015-02-17 06:22:11 -05:00
|
|
|
if (classification.first == ClassifierT::ClassLabel::POSITIVE)
|
|
|
|
{
|
|
|
|
sub.confidence = classification.second;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sub.confidence = 1-classification.second;
|
|
|
|
}
|
2014-12-08 17:46:31 -05:00
|
|
|
|
2015-02-19 18:35:51 -05:00
|
|
|
BOOST_ASSERT(sub.nodes.size() > 1);
|
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
// FIXME this is a pretty bad hack. Geometries should obtained directly
|
|
|
|
// from map_matching.
|
|
|
|
// run shortest path routing to obtain geometry
|
|
|
|
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);
|
2015-01-19 17:02:07 -05:00
|
|
|
|
2014-09-23 12:46:14 -04:00
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
DescriptorConfig descriptor_config;
|
2014-12-08 17:46:31 -05:00
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
auto iter = descriptor_table.find(route_parameters.output_format);
|
|
|
|
unsigned descriptor_type = (iter != descriptor_table.end() ? iter->second : 0);
|
2014-12-08 17:46:31 -05:00
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
descriptor_config.zoom_level = route_parameters.zoom_level;
|
|
|
|
descriptor_config.instructions = false;
|
|
|
|
descriptor_config.geometry = route_parameters.geometry;
|
|
|
|
descriptor_config.encode_geometry = route_parameters.compression;
|
2014-12-08 17:46:31 -05:00
|
|
|
|
2015-02-17 06:22:11 -05:00
|
|
|
std::shared_ptr<BaseDescriptor<DataFacadeT>> descriptor;
|
|
|
|
switch (descriptor_type)
|
|
|
|
{
|
|
|
|
// case 0:
|
|
|
|
// descriptor = std::make_shared<JSONDescriptor<DataFacadeT>>();
|
|
|
|
// break;
|
|
|
|
case 1:
|
|
|
|
descriptor = std::make_shared<GPXDescriptor<DataFacadeT>>(facade);
|
|
|
|
break;
|
|
|
|
// case 2:
|
|
|
|
// descriptor = std::make_shared<GEOJSONDescriptor<DataFacadeT>>();
|
|
|
|
// break;
|
|
|
|
default:
|
|
|
|
descriptor = std::make_shared<JSONDescriptor<DataFacadeT>>(facade);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-02-22 11:13:08 -05:00
|
|
|
osrm::json::Object temp_result;
|
2015-02-17 06:22:11 -05:00
|
|
|
descriptor->SetConfig(descriptor_config);
|
|
|
|
descriptor->Run(raw_route, temp_result);
|
2014-09-23 12:46:14 -04:00
|
|
|
|
2015-02-22 11:13:08 -05:00
|
|
|
osrm::json::Array indices;
|
2015-02-19 19:13:44 -05:00
|
|
|
for (const auto& i : sub.indices)
|
|
|
|
{
|
|
|
|
indices.values.emplace_back(i);
|
2015-02-18 19:05:24 -05:00
|
|
|
}
|
|
|
|
|
2015-02-22 11:13:08 -05:00
|
|
|
osrm::json::Object subtrace;
|
2015-02-18 19:05:24 -05:00
|
|
|
subtrace.values["geometry"] = temp_result.values["route_geometry"];
|
2015-02-17 06:22:11 -05:00
|
|
|
subtrace.values["confidence"] = sub.confidence;
|
2015-02-19 19:13:44 -05:00
|
|
|
subtrace.values["indices"] = indices;
|
2015-02-18 19:05:24 -05:00
|
|
|
subtrace.values["matched_points"] = temp_result.values["via_points"];
|
2015-02-17 06:22:11 -05:00
|
|
|
|
2015-02-22 17:21:37 -05:00
|
|
|
matchings.values.push_back(subtrace);
|
2015-02-17 06:22:11 -05:00
|
|
|
}
|
2014-12-08 17:46:31 -05:00
|
|
|
|
|
|
|
json_result.values["debug"] = debug_info;
|
2015-02-22 17:21:37 -05:00
|
|
|
json_result.values["matchings"] = matchings;
|
2014-12-08 17:46:31 -05:00
|
|
|
|
2014-09-23 12:46:14 -04:00
|
|
|
return 200;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string descriptor_string;
|
|
|
|
DataFacadeT *facade;
|
2015-02-07 09:39:07 -05:00
|
|
|
ClassifierT classifier;
|
2014-09-23 12:46:14 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif /* MAP_MATCHING_PLUGIN_H */
|