1 #include "engine/plugins/match.hpp"
2 #include "engine/plugins/plugin_base.hpp"
3
4 #include "engine/api/match_api.hpp"
5 #include "engine/api/match_parameters.hpp"
6 #include "engine/api/match_parameters_tidy.hpp"
7 #include "engine/map_matching/bayes_classifier.hpp"
8 #include "engine/map_matching/sub_matching.hpp"
9 #include "util/coordinate_calculation.hpp"
10 #include "util/integer_range.hpp"
11 #include "util/json_util.hpp"
12 #include "util/string_util.hpp"
13
14 #include <cstdlib>
15
16 #include <algorithm>
17 #include <functional>
18 #include <iterator>
19 #include <memory>
20 #include <set>
21 #include <string>
22 #include <vector>
23
24 namespace osrm
25 {
26 namespace engine
27 {
28 namespace plugins
29 {
30
31 // Filters PhantomNodes to obtain a set of viable candiates
filterCandidates(const std::vector<util::Coordinate> & coordinates,MatchPlugin::CandidateLists & candidates_lists)32 void filterCandidates(const std::vector<util::Coordinate> &coordinates,
33 MatchPlugin::CandidateLists &candidates_lists)
34 {
35 for (const auto current_coordinate : util::irange<std::size_t>(0, coordinates.size()))
36 {
37 bool allow_uturn = false;
38
39 if (coordinates.size() - 1 > current_coordinate && 0 < current_coordinate)
40 {
41 double turn_angle =
42 util::coordinate_calculation::computeAngle(coordinates[current_coordinate - 1],
43 coordinates[current_coordinate],
44 coordinates[current_coordinate + 1]);
45
46 // sharp turns indicate a possible uturn
47 if (turn_angle <= 45.0 || turn_angle >= 315.0)
48 {
49 allow_uturn = true;
50 }
51 }
52
53 auto &candidates = candidates_lists[current_coordinate];
54 if (candidates.empty())
55 {
56 continue;
57 }
58
59 // sort by forward id, then by reverse id and then by distance
60 std::sort(candidates.begin(),
61 candidates.end(),
62 [](const PhantomNodeWithDistance &lhs, const PhantomNodeWithDistance &rhs) {
63 return lhs.phantom_node.forward_segment_id.id <
64 rhs.phantom_node.forward_segment_id.id ||
65 (lhs.phantom_node.forward_segment_id.id ==
66 rhs.phantom_node.forward_segment_id.id &&
67 (lhs.phantom_node.reverse_segment_id.id <
68 rhs.phantom_node.reverse_segment_id.id ||
69 (lhs.phantom_node.reverse_segment_id.id ==
70 rhs.phantom_node.reverse_segment_id.id &&
71 lhs.distance < rhs.distance)));
72 });
73
74 auto new_end =
75 std::unique(candidates.begin(),
76 candidates.end(),
77 [](const PhantomNodeWithDistance &lhs, const PhantomNodeWithDistance &rhs) {
78 return lhs.phantom_node.forward_segment_id.id ==
79 rhs.phantom_node.forward_segment_id.id &&
80 lhs.phantom_node.reverse_segment_id.id ==
81 rhs.phantom_node.reverse_segment_id.id;
82 });
83 candidates.resize(new_end - candidates.begin());
84
85 if (!allow_uturn)
86 {
87 const auto compact_size = candidates.size();
88 for (const auto i : util::irange<std::size_t>(0, compact_size))
89 {
90 // Split edge if it is bidirectional and append reverse direction to end of list
91 if (candidates[i].phantom_node.forward_segment_id.enabled &&
92 candidates[i].phantom_node.reverse_segment_id.enabled)
93 {
94 PhantomNode reverse_node(candidates[i].phantom_node);
95 reverse_node.forward_segment_id.enabled = false;
96 candidates.push_back(
97 PhantomNodeWithDistance{reverse_node, candidates[i].distance});
98
99 candidates[i].phantom_node.reverse_segment_id.enabled = false;
100 }
101 }
102 }
103
104 // sort by distance to make pruning effective
105 std::sort(candidates.begin(),
106 candidates.end(),
107 [](const PhantomNodeWithDistance &lhs, const PhantomNodeWithDistance &rhs) {
108 return lhs.distance < rhs.distance;
109 });
110 }
111 }
112
HandleRequest(const RoutingAlgorithmsInterface & algorithms,const api::MatchParameters & parameters,osrm::engine::api::ResultT & result) const113 Status MatchPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms,
114 const api::MatchParameters ¶meters,
115 osrm::engine::api::ResultT &result) const
116 {
117 if (!algorithms.HasMapMatching())
118 {
119 return Error("NotImplemented",
120 "Map matching is not implemented for the chosen search algorithm.",
121 result);
122 }
123
124 if (!CheckAlgorithms(parameters, algorithms, result))
125 return Status::Error;
126
127 const auto &facade = algorithms.GetFacade();
128
129 BOOST_ASSERT(parameters.IsValid());
130
131 // enforce maximum number of locations for performance reasons
132 if (max_locations_map_matching > 0 &&
133 static_cast<int>(parameters.coordinates.size()) > max_locations_map_matching)
134 {
135 return Error("TooBig", "Too many trace coordinates", result);
136 }
137
138 if (!CheckAllCoordinates(parameters.coordinates))
139 {
140 return Error("InvalidValue", "Invalid coordinate value.", result);
141 }
142
143 if (max_radius_map_matching > 0 && std::any_of(parameters.radiuses.begin(),
144 parameters.radiuses.end(),
145 [&](const auto &radius) {
146 if (!radius)
147 return false;
148 return *radius > max_radius_map_matching;
149 }))
150 {
151 return Error("TooBig", "Radius search size is too large for map matching.", result);
152 }
153
154 // Check for same or increasing timestamps. Impl. note: Incontrast to `sort(first,
155 // last, less_equal)` checking `greater` in reverse meets irreflexive requirements.
156 const auto time_increases_monotonically = std::is_sorted(
157 parameters.timestamps.rbegin(), parameters.timestamps.rend(), std::greater<>{});
158
159 if (!time_increases_monotonically)
160 {
161 return Error("InvalidValue", "Timestamps need to be monotonically increasing.", result);
162 }
163
164 SubMatchingList sub_matchings;
165 api::tidy::Result tidied;
166 if (parameters.tidy)
167 {
168 // Transparently tidy match parameters, do map matching on tidied parameters.
169 // Then use the mapping to restore the original <-> tidied relationship.
170 tidied = api::tidy::tidy(parameters);
171 }
172 else
173 {
174 tidied = api::tidy::keep_all(parameters);
175 }
176
177 // Error: first and last points should be waypoints
178 if (!parameters.waypoints.empty() &&
179 (tidied.parameters.waypoints[0] != 0 ||
180 tidied.parameters.waypoints.back() != (tidied.parameters.coordinates.size() - 1)))
181 {
182 return Error(
183 "InvalidValue", "First and last coordinates must be specified as waypoints.", result);
184 }
185
186 // assuming radius is the standard deviation of a normal distribution
187 // that models GPS noise (in this model), x3 should give us the correct
188 // search radius with > 99% confidence
189 std::vector<double> search_radiuses;
190 if (tidied.parameters.radiuses.empty())
191 {
192 search_radiuses.resize(tidied.parameters.coordinates.size(),
193 routing_algorithms::DEFAULT_GPS_PRECISION * RADIUS_MULTIPLIER);
194 }
195 else
196 {
197 search_radiuses.resize(tidied.parameters.coordinates.size());
198 std::transform(tidied.parameters.radiuses.begin(),
199 tidied.parameters.radiuses.end(),
200 search_radiuses.begin(),
201 [](const boost::optional<double> &maybe_radius) {
202 if (maybe_radius)
203 {
204 return *maybe_radius * RADIUS_MULTIPLIER;
205 }
206 else
207 {
208 return routing_algorithms::DEFAULT_GPS_PRECISION * RADIUS_MULTIPLIER;
209 }
210 });
211 }
212
213 auto candidates_lists =
214 GetPhantomNodesInRange(facade, tidied.parameters, search_radiuses, true);
215
216 filterCandidates(tidied.parameters.coordinates, candidates_lists);
217 if (std::all_of(candidates_lists.begin(),
218 candidates_lists.end(),
219 [](const std::vector<PhantomNodeWithDistance> &candidates) {
220 return candidates.empty();
221 }))
222 {
223 return Error("NoSegment",
224 std::string("Could not find a matching segment for any coordinate."),
225 result);
226 }
227
228 // call the actual map matching
229 sub_matchings =
230 algorithms.MapMatching(candidates_lists,
231 tidied.parameters.coordinates,
232 tidied.parameters.timestamps,
233 tidied.parameters.radiuses,
234 parameters.gaps == api::MatchParameters::GapsType::Split);
235
236 if (sub_matchings.size() == 0)
237 {
238 return Error("NoMatch", "Could not match the trace.", result);
239 }
240
241 // trace was split, we don't support the waypoints parameter across multiple match objects
242 if (sub_matchings.size() > 1 && !parameters.waypoints.empty())
243 {
244 return Error("NoMatch", "Could not match the trace with the given waypoints.", result);
245 }
246
247 // Error: Check if user-supplied waypoints can be found in the resulting matches
248 if (!parameters.waypoints.empty())
249 {
250 std::set<std::size_t> tidied_waypoints(tidied.parameters.waypoints.begin(),
251 tidied.parameters.waypoints.end());
252 for (const auto &sm : sub_matchings)
253 {
254 std::for_each(sm.indices.begin(),
255 sm.indices.end(),
256 [&tidied_waypoints](const auto index) { tidied_waypoints.erase(index); });
257 }
258 if (!tidied_waypoints.empty())
259 {
260 return Error("NoMatch", "Requested waypoint parameter could not be matched.", result);
261 }
262 }
263 // we haven't errored yet, only allow leg collapsing if it was originally requested
264 BOOST_ASSERT(parameters.waypoints.empty() || sub_matchings.size() == 1);
265 const auto collapse_legs = !parameters.waypoints.empty();
266
267 // each sub_route will correspond to a MatchObject
268 std::vector<InternalRouteResult> sub_routes(sub_matchings.size());
269 for (auto index : util::irange<std::size_t>(0UL, sub_matchings.size()))
270 {
271 BOOST_ASSERT(sub_matchings[index].nodes.size() > 1);
272
273 // FIXME we only run this to obtain the geometry
274 // The clean way would be to get this directly from the map matching plugin
275 PhantomNodes current_phantom_node_pair;
276 for (unsigned i = 0; i < sub_matchings[index].nodes.size() - 1; ++i)
277 {
278 current_phantom_node_pair.source_phantom = sub_matchings[index].nodes[i];
279 current_phantom_node_pair.target_phantom = sub_matchings[index].nodes[i + 1];
280 BOOST_ASSERT(current_phantom_node_pair.source_phantom.IsValid());
281 BOOST_ASSERT(current_phantom_node_pair.target_phantom.IsValid());
282 sub_routes[index].segment_end_coordinates.emplace_back(current_phantom_node_pair);
283 }
284 // force uturns to be on
285 // we split the phantom nodes anyway and only have bi-directional phantom nodes for
286 // possible uturns
287 sub_routes[index] =
288 algorithms.ShortestPathSearch(sub_routes[index].segment_end_coordinates, {false});
289 BOOST_ASSERT(sub_routes[index].shortest_path_weight != INVALID_EDGE_WEIGHT);
290 if (collapse_legs)
291 {
292 std::vector<bool> waypoint_legs;
293 waypoint_legs.reserve(sub_matchings[index].indices.size());
294 for (unsigned i = 0, j = 0; i < sub_matchings[index].indices.size(); ++i)
295 {
296 auto current_wp = tidied.parameters.waypoints[j];
297 if (current_wp == sub_matchings[index].indices[i])
298 {
299 waypoint_legs.push_back(true);
300 ++j;
301 }
302 else
303 {
304 waypoint_legs.push_back(false);
305 }
306 }
307 sub_routes[index] = CollapseInternalRouteResult(sub_routes[index], waypoint_legs);
308 }
309 }
310
311 api::MatchAPI match_api{facade, parameters, tidied};
312 match_api.MakeResponse(sub_matchings, sub_routes, result);
313
314 return Status::Ok;
315 }
316 } // namespace plugins
317 } // namespace engine
318 } // namespace osrm
319