7
#include <boost/program_options.hpp>
8
#include <boost/property_tree/ptree.hpp>
9
#include <boost/property_tree/json_parser.hpp>
10
#include <boost/optional.hpp>
11
#include <boost/format.hpp>
15
#include "midgard/encoded.h"
16
#include "baldr/graphreader.h"
17
#include "baldr/tilehierarchy.h"
18
#include "baldr/pathlocation.h"
19
#include "baldr/connectivity_map.h"
20
#include "loki/search.h"
21
#include "sif/costfactory.h"
22
#include "odin/directionsbuilder.h"
23
#include "odin/util.h"
24
#include "proto/trippath.pb.h"
25
#include "proto/tripdirections.pb.h"
26
#include "proto/directions_options.pb.h"
27
#include "midgard/logging.h"
28
#include "midgard/distanceapproximator.h"
29
#include "thor/astar.h"
30
#include "thor/bidirectional_astar.h"
31
#include "thor/multimodal.h"
32
#include "thor/trippathbuilder.h"
33
#include "thor/attributes_controller.h"
34
#include "thor/route_matcher.h"
36
using namespace valhalla::midgard;
37
using namespace valhalla::baldr;
38
using namespace valhalla::loki;
39
using namespace valhalla::odin;
40
using namespace valhalla::sif;
41
using namespace valhalla::thor;
43
namespace bpo = boost::program_options;
46
class PathStatistics {
47
std::pair<float, float> origin;
48
std::pair<float, float> destination;
58
PathStatistics (std::pair<float, float> p1, std::pair<float, float> p2)
59
: origin(p1), destination(p2), success("false"),
60
passes(0), runtime(), trip_time(),
61
trip_dist(), arc_dist(), manuevers() { }
63
void setSuccess(std::string s) { success = s; }
64
void incPasses(void) { ++passes; }
65
void addRuntime(uint32_t msec) { runtime += msec; }
66
void setTripTime(uint32_t t) { trip_time = t; }
67
void setTripDist(float d) { trip_dist = d; }
68
void setArcDist(float d) { arc_dist = d; }
69
void setManuevers(uint32_t n) { manuevers = n; }
71
valhalla::midgard::logging::Log(
72
(boost::format("%f,%f,%f,%f,%s,%d,%d,%d,%f,%f,%d")
73
% origin.first % origin.second % destination.first % destination.second
74
% success % passes % runtime % trip_time % trip_dist % arc_dist % manuevers).str(),
81
* Test a single path from origin to destination.
83
TripPath PathTest(GraphReader& reader, PathLocation& origin,
84
PathLocation& dest, PathAlgorithm* pathalgorithm,
85
const std::shared_ptr<DynamicCost>* mode_costing,
86
const TravelMode mode, PathStatistics& data,
87
bool multi_run, uint32_t iterations,
88
bool using_astar, bool match_test) {
89
auto t1 = std::chrono::high_resolution_clock::now();
90
std::vector<PathInfo> pathedges;
91
pathedges = pathalgorithm->GetBestPath(origin, dest, reader, mode_costing, mode);
92
cost_ptr_t cost = mode_costing[static_cast<uint32_t>(mode)];
94
if (pathedges.size() == 0) {
95
if (cost->AllowMultiPass()) {
96
LOG_INFO("Try again with relaxed hierarchy limits");
97
pathalgorithm->Clear();
98
float relax_factor = (using_astar) ? 16.0f : 8.0f;
99
float expansion_within_factor = (using_astar) ? 4.0f : 2.0f;
100
cost->RelaxHierarchyLimits(using_astar, expansion_within_factor);
101
pathedges = pathalgorithm->GetBestPath(origin, dest, reader, mode_costing, mode);
105
if (pathedges.size() == 0) {
106
// Return an empty trip path
110
auto t2 = std::chrono::high_resolution_clock::now();
111
uint32_t msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
112
LOG_INFO("PathAlgorithm GetBestPath took " + std::to_string(msecs) + " ms");
115
t1 = std::chrono::high_resolution_clock::now();
116
AttributesController controller;
117
TripPath trip_path = TripPathBuilder::Build(controller, reader, mode_costing,
118
pathedges, origin, dest,
119
std::list<PathLocation>{});
120
t2 = std::chrono::high_resolution_clock::now();
121
msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
122
LOG_INFO("TripPathBuilder took " + std::to_string(msecs) + " ms");
124
// Time how long it takes to clear the path
125
t1 = std::chrono::high_resolution_clock::now();
126
pathalgorithm->Clear();
127
t2 = std::chrono::high_resolution_clock::now();
128
msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
129
LOG_INFO("PathAlgorithm Clear took " + std::to_string(msecs) + " ms");
133
LOG_INFO("Testing RouteMatcher");
136
std::vector<PointLL> shape = decode<std::vector<PointLL>>(trip_path.shape());
138
// Use the shape to form a single edge correlation at the start and end of
139
// the shape (using heading).
140
std::vector<valhalla::baldr::Location> locations{shape.front(), shape.back()};
141
locations.front().heading_ = std::round(PointLL::HeadingAlongPolyline(shape, 30.f));
142
locations.back().heading_ = std::round(PointLL::HeadingAtEndOfPolyline(shape, 30.f));
144
std::shared_ptr<DynamicCost> cost = mode_costing[static_cast<uint32_t>(mode)];
145
const auto projections = Search(locations, reader, cost->GetEdgeFilter(), cost->GetNodeFilter());
146
std::vector<PathLocation> path_location;
147
for (auto loc : locations) {
148
path_location.push_back(projections.at(loc));
150
std::vector<PathInfo> path;
151
bool ret = RouteMatcher::FormPath(mode_costing, mode, reader, shape,
152
path_location, path);
154
LOG_INFO("RouteMatcher succeeded");
156
LOG_ERROR("RouteMatcher failed");
160
// Run again to see benefits of caching
162
uint32_t totalms = 0;
163
for (uint32_t i = 0; i < iterations; i++) {
164
t1 = std::chrono::high_resolution_clock::now();
165
pathedges = pathalgorithm->GetBestPath(origin, dest, reader, mode_costing, mode);
166
t2 = std::chrono::high_resolution_clock::now();
167
totalms += std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
168
pathalgorithm->Clear();
170
msecs = totalms / iterations;
171
LOG_INFO("PathAlgorithm GetBestPath average: " + std::to_string(msecs) + " ms");
178
//TODO: maybe move this into location.h if its actually useful elsewhere than here?
179
std::string to_string(const valhalla::baldr::Location& l) {
181
for (auto address : { &l.name_, &l.street_, &l.city_, &l.state_, &l.zip_, &l
186
s.erase(s.end() - 1);
190
//TODO: maybe move this into location.h if its actually useful elsewhere than here?
191
std::string to_json(const valhalla::baldr::Location& l) {
192
std::string json = "{";
194
json += std::to_string(l.latlng_.lat());
197
json += std::to_string(l.latlng_.lng());
199
json += ",\"type\":\"";
201
(l.stoptype_ == valhalla::baldr::Location::StopType::THROUGH) ?
206
json += ",\"heading\":";
210
if (!l.name_.empty()) {
211
json += ",\"name\":\"";
216
if (!l.street_.empty()) {
217
json += ",\"street\":\"";
222
if (!l.city_.empty()) {
223
json += ",\"city\":\"";
228
if (!l.state_.empty()) {
229
json += ",\"state\":\"";
234
if (!l.zip_.empty()) {
235
json += ",\"postal_code\":\"";
240
if (!l.country_.empty()) {
241
json += ",\"country\":\"";
253
std::string GetFormattedTime(uint32_t seconds) {
254
uint32_t hours = (uint32_t) seconds / 3600;
255
uint32_t minutes = ((uint32_t) (seconds / 60)) % 60;
256
std::string formattedTime = "";
259
formattedTime += std::to_string(hours);
260
formattedTime += (hours == 1) ? " hour" : " hours";
262
formattedTime += ", ";
267
formattedTime += std::to_string(minutes);
268
formattedTime += (minutes == 1) ? " minute" : " minutes";
270
return formattedTime;
274
TripDirections DirectionsTest(const DirectionsOptions& directions_options,
275
TripPath& trip_path, const PathLocation& origin,
276
const PathLocation& destination, PathStatistics& data) {
277
DirectionsBuilder directions;
278
TripDirections trip_directions = directions.Build(directions_options,
280
std::string units = (
281
directions_options.units()
282
== DirectionsOptions::Units::DirectionsOptions_Units_kKilometers ?
285
valhalla::midgard::logging::Log("From: " + std::to_string(origin),
287
valhalla::midgard::logging::Log("To: " + std::to_string(destination),
289
valhalla::midgard::logging::Log(
290
"==============================================", " [NARRATIVE] ");
291
for (int i = 0; i < trip_directions.maneuver_size(); ++i) {
292
const auto& maneuver = trip_directions.maneuver(i);
294
// Depart instruction
295
if (maneuver.has_depart_instruction()) {
296
valhalla::midgard::logging::Log(
297
(boost::format(" %s")
298
% maneuver.depart_instruction()).str(),
302
// Verbal depart instruction
303
if (maneuver.has_verbal_depart_instruction()) {
304
valhalla::midgard::logging::Log(
305
(boost::format(" VERBAL_DEPART: %s")
306
% maneuver.verbal_depart_instruction()).str(),
311
valhalla::midgard::logging::Log(
312
(boost::format("%d: %s | %.1f %s") % m++ % maneuver.text_instruction()
313
% maneuver.length() % units).str(),
316
// Verbal transition alert instruction
317
if (maneuver.has_verbal_transition_alert_instruction()) {
318
valhalla::midgard::logging::Log(
319
(boost::format(" VERBAL_ALERT: %s")
320
% maneuver.verbal_transition_alert_instruction()).str(),
324
// Verbal pre transition instruction
325
if (maneuver.has_verbal_pre_transition_instruction()) {
326
valhalla::midgard::logging::Log(
327
(boost::format(" VERBAL_PRE: %s")
328
% maneuver.verbal_pre_transition_instruction()).str(),
332
// Verbal post transition instruction
333
if (maneuver.has_verbal_post_transition_instruction()) {
334
valhalla::midgard::logging::Log(
335
(boost::format(" VERBAL_POST: %s")
336
% maneuver.verbal_post_transition_instruction()).str(),
340
// Arrive instruction
341
if (maneuver.has_arrive_instruction()) {
342
valhalla::midgard::logging::Log(
343
(boost::format(" %s")
344
% maneuver.arrive_instruction()).str(),
348
// Verbal arrive instruction
349
if (maneuver.has_verbal_arrive_instruction()) {
350
valhalla::midgard::logging::Log(
351
(boost::format(" VERBAL_ARRIVE: %s")
352
% maneuver.verbal_arrive_instruction()).str(),
356
if (i < trip_directions.maneuver_size() - 1)
357
valhalla::midgard::logging::Log(
358
"----------------------------------------------", " [NARRATIVE] ");
360
valhalla::midgard::logging::Log(
361
"==============================================", " [NARRATIVE] ");
362
valhalla::midgard::logging::Log(
363
"Total time: " + GetFormattedTime(trip_directions.summary().time()),
365
valhalla::midgard::logging::Log(
366
(boost::format("Total length: %.1f %s")
367
% trip_directions.summary().length() % units).str(),
369
if(origin.date_time_) {
370
valhalla::midgard::logging::Log(
371
"Departed at: " + *origin.date_time_,
374
if(destination.date_time_) {
375
valhalla::midgard::logging::Log(
376
"Arrived at: " + *destination.date_time_,
379
data.setTripTime(trip_directions.summary().time());
380
data.setTripDist(trip_directions.summary().length());
381
data.setManuevers(trip_directions.maneuver_size());
383
return trip_directions;
386
// Returns the costing method (created from the dynamic cost factory).
387
// Get the costing options. Merge in any request costing options that
388
// override those in the config.
389
valhalla::sif::cost_ptr_t get_costing(CostFactory<DynamicCost> factory,
390
boost::property_tree::ptree& request,
391
const std::string& costing) {
392
std::string method_options = "costing_options." + costing;
393
auto costing_options = request.get_child(method_options, {});
394
return factory.Create(costing, costing_options);
397
// Main method for testing a single path
398
int main(int argc, char *argv[]) {
399
bpo::options_description options("valhalla_run_route " VERSION "\n"
401
" Usage: valhalla_run_route [options]\n"
403
"valhalla_run_route is a simple command line test tool for shortest path routing. "
405
"Use the -o and -d options OR the -j option for specifying the locations. "
409
std::string origin, destination, routetype, json, config;
410
bool connectivity, multi_run, match_test;
411
connectivity = multi_run = match_test = false;
414
options.add_options()("help,h", "Print this help message.")(
415
"version,v", "Print the version of this software.")(
417
boost::program_options::value<std::string>(&origin),
418
"Origin: lat,lng,[through|stop],[name],[street],[city/town/village],[state/province/canton/district/region/department...],[zip code],[country].")(
420
boost::program_options::value<std::string>(&destination),
421
"Destination: lat,lng,[through|stop],[name],[street],[city/town/village],[state/province/canton/district/region/department...],[zip code],[country].")(
422
"type,t", boost::program_options::value<std::string>(&routetype),
423
"Route Type: auto|bicycle|pedestrian|auto-shorter")(
425
boost::program_options::value<std::string>(&json),
426
"JSON Example: '{\"locations\":[{\"lat\":40.748174,\"lon\":-73.984984,\"type\":\"break\",\"heading\":200,\"name\":\"Empire State Building\",\"street\":\"350 5th Avenue\",\"city\":\"New York\",\"state\":\"NY\",\"postal_code\":\"10118-0110\",\"country\":\"US\"},{\"lat\":40.749231,\"lon\":-73.968703,\"type\":\"break\",\"name\":\"United Nations Headquarters\",\"street\":\"405 East 42nd Street\",\"city\":\"New York\",\"state\":\"NY\",\"postal_code\":\"10017-3507\",\"country\":\"US\"}],\"costing\":\"auto\",\"directions_options\":{\"units\":\"miles\"}}'")
427
("connectivity", "Generate a connectivity map before testing the route.")
428
("match-test", "Test RouteMatcher with resulting shape.")
429
("multi-run", bpo::value<uint32_t>(&iterations), "Generate the route N additional times before exiting.")
430
// positional arguments
431
("config", bpo::value<std::string>(&config), "Valhalla configuration file");
434
bpo::positional_options_description pos_options;
435
pos_options.add("config", 1);
437
bpo::variables_map vm;
441
bpo::command_line_parser(argc, argv).options(options).positional(
446
} catch (std::exception &e) {
447
std::cerr << "Unable to parse command line options because: " << e.what()
448
<< "\n" << "This is a bug, please report it at " PACKAGE_BUGREPORT
453
if (vm.count("help")) {
454
std::cout << options << "\n";
458
if (vm.count("version")) {
459
std::cout << "valhalla_run_route " << VERSION << "\n";
463
if (vm.count("connectivity")) {
467
if (vm.count("match-test")) {
471
if (vm.count("multi-run")) {
475
// Directions options - set defaults
476
DirectionsOptions directions_options;
477
directions_options.set_units(
478
DirectionsOptions::Units::DirectionsOptions_Units_kMiles);
479
directions_options.set_language("en-US");
482
std::vector<valhalla::baldr::Location> locations;
484
// argument checking and verification
485
boost::property_tree::ptree json_ptree;
486
if (vm.count("json") == 0) {
487
for (auto arg : std::vector<std::string> { "origin", "destination", "type",
489
if (vm.count(arg) == 0) {
493
<< "> argument was not provided, but is mandatory when json is not provided\n\n";
494
std::cerr << options << "\n";
498
locations.push_back(valhalla::baldr::Location::FromCsv(origin));
499
locations.push_back(valhalla::baldr::Location::FromCsv(destination));
501
////////////////////////////////////////////////////////////////////////////
502
// Process json input
504
std::stringstream stream;
506
boost::property_tree::read_json(stream, json_ptree);
509
for (const auto& location : json_ptree.get_child("locations"))
510
locations.emplace_back(std::move(valhalla::baldr::Location::FromPtree(location.second)));
511
if (locations.size() < 2)
514
throw std::runtime_error(
515
"insufficiently specified required parameter 'locations'");
518
// Parse out the type of route - this provides the costing method to use
521
routetype = json_ptree.get<std::string>("costing");
523
throw std::runtime_error("No edge/node costing provided");
526
// Grab the directions options, if they exist
527
auto directions_options_ptree_ptr = json_ptree.get_child_optional(
528
"directions_options");
529
if (directions_options_ptree_ptr) {
530
directions_options = valhalla::odin::GetDirectionsOptions(
531
*directions_options_ptree_ptr);
534
// Grab the date_time, if is exists
535
auto date_time_ptr = json_ptree.get_child_optional("date_time");
537
auto date_time_type = (*date_time_ptr).get<int>("type");
538
auto date_time_value = (*date_time_ptr).get_optional<std::string>("value");
540
if (date_time_type == 0) // current
541
locations.front().date_time_ = "current";
542
else if (date_time_type == 1) // depart at
543
locations.front().date_time_ = date_time_value;
544
else if (date_time_type == 2) // arrive by
545
locations.back().date_time_ = date_time_value;
551
boost::property_tree::ptree pt;
552
boost::property_tree::read_json(config.c_str(), pt);
555
boost::optional<boost::property_tree::ptree&> logging_subtree = pt
556
.get_child_optional("thor.logging");
557
if (logging_subtree) {
558
auto logging_config = valhalla::midgard::ToMap<
559
const boost::property_tree::ptree&,
560
std::unordered_map<std::string, std::string> >(logging_subtree.get());
561
valhalla::midgard::logging::Configure(logging_config);
564
// Something to hold the statistics
565
uint32_t n = locations.size() - 1;
566
PathStatistics data({locations[0].latlng_.lat(), locations[0].latlng_.lng()},
567
{locations[n].latlng_.lat(), locations[n].latlng_.lng()});
569
// Crow flies distance between locations (km)
571
for (uint32_t i = 0; i < n; i++) {
572
d1 += locations[i].latlng_.Distance(locations[i+1].latlng_) * kKmPerMeter;
575
// Get something we can use to fetch tiles
576
valhalla::baldr::GraphReader reader(pt.get_child("mjolnir"));
578
auto t0 = std::chrono::high_resolution_clock::now();
581
CostFactory<DynamicCost> factory;
582
factory.Register("auto", CreateAutoCost);
583
factory.Register("auto_shorter", CreateAutoShorterCost);
584
factory.Register("bus", CreateBusCost);
585
factory.Register("bicycle", CreateBicycleCost);
586
factory.Register("pedestrian", CreatePedestrianCost);
587
factory.Register("truck", CreateTruckCost);
588
factory.Register("transit", CreateTransitCost);
590
// Figure out the route type
591
for (auto & c : routetype)
593
LOG_INFO("routetype: " + routetype);
595
// Get the costing method - pass the JSON configuration
598
std::shared_ptr<DynamicCost> mode_costing[4];
599
if (routetype == "multimodal") {
600
// Create array of costing methods per mode and set initial mode to
602
mode_costing[0] = get_costing(factory, json_ptree, "auto");
603
mode_costing[1] = get_costing(factory, json_ptree, "pedestrian");
604
mode_costing[2] = get_costing(factory, json_ptree, "bicycle");
605
mode_costing[3] = get_costing(factory, json_ptree, "transit");
606
mode = TravelMode::kPedestrian;
608
// Assign costing method, override any config options that are in the
610
std::shared_ptr<DynamicCost> cost = get_costing(factory,
611
json_ptree, routetype);
612
mode = cost->travel_mode();
613
mode_costing[static_cast<uint32_t>(mode)] = cost;
617
auto t1 = std::chrono::high_resolution_clock::now();
618
std::shared_ptr<DynamicCost> cost = mode_costing[static_cast<uint32_t>(mode)];
619
const auto projections = Search(locations, reader, cost->GetEdgeFilter(), cost->GetNodeFilter());
620
std::vector<PathLocation> path_location;
621
for (auto loc : locations) {
623
path_location.push_back(projections.at(loc));
624
//TODO: get transit level for transit costing
625
//TODO: if transit send a non zero radius
627
data.setSuccess("fail_invalid_origin");
632
// If we are testing connectivity
634
std::unordered_map<size_t, size_t> color_counts;
635
connectivity_map_t connectivity_map(pt.get_child("mjolnir"));
636
auto colors = connectivity_map.get_colors(TileHierarchy::levels().rbegin()->first,
637
path_location.back(), 0);
638
for(auto color : colors){
639
auto itr = color_counts.find(color);
640
if(itr == color_counts.cend())
641
color_counts[color] = 1;
646
//are all the locations in the same color regions
647
bool connected = false;
648
for(const auto& c : color_counts) {
649
if(c.second == locations.size()) {
655
LOG_INFO("No tile connectivity between locations");
656
data.setSuccess("fail_no_connectivity");
661
auto t2 = std::chrono::high_resolution_clock::now();
662
uint32_t msecs = std::chrono::duration_cast<std::chrono::milliseconds>(
664
LOG_INFO("Location Processing took " + std::to_string(msecs) + " ms");
667
AStarPathAlgorithm astar;
668
BidirectionalAStar bd;
669
MultiModalPathAlgorithm mm;
670
for (uint32_t i = 0; i < n; i++) {
671
// Choose path algorithm
672
PathAlgorithm* pathalgorithm;
673
if (routetype == "multimodal") {
675
} else if (routetype == "pedestrian") {
678
// Use bidirectional except for possible trivial cases
680
for (auto& edge1 : path_location[i].edges) {
681
for (auto& edge2 : path_location[i+1].edges) {
682
if (edge1.id == edge2.id) {
683
pathalgorithm = &astar;
688
bool using_astar = (pathalgorithm == &astar);
692
trip_path = PathTest(reader, path_location[i], path_location[i + 1],
693
pathalgorithm, mode_costing, mode, data, multi_run,
694
iterations, using_astar, match_test);
695
} catch (std::runtime_error& rte) {
696
LOG_ERROR("trip_path not found");
699
// If successful get directions
700
if (trip_path.node().size() > 0) {
701
// Try the the directions
702
t1 = std::chrono::high_resolution_clock::now();
703
TripDirections trip_directions = DirectionsTest(directions_options, trip_path,
704
path_location[i], path_location[i+1], data);
705
t2 = std::chrono::high_resolution_clock::now();
706
msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
708
auto trip_time = trip_directions.summary().time();
709
auto trip_length = trip_directions.summary().length() * 1609.344f;
710
LOG_INFO("trip_processing_time (ms)::" + std::to_string(msecs));
711
LOG_INFO("trip_time (secs)::" + std::to_string(trip_time));
712
LOG_INFO("trip_length (meters)::" + std::to_string(trip_length));
713
data.setSuccess("success");
715
// Check if origins are unreachable
716
bool unreachable_origin = false;
717
for (auto& edge : path_location[i].edges) {
718
const GraphTile* tile = reader.GetGraphTile(edge.id);
719
const DirectedEdge* directededge = tile->directededge(edge.id);
720
auto ei = tile->edgeinfo(directededge->edgeinfo_offset());
721
if (directededge->unreachable()) {
722
LOG_INFO("Origin edge is unconnected: wayid = " + std::to_string(ei.wayid()));
723
unreachable_origin = true;
725
LOG_INFO("Origin wayId = " + std::to_string(ei.wayid()));
728
// Check if destinations are unreachable
729
bool unreachable_dest = false;
730
for (auto& edge : path_location[i+1].edges) {
731
const GraphTile* tile = reader.GetGraphTile(edge.id);
732
const DirectedEdge* directededge = tile->directededge(edge.id);
733
auto ei = tile->edgeinfo(directededge->edgeinfo_offset());
734
if (directededge->unreachable()) {
735
LOG_INFO("Destination edge is unconnected: wayid = " + std::to_string(ei.wayid()));
736
unreachable_dest = true;
738
LOG_INFO("Destination wayId = " + std::to_string(ei.wayid()));
741
// Route was unsuccessful
742
if (unreachable_origin && unreachable_dest) {
743
data.setSuccess("fail_unreachable_locations");
744
} else if (unreachable_origin) {
745
data.setSuccess("fail_unreachable_origin");
746
} else if (unreachable_dest) {
747
data.setSuccess("fail_unreachable_dest");
749
data.setSuccess("fail_no_route");
754
// Set the arc distance. Convert to miles if needed
755
if (directions_options.units() == DirectionsOptions::Units::DirectionsOptions_Units_kMiles) {
760
// Time all stages for the stats file: location processing,
761
// path computation, trip path building, and directions
762
t2 = std::chrono::high_resolution_clock::now();
763
msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t0).count();
764
LOG_INFO("Total time= " + std::to_string(msecs) + " ms");
765
data.addRuntime(msecs);