1
#include <prime_server/prime_server.hpp>
3
using namespace prime_server;
10
#include <boost/property_tree/ptree.hpp>
11
#include <boost/property_tree/json_parser.hpp>
13
#include "baldr/json.h"
14
#include "baldr/graphconstants.h"
15
#include "baldr/directededge.h"
16
#include "midgard/logging.h"
17
#include "midgard/constants.h"
18
#include "baldr/errorcode_util.h"
19
#include "odin/util.h"
20
#include "odin/enhancedtrippath.h"
21
#include "proto/tripdirections.pb.h"
22
#include "proto/trippath.pb.h"
24
#include "thor/service.h"
25
#include "thor/attributes_controller.h"
26
#include "thor/match_result.h"
28
using namespace valhalla;
29
using namespace valhalla::midgard;
30
using namespace valhalla::baldr;
31
using namespace valhalla::sif;
32
using namespace valhalla::odin;
33
using namespace valhalla::thor;
37
const headers_t::value_type CORS { "Access-Control-Allow-Origin", "*" };
38
const headers_t::value_type JSON_MIME { "Content-type", "application/json;charset=utf-8" };
39
const headers_t::value_type JS_MIME { "Content-type", "application/javascript;charset=utf-8" };
41
json::MapPtr serialize(const AttributesController& controller,
42
const valhalla::odin::TripPath& trip_path,
43
const boost::optional<std::string>& id,
44
const DirectionsOptions& directions_options,
45
const std::vector<thor::MatchResult>& match_results) {
46
// Length and speed default to kilometers
48
if (directions_options.has_units()
49
&& directions_options.units() == DirectionsOptions::kMiles) {
53
// Loop over edges to add attributes
54
json::ArrayPtr edge_array = json::array({});
55
for (int i = 1; i < trip_path.node().size(); i++) {
57
if (trip_path.node(i-1).has_edge()) {
58
const auto& edge = trip_path.node(i - 1).edge();
61
auto edge_map = json::map({});
62
if (edge.has_truck_route())
63
edge_map->emplace("truck_route", static_cast<bool>(edge.truck_route()));
64
if (edge.has_truck_speed() && (edge.truck_speed() > 0))
65
edge_map->emplace("truck_speed", static_cast<uint64_t>(std::round(edge.truck_speed() * scale)));
66
if (edge.has_speed_limit() && (edge.speed_limit() > 0))
67
edge_map->emplace("speed_limit", static_cast<uint64_t>(std::round(edge.speed_limit() * scale)));
68
if (edge.has_density())
69
edge_map->emplace("density", static_cast<uint64_t>(edge.density()));
70
if (edge.has_sidewalk())
71
edge_map->emplace("sidewalk", to_string(edge.sidewalk()));
72
if (edge.has_bicycle_network())
73
edge_map->emplace("bicycle_network", static_cast<uint64_t>(edge.bicycle_network()));
74
if (edge.has_cycle_lane())
75
edge_map->emplace("cycle_lane", to_string(static_cast<CycleLane>(edge.cycle_lane())));
76
if (edge.has_lane_count())
77
edge_map->emplace("lane_count", static_cast<uint64_t>(edge.lane_count()));
78
if (edge.lane_connectivity_size()) {
79
auto lane_connectivity = json::array({});
80
for (const auto& l : edge.lane_connectivity()) {
81
auto element = json::map({});
82
element->emplace("from", l.from_way_id());
83
element->emplace("to_lanes", l.to_lanes());
84
element->emplace("from_lanes", l.from_lanes());
85
lane_connectivity->push_back(element);
87
edge_map->emplace("lane_connectivity", lane_connectivity);
89
if (edge.has_max_downward_grade())
90
edge_map->emplace("max_downward_grade", static_cast<int64_t>(edge.max_downward_grade()));
91
if (edge.has_max_upward_grade())
92
edge_map->emplace("max_upward_grade", static_cast<int64_t>(edge.max_upward_grade()));
93
if (edge.has_weighted_grade())
94
edge_map->emplace("weighted_grade", json::fp_t{edge.weighted_grade(), 3});
95
if (edge.has_mean_elevation()) {
96
// Convert to feet if a valid elevation and units are miles
97
float mean = edge.mean_elevation();
98
if (mean != kNoElevationData && directions_options.has_units()
99
&& directions_options.units() == DirectionsOptions::kMiles) {
100
mean *= kFeetPerMeter;
102
edge_map->emplace("mean_elevation", static_cast<int64_t>(mean));
104
if (edge.has_way_id())
105
edge_map->emplace("way_id", static_cast<uint64_t>(edge.way_id()));
107
edge_map->emplace("id", static_cast<uint64_t>(edge.id()));
108
if (edge.has_travel_mode())
109
edge_map->emplace("travel_mode", to_string(edge.travel_mode()));
110
if (edge.has_vehicle_type())
111
edge_map->emplace("vehicle_type", to_string(edge.vehicle_type()));
112
if (edge.has_pedestrian_type())
113
edge_map->emplace("pedestrian_type", to_string(edge.pedestrian_type()));
114
if (edge.has_bicycle_type())
115
edge_map->emplace("bicycle_type", to_string(edge.bicycle_type()));
116
if (edge.has_surface())
117
edge_map->emplace("surface", to_string(static_cast<baldr::Surface>(edge.surface())));
118
if (edge.has_drive_on_right())
119
edge_map->emplace("drive_on_right", static_cast<bool>(edge.drive_on_right()));
120
if (edge.has_internal_intersection())
121
edge_map->emplace("internal_intersection", static_cast<bool>(edge.internal_intersection()));
122
if (edge.has_roundabout())
123
edge_map->emplace("roundabout", static_cast<bool>(edge.roundabout()));
124
if (edge.has_bridge())
125
edge_map->emplace("bridge", static_cast<bool>(edge.bridge()));
126
if (edge.has_tunnel())
127
edge_map->emplace("tunnel", static_cast<bool>(edge.tunnel()));
128
if (edge.has_unpaved())
129
edge_map->emplace("unpaved", static_cast<bool>(edge.unpaved()));
131
edge_map->emplace("toll", static_cast<bool>(edge.toll()));
133
edge_map->emplace("use", to_string(static_cast<baldr::Use>(edge.use())));
134
if (edge.has_traversability())
135
edge_map->emplace("traversability", to_string(edge.traversability()));
136
if (edge.has_end_shape_index())
137
edge_map->emplace("end_shape_index", static_cast<uint64_t>(edge.end_shape_index()));
138
if (edge.has_begin_shape_index())
139
edge_map->emplace("begin_shape_index", static_cast<uint64_t>(edge.begin_shape_index()));
140
if (edge.has_end_heading())
141
edge_map->emplace("end_heading", static_cast<uint64_t>(edge.end_heading()));
142
if (edge.has_begin_heading())
143
edge_map->emplace("begin_heading", static_cast<uint64_t>(edge.begin_heading()));
144
if (edge.has_road_class())
145
edge_map->emplace("road_class", to_string(static_cast<baldr::RoadClass>(edge.road_class())));
146
if (edge.has_speed())
147
edge_map->emplace("speed", static_cast<uint64_t>(std::round(edge.speed() * scale)));
148
if (edge.has_length())
149
edge_map->emplace("length", json::fp_t{edge.length() * scale, 3});
150
if (edge.name_size() > 0) {
151
auto names_array = json::array({});
152
for (const auto& name : edge.name())
153
names_array->push_back(name);
154
edge_map->emplace("names", names_array);
158
if (edge.has_sign()) {
159
auto sign_map = json::map({});
161
// Populate exit number array
162
if (edge.sign().exit_number_size() > 0) {
163
auto exit_number_array = json::array({});
164
for (const auto& exit_number : edge.sign().exit_number()) {
165
exit_number_array->push_back(exit_number);
167
sign_map->emplace("exit_number", exit_number_array);
170
// Populate exit branch array
171
if (edge.sign().exit_branch_size() > 0) {
172
auto exit_branch_array = json::array({});
173
for (const auto& exit_branch : edge.sign().exit_branch()) {
174
exit_branch_array->push_back(exit_branch);
176
sign_map->emplace("exit_branch", exit_branch_array);
179
// Populate exit toward array
180
if (edge.sign().exit_toward_size() > 0) {
181
auto exit_toward_array = json::array({});
182
for (const auto& exit_toward : edge.sign().exit_toward()) {
183
exit_toward_array->push_back(exit_toward);
185
sign_map->emplace("exit_toward", exit_toward_array);
188
// Populate exit name array
189
if (edge.sign().exit_name_size() > 0) {
190
auto exit_name_array = json::array({});
191
for (const auto& exit_name : edge.sign().exit_name()) {
192
exit_name_array->push_back(exit_name);
194
sign_map->emplace("exit_name", exit_name_array);
197
edge_map->emplace("sign", sign_map);
200
// Process edge end node only if any node items are enabled
201
if (controller.category_attribute_enabled(kNodeCategory)) {
202
const auto& node = trip_path.node(i);
203
auto end_node_map = json::map({});
205
if (node.intersecting_edge_size() > 0) {
206
auto intersecting_edge_array = json::array({});
207
for (const auto& xedge : node.intersecting_edge()) {
208
auto xedge_map = json::map({});
209
if (xedge.has_walkability() && (xedge.walkability() != TripPath_Traversability_kNone))
210
xedge_map->emplace("walkability", to_string(xedge.walkability()));
211
if (xedge.has_cyclability() && (xedge.cyclability() != TripPath_Traversability_kNone))
212
xedge_map->emplace("cyclability", to_string(xedge.cyclability()));
213
if (xedge.has_driveability() && (xedge.driveability() != TripPath_Traversability_kNone))
214
xedge_map->emplace("driveability", to_string(xedge.driveability()));
215
xedge_map->emplace("from_edge_name_consistency", static_cast<bool>(xedge.prev_name_consistency()));
216
xedge_map->emplace("to_edge_name_consistency", static_cast<bool>(xedge.curr_name_consistency()));
217
xedge_map->emplace("begin_heading", static_cast<uint64_t>(xedge.begin_heading()));
219
intersecting_edge_array->emplace_back(xedge_map);
221
end_node_map->emplace("intersecting_edges", intersecting_edge_array);
224
if (node.has_elapsed_time())
225
end_node_map->emplace("elapsed_time", static_cast<uint64_t>(node.elapsed_time()));
226
if (node.has_admin_index())
227
end_node_map->emplace("admin_index", static_cast<uint64_t>(node.admin_index()));
229
end_node_map->emplace("type", to_string(static_cast<baldr::NodeType>(node.type())));
231
end_node_map->emplace("fork", static_cast<bool>(node.fork()));
232
if (node.has_time_zone())
233
end_node_map->emplace("time_zone", node.time_zone());
235
// TODO transit info at node
236
// kNodeTransitStopInfoType = "node.transit_stop_info.type";
237
// kNodeTransitStopInfoOnestopId = "node.transit_stop_info.onestop_id";
238
// kNodetransitStopInfoName = "node.transit_stop_info.name";
239
// kNodeTransitStopInfoArrivalDateTime = "node.transit_stop_info.arrival_date_time";
240
// kNodeTransitStopInfoDepartureDateTime = "node.transit_stop_info.departure_date_time";
241
// kNodeTransitStopInfoIsParentStop = "node.transit_stop_info.is_parent_stop";
242
// kNodeTransitStopInfoAssumedSchedule = "node.transit_stop_info.assumed_schedule";
243
// kNodeTransitStopInfoLatLon = "node.transit_stop_info.lat_lon";
245
edge_map->emplace("end_node", end_node_map);
248
// TODO - transit info on edge
249
// kEdgeTransitType = "edge.transit_type";
250
// kEdgeTransitRouteInfoOnestopId = "edge.transit_route_info.onestop_id";
251
// kEdgeTransitRouteInfoBlockId = "edge.transit_route_info.block_id";
252
// kEdgeTransitRouteInfoTripId = "edge.transit_route_info.trip_id";
253
// kEdgeTransitRouteInfoShortName = "edge.transit_route_info.short_name";
254
// kEdgeTransitRouteInfoLongName = "edge.transit_route_info.long_name";
255
// kEdgeTransitRouteInfoHeadsign = "edge.transit_route_info.headsign";
256
// kEdgeTransitRouteInfoColor = "edge.transit_route_info.color";
257
// kEdgeTransitRouteInfoTextColor = "edge.transit_route_info.text_color";
258
// kEdgeTransitRouteInfoDescription = "edge.transit_route_info.description";
259
// kEdgeTransitRouteInfoOperatorOnestopId = "edge.transit_route_info.operator_onestop_id";
260
// kEdgeTransitRouteInfoOperatorName = "edge.transit_route_info.operator_name";
261
// kEdgeTransitRouteInfoOperatorUrl = "edge.transit_route_info.operator_url";
263
edge_array->emplace_back(edge_map);
268
auto json = json::map({
269
{"edges", edge_array}
272
// Add result id, if supplied
274
json->emplace("id", *id);
277
if (trip_path.has_shape())
278
json->emplace("shape", trip_path.shape());
280
// Add matched points, if requested
281
if (controller.category_attribute_enabled(kMatchedCategory)
282
&& !match_results.empty()) {
283
auto match_points_array = json::array({});
284
for (const auto& match_result : match_results) {
285
auto match_points_map = json::map({});
287
// Process matched point
288
if (controller.attributes.at(kMatchedPoint)) {
289
match_points_map->emplace("lon", json::fp_t{match_result.lnglat.first,6});
290
match_points_map->emplace("lat", json::fp_t{match_result.lnglat.second,6});
293
// Process matched type
294
if (controller.attributes.at(kMatchedType)) {
295
switch (match_result.type) {
296
case thor::MatchResult::Type::kMatched:
297
match_points_map->emplace("type", std::string("matched"));
299
case thor::MatchResult::Type::kInterpolated:
300
match_points_map->emplace("type", std::string("interpolated"));
303
match_points_map->emplace("type", std::string("unmatched"));
308
// Process matched point edge index
309
if (controller.attributes.at(kMatchedEdgeIndex) && match_result.HasEdgeIndex())
310
match_points_map->emplace("edge_index", static_cast<uint64_t>(match_result.edge_index));
312
// Process matched point distance along edge
313
if (controller.attributes.at(kMatchedDistanceAlongEdge))
314
match_points_map->emplace("distance_along_edge", json::fp_t{match_result.distance_along,3});
316
// Process matched point distance from trace point
317
if (controller.attributes.at(kMatchedDistanceFromTracePoint))
318
match_points_map->emplace("distance_from_trace_point", json::fp_t{match_result.distance_from,3});
320
match_points_array->push_back(match_points_map);
322
json->emplace("matched_points", match_points_array);
326
if (trip_path.has_osm_changeset())
327
json->emplace("osm_changeset", trip_path.osm_changeset());
330
if (trip_path.admin_size() > 0) {
331
auto admin_array = json::array({});
332
for (const auto& admin : trip_path.admin()) {
333
auto admin_map = json::map({});
334
if (admin.has_country_code())
335
admin_map->emplace("country_code", admin.country_code());
336
if (admin.has_country_text())
337
admin_map->emplace("country_text", admin.country_text());
338
if (admin.has_state_code())
339
admin_map->emplace("state_code", admin.state_code());
340
if (admin.has_state_text())
341
admin_map->emplace("state_text", admin.state_text());
343
admin_array->push_back(admin_map);
345
json->emplace("admins", admin_array);
348
// Add units, if specified
349
if (directions_options.has_units()) {
350
json->emplace("units", std::string(
351
(directions_options.units() == valhalla::odin::DirectionsOptions::kKilometers)
352
? "kilometers" : "miles"));
363
void thor_worker_t::filter_attributes(const boost::property_tree::ptree& request, AttributesController& controller) {
364
std::string filter_action = request.get("filters.action", "");
366
if (filter_action.size() && filter_action == "include") {
367
controller.disable_all();
368
for (const auto& kv : request.get_child("filters.attributes"))
369
controller.attributes.at(kv.second.get_value<std::string>()) = true;
371
} else if (filter_action.size() && filter_action == "exclude") {
372
controller.enable_all();
373
for (const auto& kv : request.get_child("filters.attributes"))
374
controller.attributes.at(kv.second.get_value<std::string>()) = false;
377
controller.enable_all();
382
* The trace_attributes action takes a GPS trace or latitude, longitude positions
383
* from a portion of an existing route and returns detailed attribution along the
384
* portion of the route. This includes details for each section of road along the
385
* path as well as any intersections along the path.
387
worker_t::result_t thor_worker_t::trace_attributes(
388
const boost::property_tree::ptree &request,
389
const std::string &request_str, http_request_info_t& request_info) {
390
//get time for start of request
391
auto s = std::chrono::system_clock::now();
394
parse_locations(request);
395
parse_shape(request);
396
parse_costing(request);
397
parse_trace_config(request);
399
* A flag indicating whether the input shape is a GPS trace or exact points from a
400
* prior route run against the Valhalla road network. Knowing that the input is from
401
* Valhalla will allow an efficient “edge-walking” algorithm rather than a more extensive
402
* map-matching method. If true, this enforces to only use exact route match algorithm.
404
odin::TripPath trip_path;
405
std::vector<thor::MatchResult> match_results;
406
std::pair<odin::TripPath, std::vector<thor::MatchResult>> trip_match;
407
AttributesController controller;
408
filter_attributes(request, controller);
409
auto shape_match = STRING_TO_MATCH.find(request.get<std::string>("shape_match", "walk_or_snap"));
410
if (shape_match == STRING_TO_MATCH.cend())
411
throw valhalla_exception_t{400, 445};
413
// If the exact points from a prior route that was run against the Valhalla road network,
414
// then we can traverse the exact shape to form a path by using edge-walking algorithm
415
switch (shape_match->second) {
418
trip_path = route_match(controller);
419
if (trip_path.node().size() == 0)
420
throw valhalla_exception_t{400, 443};
421
} catch (const valhalla_exception_t& e) {
422
throw valhalla_exception_t{400, 443, shape_match->first + " algorithm failed to find exact route match. Try using shape_match:'walk_or_snap' to fallback to map-matching algorithm"};
425
// If non-exact shape points are used, then we need to correct this shape by sending them
426
// through the map-matching algorithm to snap the points to the correct shape
429
trip_match = map_match(controller, true);
430
trip_path = std::move(trip_match.first);
431
match_results = std::move(trip_match.second);
432
} catch (const valhalla_exception_t& e) {
433
throw valhalla_exception_t{400, 444, shape_match->first + " algorithm failed to snap the shape points to the correct shape."};
436
//If we think that we have the exact shape but there ends up being no Valhalla route match, then
437
// then we want to fallback to try and use meili map matching to match to local route network.
438
//No shortcuts are used and detailed information at every intersection becomes available.
440
trip_path = route_match(controller);
441
if (trip_path.node().size() == 0) {
442
LOG_WARN(shape_match->first + " algorithm failed to find exact route match; Falling back to map_match...");
444
trip_match = map_match(controller, true);
445
trip_path = std::move(trip_match.first);
446
match_results = std::move(trip_match.second);
447
} catch (const valhalla_exception_t& e) {
448
throw valhalla_exception_t{400, 444, shape_match->first + " algorithm failed to snap the shape points to the correct shape."};
455
auto id = request.get_optional<std::string>("id");
456
// Get the directions_options if they are in the request
457
DirectionsOptions directions_options;
458
auto options = request.get_child_optional("directions_options");
460
directions_options = valhalla::odin::GetDirectionsOptions(*options);
462
//serialize output to Thor
464
if (trip_path.node().size() > 0)
465
json = serialize(controller, trip_path, id, directions_options, match_results);
466
else throw valhalla_exception_t{400, 442};
468
//jsonp callback if need be
469
std::ostringstream stream;
470
auto jsonp = request.get_optional<std::string>("jsonp");
472
stream << *jsonp << '(';
477
// Get processing time for thor
478
auto e = std::chrono::system_clock::now();
479
std::chrono::duration<float, std::milli> elapsed_time = e - s;
480
//log request if greater than X (ms)
481
if (!healthcheck && !request_info.spare && (elapsed_time.count() / shape.size()) > (long_request / 1100)) {
482
LOG_WARN("thor::trace_attributes elapsed time (ms)::"+ std::to_string(elapsed_time.count()));
483
LOG_WARN("thor::trace_attributes exceeded threshold::"+ request_str);
484
midgard::logging::Log("valhalla_thor_long_request_trace_attributes", " [ANALYTICS] ");
486
http_response_t response(200, "OK", stream.str(), headers_t{CORS, jsonp ? JS_MIME : JSON_MIME});
487
response.from_info(request_info);
488
worker_t::result_t result{false};
489
result.messages.emplace_back(response.to_string());