Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

path expansion endpoint #1849

Merged
merged 8 commits into from
Jun 17, 2019
Merged

path expansion endpoint #1849

merged 8 commits into from
Jun 17, 2019

Conversation

kevinkreiser
Copy link
Member

this adds a new endpoint to valhalla called /expansion which takes a normal /route style request but returns geojson. the geojson is a time-series dataset of which edges of the routing graph were touched by the path algorithm (only bidirectional a* implemented so far) in the order they were touched and what type of label was assigned to them.

the geojson is a small as possible (for such a verbose format). it returns only a single feature which has a geometry of type MultiLineString. Each LineString represents the beginning and end points (so only two coordinates) of each edge the algorithm touched.

the properties of the feature contain two parallel arrays (which match the number of edges/linestrings in the geometry). the first array is a list of the edge_ids and the second array is a list of the statuses. the statuses can be one of r (reached), s (settled) or c (connected).

this should work for multileg routes as well but there is no signifier of when one legs expansion history ends and the next one begins.

there should be little to no performance impact on the /route endpoint as the function that tracks this data is both inlined and wrapped in an if that says don't track this if we werent called from the /expansion action/endpont.

Copy link
Member

@dnesbitt61 dnesbitt61 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any impact on performance when not tracking expansion?

rapidjson::Pointer("/features/0/geometry/type").Set(*expansion_, "MultiLineString");
rapidjson::Pointer("/features/0/geometry/coordinates").Create(*expansion_).SetArray();
rapidjson::Pointer("/features/0/properties/edge_ids").Create(*expansion_).SetArray();
rapidjson::Pointer("/features/0/properties/statuses").Create(*expansion_).SetArray();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we fill out a template of the feature collection and the one feature that holds all the linestrings. if its a multileg route we have a problem where the last leg will win on setting which algorithm wsa used. seems like in the future we should ahve a separate feature for each leg

// serialize it
auto json = rapidjson::to_string(*expansion_, 5);
// tell the path algorithms to stop tracking the expansion at all
expansion_.reset();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so when we do this reset the algorithms will see that their weak_ptr doesnt point to anything and so wont track

rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
if (decimal_places >= 0)
writer.SetMaxDecimalPlaces(decimal_places);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes it easy for us to truncate all precision to a certain value, which is nice for geojson where we can just say we only care about 5 places for example

@kevinkreiser
Copy link
Member Author

@dnesbitt61 shouldnt be any performance impact, i was very careful about that. i can do performance testing on my own but would there but a specific test suite that you would recommend to verify that?

@dnesbitt61
Copy link
Member

I always check Lancaster to San Diego as my long test route, though any long route would work. valhalla_run_route with multi-run option works best (N successive path generation times are averaged to give the result - I usually use 100). I tried to keep a running tally of where this was, but now I have a new machine. I could try before and after with this branch but I have data without hierarchies and shortcuts.

dnesbitt61
dnesbitt61 previously approved these changes Jun 14, 2019
Copy link
Member

@dnesbitt61 dnesbitt61 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested performance and looks fine (just a very small drop 1-2%). Code looks good to me, but didn't test the actual generation of GeoJSON. Will leave that to others!

@kevinkreiser
Copy link
Member Author

I noticed a little bit more of a performance penalty locally using std::weak_ptr so i changed the whole thing around and decided to pass an std::function into the path algorithm that allows you to track what the algorithm is doing from the outside. this is cool for few reasons.

  1. its more general. we are using it to do make some geojson but you could do lots of other stuff with it
  2. it keeps the logic out of the path algorithm an in the action/api layer
  3. its faster. in my tests there was no difference with this code and master:
