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 &parameters,
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