-
Notifications
You must be signed in to change notification settings - Fork 660
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
Add support for ignoring live traffic closures for waypoints #2685
Conversation
2d49e0c
to
8a8c834
Compare
valhalla/sif/dynamiccost.h
Outdated
* @param edgeid GraphId of the opposing edge. | ||
* @return Returns true if the edge is filtered due to live traffic closure, false if not. | ||
*/ | ||
inline virtual bool FilterClosed(const baldr::DirectedEdge* edge, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im not a fan of making this public especially bcause it has some hidden state in there making use of filter_closures_
what if we make a generic public function like this:
inline virtual bool IsClosed(const baldr::DirectedEdge* edge,
const baldr::GraphTile* tile) const {
 return (flow_mask_ & baldr::kCurrentFlowMask) && tile->IsClosed(edge);
 }
and inside of the Filter
function we call it with the boolean filter_closures_
and inside of the allowed function we call it with the boolean ignore_closures
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the Allowed
function using ignore_closures
currently. Looks like its used during a-star search? Not sure of its impact if we move the ignore_closures
check there.
Even with that, the basic issue IMO, still stands i.e during search (specifically in is_search_filter_triggered
), how do we know the state of ignore_closures
? filter_closures
is already appropriately set taking ignore_closures
into account, so I thought it would be good to expose it indirectly via the Filter
function. I don't think doing so exposes an internal state, but rather rather exposes an API that just uses it.
Not sure how making IsClosed
public helps here, since we still need to access filter_closures_
during search. Maybe I'm missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I don't need to make FilterClosed
(or IsClosed
) public. I guess I can call costing.Filter(...)
inside is_search_filter_triggered
:
if ((road_class > min_road_class || road_class < max_road_class) ||
(search_filter.exclude_tunnel_ && edge->tunnel()) ||
(search_filter.exclude_bridge_ && edge->bridge()) ||
(search_filter.exclude_ramp_ && (edge->use() == Use::kRamp)) ||
(search_filter.exclude_closures_ && costing.Filter(edge, tile))) {
return true;
Here's how I think it will work:
ignore_closures: false
,exclude_closures: true
(both defaults):Costing::Filter
filters candidates due to closures Search filter is never called for candidates that are already removed byCosting::Filter
.ignore_closures: true
,exclude_closures: true or false
: This setsfilter_closures_
tofalse
andCosting::Filter
does not filter any candidates due to closures. search filter is also not triggered, sinceCosting::Filter
always returns0.0/false
ignore_closures: false
,exclude_closures: false
: This setsfilter_closures_
tofalse
andCosting::Filter
does not filter any candidates due to closures. search filter is also not triggered, sinceexclude_closures
isfalse
.
src/worker.cc
Outdated
// set the corresponding internal costing attribute, so that loki search | ||
// does not drop a start/end candidate when an edge is marked as closed due | ||
// to live traffic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT:
// set the corresponding internal costing attribute, so that loki search | |
// does not drop a start/end candidate when an edge is marked as closed due | |
// to live traffic | |
// we tell the costing to let all closed roads through so that we can do | |
// a secondary per-location filtering using lokis search_filter functionality |
dfa33a2
to
c94c782
Compare
@@ -825,7 +845,6 @@ void from_json(rapidjson::Document& doc, Options& options) { | |||
|
|||
// Parse all of the costing options in their specified order | |||
sif::ParseCostingOptions(doc, "/costing_options", options); | |||
options.set_costing(costing); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed, since its being set above:
if (valhalla::Costing_Enum_Parse(costing_str, &costing)) {
options.set_costing(costing);
} else {
throw valhalla_exception_t{125, "'" + costing_str + "'"};
}
src/loki/search.cc
Outdated
@@ -36,7 +38,8 @@ bool is_search_filter_triggered(const DirectedEdge* edge, | |||
if ((road_class > min_road_class || road_class < max_road_class) || | |||
(search_filter.exclude_tunnel_ && edge->tunnel()) || | |||
(search_filter.exclude_bridge_ && edge->bridge()) || | |||
(search_filter.exclude_ramp_ && (edge->use() == Use::kRamp))) { | |||
(search_filter.exclude_ramp_ && (edge->use() == Use::kRamp)) || | |||
(search_filter.exclude_closures_ && !costing.Filter(edge, tile))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it doesnt make sense for us to rely on the meaning of the costing filter here. for starters, this code path could never be hit if !costing.Filter
is true
because it means we would have filtered the edge out in the first place and never checked the search filters for it.
I suggest we just check if you want to exclude closures and if you are a costing that cares about current flow and current flow is 0. ie this:
(search_filter.exclude_closures_ && !costing.Filter(edge, tile))) { | |
(search_filter.exclude_closures_ && (costing.flow_mask() & baldr::kCurrentFlowMask) && tile->IsClosed(edge))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for starters, this code path could never be hit if !costing.Filter is true because it means we would have filtered the edge out in the first place and never checked the search filters for it
You're right, in that is_search_filter_triggered
is never called, IF exclude_closures
is true
. However, if exclude_closure
is false
, we internally make filter_closures: true
which results in !costing.Filter
returning false
and we end up invoking the search filter.
Basically there is never a case where we want Costing::Filter()
to pass a candidate which might be filtered due to closures, but then have is_search_filter_triggered
filter it out.
The change you suggested won't work if ignore_closures: true
and exclude_closures: true
, because the candidates will pass Costing::Filter
but then fail in is_search_filter_triggered
. So either we access the state of ignore_closures
here, or use DynmaicCost::IsClosed
by making it public (as that already takes ignore_closures
into account).
I'm leaning towards using the ignore_closures
value from the costing options pbf. Something like:
search_filter.exclude_closures = !costing_options.ignore_closures && location_options.exclude_closures
and then use it the way you suggested in is_search_filter_triggered
. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess im just saying that it makes no sense to do:
search_filter.exclude_closures_ && !costing.Filter(edge, tile)
this code says that if i want to exclude closures and the edge wasnt filtered before filter it now. it not being filtered before is not the same as saying it currently has a closure. there are many reasons an edge can be filtered, a closure is not the only reason.
let me think about this a bit more. there simply has to be a way to make this less confusing 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess im just saying that it makes no sense to do:
search_filter.exclude_closures_ && !costing.Filter(edge, tile)this code says that if i want to exclude closures and the edge wasnt filtered before filter it now. it not being filtered before is not the same as saying it currently has a closure. there are many reasons an edge can be filtered, a closure is not the only reason.
Understood. We don't have to use costing filter here. Thats why I suggested accessing ignore_closures
from the costing options here (or make DynmaicCost::IsClosed
public). We'll need it to respect its override behavior.
f3f3ddf
to
e0dcaf9
Compare
bench/thor/routes.cc
Outdated
@@ -81,7 +82,8 @@ boost::property_tree::ptree build_config(const char* live_traffic_tar) { | |||
|
|||
constexpr float kMaxRange = 256; | |||
|
|||
static void BM_UtrechtBidirectionalAstar(benchmark::State& state) { | |||
template <class ALGO> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated benchmark test to be able to run multiple algo's for the same test.
@mandeepsandhu do you think we should consider an alternate implementation where we treat closures at the beginning and ending of routes similar to not_through regions. ie we can route to get out of them but we dont go in them in the middle of the route? i worry that a user getting a different edge candidate simply because they snapped to a multi edge closure would be unexpected/desired. was breifly discussing this with @danpaz |
@@ -179,6 +180,8 @@ const std::unordered_map<unsigned, std::string> OSRM_ERRORS_CODES{ | |||
R"({"code":"InvalidValue","message":"The successfully parsed query parameters are invalid."})"}, | |||
{142, | |||
R"({"code":"InvalidValue","message":"The successfully parsed query parameters are invalid."})"}, | |||
{143, | |||
R"({"code":"InvalidValue","message":"The successfully parsed query parameters are invalid."})"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this error message specify what was invalid about the query?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once we can confirm that is the case and those test cases are failing, we can disable them and get thsi shipped with the idea that we will do smoe more work. specifically i think we should make starting and ending in a closure work like starting/ending in a not_thru area, where you are allowed to route inside of it until you leave it. as long as your predicessory label is marked that way you can keep expanding in there but as soon as the predecessor is not marked that way you can no longer expand edges like that. this will require us to know whether or not an edge is closed outside of the allowed method though which will be quite annoying...
I get how continuing to expand on consecutive closures would work when doing bidir a-star (since we start expansion from both ends). But what would the behavior be for the unidir a-star variants if there are multiple consecutive closures leading to the destination? When expanding from an intermediate open edge, we would avoid the closure, even though it might lead to the destination. If so, would it fail to find the route (what would happen if this was a not-thru edge instead)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i cant remember if the forward/reverse-only algos handle this, i have to check the code to refresh my memory on that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this error message specify what was invalid about the query?
The specific error message is described in the new internal error code. I think the error messages here are kept to match OSRM, which has this message for most error codes (@kevinkreiser CMIIW).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunate though, since when making a request with format=osrm
we lose any context the error message might have had. No change needed for this PR though.
test/gurka/test_search_filter.cc
Outdated
gurka::assert::raw::expect_path(result, {"HIC", "CFGD"}); | ||
|
||
// Specify search filter to disable exclude_closures at departure. Due to | ||
// consecutive closures, this should result in a "no route" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this test captures the multi-edge closure case, which we do want to return a valid route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For completeness we should also try a test that has closures not just at the origin and destination but also on edge in between, to ensure we allow closures at the endpoints but still avoid them along the route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For completeness we should also try a test that has closures not just at the origin and destination but also on edge in between, to ensure we allow closures at the endpoints but still avoid them along the route.
Intermediate closure scenario is covered in IgnoreClosuresOverridesExcludeClosures
testcase. We route from 1 -> 2
and close edge CD
. Without ignore_closures
costing option we don't route through CD which is an intermediate edge.
Would you rather see this a separate standalone test (and named appropriately)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of adding a new test case that closes the origin edge, destination edge, and an intermediate edge, to exercise the case where a user can snap to a closure but avoid closures in between the snapped edges. Maybe this logic path is covered already though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this test captures the multi-edge closure case, which we do want to return a valid route.
That would depend on whether there are suitable edges nearby, no? If loki's search results in only 1 candidate edge, then I guess if there are no reachable edges to it it would fail to find a route? CMIIW.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But in this test the request has exclude_closures=false
, so we know that there are suitable edges to snap to. Why then does it return a NoRoute?
29f2e7d
to
ca18da4
Compare
@@ -494,6 +495,7 @@ The codes correspond to code returned from a particular [Valhalla project](https | |||
|140 | Action does not support multimodal costing | | |||
|141 | Arrive by for multimodal not implemented yet | | |||
|142 | Arrive by not implemented for isochrones | | |||
|143 | ignore_closure in costing and exclude_closure in search_filter cannot both be specified | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the new exception type added which explains the error related to the new option.
f883d1d
to
a1cc150
Compare
@kevinkreiser @danpaz I've added more tests for the following scenarios:
CC: @danpat |
a1cc150
to
d2cd831
Compare
@@ -685,6 +720,43 @@ void from_json(rapidjson::Document& doc, Options& options) { | |||
options.set_linear_references(*linear_references); | |||
} | |||
|
|||
// costing defaults to none which is only valid for locate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code block was moved up before parsing locations so that we have the costing options parsed before hand. This helps us do the following checks:
- If
ignore_closure
is set, it overrides the defaultexclude_closure
behavior. - If both
ignore_closures
&exclude_closure
are set, we throw an exception back to the caller.
H---I | ||
|
||
|
||
L-4---5-M-6-N |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used for testing trivial cases which fallback to using timedep fwd
test/gurka/test_search_filter.cc
Outdated
}; | ||
test::customize_live_traffic_data(closure_map.config, close_edge); | ||
|
||
// TODO: 1->2 fails to get route without ignoring closure at 2. why? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found it odd that if I close edge DE
and route to 2
, its unable to find the route. However the same does not happen when I close JK
& route to 3
. I haven't found a good reason for this difference in behavior yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mandeepsandhu I wonder if it's node snapping since DE is shorter than JK. Can you try increasing the gridsize from 10 to 100 and see if the same happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the gridsize to 100 does stop the no route exception. When you say "node snapping", do you mean its snapping to E
? If so, shouldn't it still route upto D
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I also had to increase DE's length, otherwise its still unable to find the route
.str(); | ||
result = gurka::route(closure_map, req_disable_exclude_closures, reader); | ||
gurka::assert::osrm::expect_steps(result, {"CD", "AC", "AB", "BE"}); | ||
gurka::assert::raw::expect_path(result, {"CD", "CD", "AC", "AB", "BE"}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test currently fails if expecting a uturn on CD
(the actual path returned is "CD", "AC", "AB", "BE"
) even with a heading of 90º (going towards D
).
Seems like the uturn wouldn't occur with left-side driving?
2430dbf
to
0dc7df6
Compare
Performance impact of change: branch: | 50th | 90th | 99th
master | 0.03990912437438965 | 0.07622202237447102 | 0.19320416450500488
this branch | 0.04096802075703939 (+2.65%) | 0.07817689577738444 (+2.56%) | 0.19673490524291992 (+1.83%) Note: These are average of 3 runs for each branch with 20k route requests (pre-warmed tile-cache by running 20k requests before measuring performance of each branch). Also, for another set of test requests ( |
* Add support for ignoring live traffic closures for waypoints Adds support for ignoring road closures due to traffic for location waypoints, byt way of a new search_filter option - "exclude_closures" Roads which have live traffic speed of 0, are considered closed. If search_filter.exclude_closures is set to false for a location, then any outgoing edges that are marked closed, will be considered when loki searches for start/end candidates. * lint fix * an idea of how to have ignore_closures win over filter_closures when its turned on * Update filtering logic to override search_filter.exclude_closures if costing_options.ignore_closures is set * Reword api docs * Use Costing::Filter in is_search_filter_triggered and update changelog * typo * Cleanup and add more tests for conflicting options * Fixed astar algos to include destination closures. Broke prefferred side test * add expansion tracking for debugging purposes * Fix merge conflicts due to astar algo removal * lint fix * Add new internal exception code and update docs * lint fix * reorder top_speed & filter_closure in dynamiccost.h * More tests. Disable tests for consecutive closures * enable more costing profiles in tests * linting * clang-tidy fix * Address review comments * updated one of the disabled tests with comments on why its disabled * change to use intrusive ptr after merging latest master * modify the graph so that snapping happens where we expect * Added disabled consecutive closure test with reachability set * lint fix * remove the need to expose Costing::IsClosed and fix a bug in request parsing not overriding exclude_closure when only ignore_closures is set * rename filter to allowed to match the rest of the methods * refactor of filter function complete, now we can control how it works from loki::search and loki::reach * Track Distance in Isochrones and Generate Distance Contours Optionally (#2699) * templatize the value part of gridded data * remove templates and stub out for adding distance contours * [ci skip] **NO_CI** first implementation for isodistances; lacks distance tracker in siff::Cost * temp * distance for isos works now on top of time; no tests yet; no multimodal implementation yet * add to docs * address review comments; add path_distance to origin and destination edge labels * map orders internally.. anyways, switch to lighter syntax [ci skip] ***NO_CI*** * partial distance for origin/destination edges via directededges [ci skip] ***NO_CI*** * simplify isodistances addition by making gridded data class do more of the work * fix what must be a typo * update tests and test configs * bugs in dijkstras labels, still bugs in gridded data * typo in griddata, forgot to add initial feature to contour * all bugs squashed. performance checks remaining * clean up benching diffing server based tool. allow writing no output to disk. track progress more efficiently * fix benchmark configs * simplify isochrone limits check * reclaim performance loss * lose the ternaries as well * try to fix mac build * shave more dead code out of the gridded data interface * straighten out bug in max_time/distance to construct grid * satisfy linter * back to using std::max_element but this time the comparator is actually properly implemented as less than ;o) Co-authored-by: nilsnolde <nils.nolde@gmail.com> * optimizations (#2743) * reserve protobuf objects wherever possible in path building * add some reservations to the serialization code as well to derisk reallocs * convert serialization for valhalla format to rapidjson SAX writer * namespace missing * avoid hashing and indirection in simple tile cache * templatize double bucket queue to remove lambda calls. step on the way to merging edgelabel edgestatus and queue into one structure * fix build, maybe not tests * simplify derived uturn in bidirectional expansion * simplify expansion functions with less conditionals * remove recursion from expansion methods in standard algorithms * use route_index in serializer * allow using simple cache to avoid 8.5mb ram usage * clean up make_pair to have the std namespace prefix. not sure how it compiled without it... * cleanup * more cleanup * Enhance BSS import (#2575) * fix when projected edge's start node and end node are not in the same tile * refactor * split the bss connections for pedestrian and bicycle * refactor * update bssimport test * fix test * add test and use google test utils * remove accidentally added header * code cleaning after lint * fix test after merging master * fix windows compilation error; remove some uselss headers * lint * fix complilation bug * format Co-authored-by: Kevin Kreiser <kevinkreiser@gmail.com> * remove filter function and do filtering inline so we can control it by parameter. update reach to make use of this control * refactor the common stuff in the allowed method to the base class * lint * tidy * tidy again * lint again Co-authored-by: Mandeep Sandhu <mandeep.sandhu@mapbox.com> Co-authored-by: nilsnolde <nils.nolde@gmail.com> Co-authored-by: Patrick Qian <patrick.qian@kisio.com>
bench/thor/routes.cc
Outdated
@@ -11,6 +11,7 @@ | |||
#include "sif/costfactory.h" | |||
#include "test.h" | |||
#include "thor/bidirectional_astar.h" | |||
#include "thor/timedep.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we revert this file? i know this was a long running pr with many iterations but thsi seems irrelevant now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was using bench for seeing the impact of the change on timedep a-star implementations. We're no longer doing that in this PR now, so I could revert it. Although I feel its okay to test the timedep implementations in the bench tests for completeness.
@@ -93,7 +92,7 @@ def make_request(post_body): | |||
parser.add_argument('--test-file', type=str, help='The file with the test requests', required=True) | |||
parser.add_argument('--url', type=str, help='The url to which you want to POST the request bodies', default='http://localhost:8002/route') | |||
parser.add_argument('--output-dir', type=str, help='The directory in which to place the result of each request') | |||
parser.add_argument('--concurrency', type=int, help='The number of processes to use to make requests', default=psutil.cpu_count(logical=True)) | |||
parser.add_argument('--concurrency', type=int, help='The number of processes to use to make requests', default=multiprocessing.cpu_count()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was this something mac specific or?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. This happened on Linux as well. I didn't have psutil
installed so this script was failing. Since multiprocessing
has the cpu_count
I thought of using that instead of psutil
since its already in use here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i mean you can just pip install it but sure, one less thing seems better (i cant remember why i used that in the first place, maybe one works better under docker or something...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah 1 less package to install. I _ think_ this should work inside docker too.
@@ -62,7 +62,7 @@ class InstructionsSmallEndRampForks : public ::testing::Test { | |||
const auto layout = | |||
gurka::detail::map_to_coordinates(ascii_map, gridsize_metres, {5.1079374, 52.0887174}); | |||
|
|||
map = gurka::buildtiles(layout, ways, {}, {}, "test/data/gurka_instructions_keep_highway_signs", | |||
map = gurka::buildtiles(layout, ways, {}, {}, "test/data/gurka_instructions_small_end_ramp_forks", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was a collision with another test, looks like it was a copy paste fail
…e unused function from dynamiccost
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR looks clean to me now. Sorry for the long slog on this one!
Thanks for slogging with me on this one! :) Appreciate you taking the time to help me along the way 🙏 |
Performance impact after latest merge from master: branch: | 50th | 90th | 99th
master | 0.03852168718973795 | 0.07410685221354167 | 0.18502410252888998
this branch | 0.03876058260599772 (+0.62%) | 0.07470011711120605 (+0.8%) | 0.18315505981445312 (*-1.01%*) Note: These are average of 3 runs for each branch with 20k route requests (pre-warmed tile-cache by running 20k requests before measuring performance of each branch). RAD tests showed 0 diffs. I think we're good to merge this change. |
Adds support for ignoring road closures due to traffic for location
waypoints, by way of a new search_filter option - "exclude_closures"
Roads which have live traffic speed of 0, are considered closed. If
search_filter.exclude_closures is set to false for a location, then any
outgoing edges that are marked closed, will be considered when loki
searches for start/end candidates.