BEFORE:
kkreiser@kcarbon:~/sandbox/valhalla/valhalla/master$ ./valhalla_run_route --config ../valhalla.json --multi-run 100 -j '{"locations":[{"lat":32.697958,"lon":-117.174648,"street":"Park Boulevard"},{"lat":40.042553,"lon":-76.299115}],"costing":"auto"}' | grep -F average
2019/06/14 20:49:13.476930 [INFO] PathAlgorithm GetBestPath average: 120 ms
kkreiser@kcarbon:~/sandbox/valhalla/valhalla/master$ ./valhalla_run_route --config ../valhalla.json --multi-run 100 -j '{"locations":[{"lat":32.697958,"lon":-117.174648,"street":"Park Boulevard"},{"lat":40.042553,"lon":-76.299115}],"costing":"auto"}' | grep -F average
2019/06/14 20:49:27.358655 [INFO] PathAlgorithm GetBestPath average: 119 ms
kkreiser@kcarbon:~/sandbox/valhalla/valhalla/master$ ./valhalla_run_route --config ../valhalla.json --multi-run 100 -j '{"locations":[{"lat":32.697958,"lon":-117.174648,"street":"Park Boulevard"},{"lat":40.042553,"lon":-76.299115}],"costing":"auto"}' | grep -F average
2019/06/14 20:49:41.409925 [INFO] PathAlgorithm GetBestPath average: 119 ms
AFTER:
kkreiser@kcarbon:~/sandbox/valhalla/valhalla/build$ ./valhalla_run_route --config ../valhalla.json --multi-run 100 -j '{"locations":[{"lat":32.697958,"lon":-117.174648,"street":"Park Boulevard"},{"lat":40.042553,"lon":-76.299115}],"costing":"auto"}' | grep -F average
2019/06/14 20:48:29.718511 [INFO] PathAlgorithm GetBestPath average: 118 ms
kkreiser@kcarbon:~/sandbox/valhalla/valhalla/build$ ./valhalla_run_route --config ../valhalla.json --multi-run 100 -j '{"locations":[{"lat":32.697958,"lon":-117.174648,"street":"Park Boulevard"},{"lat":40.042553,"lon":-76.299115}],"costing":"auto"}' | grep -F average
2019/06/14 20:48:43.827638 [INFO] PathAlgorithm GetBestPath average: 118 ms
kkreiser@kcarbon:~/sandbox/valhalla/valhalla/build$ ./valhalla_run_route --config ../valhalla.json --multi-run 100 -j '{"locations":[{"lat":32.697958,"lon":-117.174648,"street":"Park Boulevard"},{"lat":40.042553,"lon":-76.299115}],"costing":"auto"}' | grep -F average
2019/06/14 20:48:57.987518 [INFO] PathAlgorithm GetBestPath average: 120 ms

@@ -181,6 +181,10 @@ void BidirectionalAStar::ExpandForward(GraphReader& graphreader,
(pred.not_thru_pruning() || !directededge->not_thru()));
adjacencylist_forward_->add(idx);
*es = {EdgeSet::kTemporary, idx};

// setting this edge as reached
if (expansion_callback_)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::function<> has an operator bool which lets you check if its actually assigned to something callable, this check is way faster than std::weak_ptr<>::lock

@@ -754,7 +787,7 @@ std::vector<PathInfo> BidirectionalAStar::FormPath(GraphReader& graphreader) {
cost += edgelabel.cost() - edgelabels_reverse_[predidx].cost();
}
cost += tc;
path.emplace_back(edgelabel.mode(), cost.secs, oppedge, 0, cost.cost);
path.emplace_back(edgelabel.mode(), cost.secs, edgelabel.opp_edgeid(), 0, cost.cost);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a simplification. notice above that we go fetch the opposing edge id the hard way. the edge labels for bidirectional already have the opposing edge id on them so i just used that instead. @dnesbitt61 there's not a gotcha here right?

rapidjson::Pointer("/features/0/geometry/type").Set(dom, "MultiLineString");
rapidjson::Pointer("/features/0/geometry/coordinates").Create(dom).SetArray();
rapidjson::Pointer("/features/0/properties/edge_ids").Create(dom).SetArray();
rapidjson::Pointer("/features/0/properties/statuses").Create(dom).SetArray();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we start by prepopulating the feature

.Get(dom)
->GetArray()
.PushBack(rapidjson::Value{}.SetString(status, a), a);
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we inject this lambda into the path algorithm and it calls us back with it. here we are building geojson but we could do something else with this information if we wanted.

&bidir_astar,
}) {
alg->set_track_expansion(track_expansion);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where we tell all the algorithms to actually use our lambda to track stuff as the algorithm progresses

&astar,
&bidir_astar,
}) {
alg->set_track_expansion(nullptr);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where we tell all the algorithms that they no longer have a callback to call

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants