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

Fix NoRoute for waypoints with low reachability edges #2914

Merged
merged 18 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* FIXED: Ensure route summaries are unique among all returned route/legs [#2874](https://github.com/valhalla/valhalla/pull/2874)
* FIXED: Fix compilation errors when boost < 1.68 and libprotobuf < 3.6 [#2878](https://github.com/valhalla/valhalla/pull/2878)
* FIXED: Allow u-turns at no-access barriers when forced by heading [#2875](https://github.com/valhalla/valhalla/pull/2875)
* FIXED: Fixed "No route found" error in case of multipoint request with locations near low reachability edges [#2914](https://github.com/valhalla/valhalla/pull/2914)
* FIXED: Skip bindings if there's no Python development version [#2893](https://github.com/valhalla/valhalla/pull/2878)

* **Enhancement**
Expand Down
4 changes: 4 additions & 0 deletions src/baldr/pathlocation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,9 @@ bool PathLocation::shares_edges(const PathLocation& other) const {
return true;
}

bool PathLocation::is_high_reachable(const PathEdge& edge) const {
Copy link
Member

Choose a reason for hiding this comment

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

nit: should be is_highly_reachable or has_high_reach

return edge.inbound_reach >= min_inbound_reach_ && edge.outbound_reach >= min_outbound_reach_;
}

} // namespace baldr
} // namespace valhalla
2 changes: 1 addition & 1 deletion src/loki/search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ struct bin_handler_t {
auto opposing_edge_id = reader.GetOpposingEdgeId(candidate.edge_id, other_edge, other_tile);

if (other_edge && costing->Allowed(other_edge, other_tile)) {
auto reach = get_reach(opposing_edge_id, other_edge);
Copy link
Member

Choose a reason for hiding this comment

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

hahaha oops! thanks for removing the shadowing!

reach = get_reach(opposing_edge_id, other_edge);
PathLocation::PathEdge other_path_edge{opposing_edge_id, 1 - length_ratio, candidate.point,
distance, flip_side(side), reach.outbound,
reach.inbound};
Expand Down
220 changes: 161 additions & 59 deletions src/thor/route_action.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,42 @@ void via_discontinuity(
}
}

inline bool is_through_point(const valhalla::Location& l) {
return l.type() == valhalla::Location::kThrough || l.type() == valhalla::Location::kBreakThrough;
}

inline bool is_break_point(const valhalla::Location& l) {
return l.type() == valhalla::Location::kBreak || l.type() == valhalla::Location::kBreakThrough;
}

inline bool is_high_reachable(const valhalla::Location& loc,
kevinkreiser marked this conversation as resolved.
Show resolved Hide resolved
const valhalla::Location_PathEdge& edge) {
return edge.inbound_reach() >= loc.minimum_reachability() &&
edge.outbound_reach() >= loc.minimum_reachability();
}

inline void remove_edges_with_low_reachability(valhalla::Location& loc) {
auto new_end = std::remove_if(loc.mutable_path_edges()->begin(), loc.mutable_path_edges()->end(),
[&loc](const auto& pe) { return !is_high_reachable(loc, pe); });
int start_idx = std::distance(loc.mutable_path_edges()->begin(), new_end);
loc.mutable_path_edges()->DeleteSubrange(start_idx, loc.path_edges_size() - start_idx);

new_end = std::remove_if(loc.mutable_filtered_edges()->begin(), loc.mutable_filtered_edges()->end(),
[&loc](const auto& pe) { return !is_high_reachable(loc, pe); });
start_idx = std::distance(loc.mutable_filtered_edges()->begin(), new_end);
loc.mutable_filtered_edges()->DeleteSubrange(start_idx, loc.filtered_edges_size() - start_idx);
}

inline void remove_all_edges_except_one(valhalla::Location& loc, const GraphId& edgeid) {
kevinkreiser marked this conversation as resolved.
Show resolved Hide resolved
while (loc.path_edges_size() > 1) {
if (loc.path_edges().rbegin()->graph_id() == edgeid) {
loc.mutable_path_edges()->SwapElements(0, loc.path_edges_size() - 1);
}
loc.mutable_path_edges()->RemoveLast();
}
loc.mutable_filtered_edges()->Clear();
}

/**
// removes any edges from the location that aren't connected to it (because of radius)
void remove_edges(const GraphId& edge_id, valhalla::Location& loc, GraphReader& reader) {
Copy link
Member

Choose a reason for hiding this comment

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

delete this function i mean, since you are doing it a different way

Expand Down Expand Up @@ -382,10 +418,6 @@ std::vector<std::vector<thor::PathInfo>> thor_worker_t::get_path(PathAlgorithm*
}
}

// All or nothing
if (paths.empty()) {
throw valhalla_exception_t{442};
}
return paths;
}

Expand All @@ -395,38 +427,33 @@ void thor_worker_t::path_arrive_by(Api& api, const std::string& costing) {
GraphId first_edge;
std::unordered_map<size_t, std::pair<EdgeTrimmingInfo, EdgeTrimmingInfo>> vias;
std::vector<thor::PathInfo> path;
auto& correlated = *api.mutable_options()->mutable_locations();
std::vector<std::string> algorithms;
api.mutable_trip()->mutable_routes()->Reserve(api.options().alternates() + 1);
const Options& options = api.options();
valhalla::Trip& trip = *api.mutable_trip();
trip.mutable_routes()->Reserve(options.alternates() + 1);

// For each pair of locations
for (auto origin = ++correlated.rbegin(); origin != correlated.rend(); ++origin) {
auto route_two_points = [&, this](auto& origin, auto& destination) -> bool {
// Get the algorithm type for this location pair
auto destination = std::prev(origin);
thor::PathAlgorithm* path_algorithm =
get_path_algorithm(costing, *origin, *destination, api.options());
thor::PathAlgorithm* path_algorithm = get_path_algorithm(costing, *origin, *destination, options);
path_algorithm->Clear();
algorithms.push_back(path_algorithm->name());
LOG_INFO(std::string("algorithm::") + path_algorithm->name());

// TODO: delete this and send all cases to the function above
Copy link
Member

Choose a reason for hiding this comment

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

so since we deleted this, then can we delete that function i had lying around above and also delete this comment?

// If we are continuing through a location we need to make sure we
// only allow the edge that was used previously (avoid u-turns)
bool through = destination->type() == valhalla::Location::kThrough ||
destination->type() == valhalla::Location::kBreakThrough;
while (through && first_edge.Is_Valid() && destination->path_edges_size() > 1) {
if (destination->path_edges().rbegin()->graph_id() == first_edge) {
destination->mutable_path_edges()->SwapElements(0, destination->path_edges_size() - 1);
}
destination->mutable_path_edges()->RemoveLast();
if (is_through_point(*destination) && first_edge.Is_Valid()) {
remove_all_edges_except_one(*destination, first_edge);
}

// Get best path and keep it
auto temp_paths = get_path(path_algorithm, *origin, *destination, costing, api.options());
auto temp_paths = get_path(path_algorithm, *origin, *destination, costing, options);
if (temp_paths.empty())
return false;
kevinkreiser marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

nit: this line isnt covered because your unit test doesnt use date_time type=2 (arrive_by). you could increase coverage by adding another permutation just in case to make sure that the fix works for arrive_by as well.


for (auto& temp_path : temp_paths) {
// back propagate time information
if (destination->has_date_time() &&
api.options().date_time_type() != valhalla::Options::invariant) {
if (destination->has_date_time() && options.date_time_type() != valhalla::Options::invariant) {
auto origin_dt = offset_date(*reader, destination->date_time(), temp_path.back().edgeid,
-temp_path.back().elapsed_cost.secs, temp_path.front().edgeid);
origin->set_date_time(origin_dt);
Expand Down Expand Up @@ -455,32 +482,29 @@ void thor_worker_t::path_arrive_by(Api& api, const std::string& costing) {

// Build trip path for this leg and add to the result if this
// location is a BREAK or if this is the last location
if (origin->type() == valhalla::Location::kBreak ||
origin->type() == valhalla::Location::kBreakThrough) {
if (is_break_point(*origin)) {
// Move destination back to the last break and collect the throughs
std::list<valhalla::Location> throughs;
while (destination->type() != valhalla::Location::kBreak &&
destination->type() != valhalla::Location::kBreakThrough) {
while (!is_break_point(*destination)) {
throughs.push_back(*destination);
--destination;
}

// We have to flip the via indices because we built them in backwards order
decltype(vias) flipped;
std::remove_reference<decltype(vias)>::type flipped;
kevinkreiser marked this conversation as resolved.
Show resolved Hide resolved
flipped.reserve(vias.size());
for (const auto& kv : vias)
flipped.emplace(path.size() - kv.first, kv.second);
vias.swap(flipped);

// Form output information based on path edges
if (api.trip().routes_size() == 0 || api.options().alternates() > 0) {
route = api.mutable_trip()->mutable_routes()->Add();
route->mutable_legs()->Reserve(correlated.size());
if (trip.routes_size() == 0 || options.alternates() > 0) {
route = trip.mutable_routes()->Add();
route->mutable_legs()->Reserve(options.locations_size());
}
auto& leg = *route->mutable_legs()->Add();
TripLegBuilder::Build(api.options(), controller, *reader, mode_costing, path.begin(),
path.end(), *origin, *destination, throughs, leg, algorithms, interrupt,
&vias);
TripLegBuilder::Build(options, controller, *reader, mode_costing, path.begin(), path.end(),
*origin, *destination, throughs, leg, algorithms, interrupt, &vias);
path.clear();
vias.clear();
}
Expand All @@ -489,50 +513,87 @@ void thor_worker_t::path_arrive_by(Api& api, const std::string& costing) {
// if we just made a leg that means we are done recording which algorithms were used
if (path.empty())
algorithms.clear();
}

return true;
};

auto correlated = options.locations();
bool allow_retry = true;

// For each pair of locations
auto origin = ++correlated.rbegin();
while (origin != correlated.rend()) {
auto destination = std::prev(origin);
if (!route_two_points(origin, destination)) {
// if routing failed because an intermediate waypoint was snapped to the low reachability road
// (such road lies in a small connectivity component that is not connected to other locations)
// we should leave only high reachability candidates and try to route again
if (allow_retry && is_through_point(*destination) &&
kevinkreiser marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

cant this happen on other locations types than through though? like even the break or via types could fail right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

for break and via types we use all path edges. it means if we failed to find a route it's definitely not a connectivity problem, so it doesn't make sense to remove low reachable edges and retry

Copy link
Member

Choose a reason for hiding this comment

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

haha i made the mistake of leaving this comment on the arrive_by which is harder to think about. let me see if i can mentally find a crack in the forward direction (depart_at). i just need to mull it over a bit to see if it handles everything we're worried about sorry for the confusion, just want to really understand the changes to this critical section ;)

!is_high_reachable(*destination, destination->path_edges(0))) {
Copy link
Member

Choose a reason for hiding this comment

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

the reason why 0 works here is only because its a through which means we've culled all other edges but the matching ones? what about on the first leg of the route where we havent dont any culling yet, why does 0 always work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm, good question. I though that the first (last) point can't be a through type

Copy link
Member

Choose a reason for hiding this comment

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

youre right it cant!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should we handle it somehow ?

allow_retry = false;
// for each intermediate waypoint remove candidates with low reachability
correlated = options.locations();
for (auto loc = std::next(correlated.begin()); loc != std::prev(correlated.end()); ++loc) {
remove_edges_with_low_reachability(*loc);
// it doesn't make sense to continue if there are no more path edges
if (loc->path_edges_size() == 0)
// no route found
throw valhalla_exception_t{442};
}
// reset state and try to route again
route = nullptr;
first_edge = {};
vias.clear();
path.clear();
algorithms.clear();
trip.mutable_routes()->Clear();
origin = ++correlated.rbegin();
continue;
Copy link
Member

Choose a reason for hiding this comment

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

resets the entire state of all the legs of the route and starts completely over from the beginning doing all the legs over.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is it proposition for comment ? to make more detailed ?

Copy link
Member

Choose a reason for hiding this comment

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

It's just me clarifying my understanding or anyone else's who is following along haha

}
// no route found
throw valhalla_exception_t{442};
}
++origin;
}
// Reverse the legs because protobuf only has adding to the end
std::reverse(route->mutable_legs()->begin(), route->mutable_legs()->end());
// assign changed locations
*api.mutable_options()->mutable_locations() = std::move(correlated);
}

void thor_worker_t::path_depart_at(Api& api, const std::string& costing) {
// Things we'll need
GraphId last_edge;
TripRoute* route = nullptr;
GraphId last_edge;
std::unordered_map<size_t, std::pair<EdgeTrimmingInfo, EdgeTrimmingInfo>> vias;
std::vector<thor::PathInfo> path;
std::list<valhalla::TripLeg> trip_paths;
auto& correlated = *api.mutable_options()->mutable_locations();
std::vector<std::string> algorithms;
api.mutable_trip()->mutable_routes()->Reserve(api.options().alternates() + 1);
const Options& options = api.options();
valhalla::Trip& trip = *api.mutable_trip();
trip.mutable_routes()->Reserve(options.alternates() + 1);

// For each pair of locations
for (auto destination = ++correlated.begin(); destination != correlated.end(); ++destination) {
auto route_two_locations = [&, this](auto& origin, auto& destination) -> bool {
// Get the algorithm type for this location pair
auto origin = std::prev(destination);
thor::PathAlgorithm* path_algorithm =
get_path_algorithm(costing, *origin, *destination, api.options());
thor::PathAlgorithm* path_algorithm = get_path_algorithm(costing, *origin, *destination, options);
path_algorithm->Clear();
algorithms.push_back(path_algorithm->name());
LOG_INFO(std::string("algorithm::") + path_algorithm->name());

// TODO: delete this and send all cases to the function above
// If we are continuing through a location we need to make sure we
// only allow the edge that was used previously (avoid u-turns)
bool through = origin->type() == valhalla::Location::kThrough ||
origin->type() == valhalla::Location::kBreakThrough;
while (through && last_edge.Is_Valid() && origin->path_edges_size() > 1) {
if (origin->path_edges().rbegin()->graph_id() == last_edge) {
origin->mutable_path_edges()->SwapElements(0, origin->path_edges_size() - 1);
}
origin->mutable_path_edges()->RemoveLast();
if (is_through_point(*origin) && last_edge.Is_Valid()) {
remove_all_edges_except_one(*origin, last_edge);
}

// Get best path and keep it
auto temp_paths = get_path(path_algorithm, *origin, *destination, costing, api.options());
auto temp_paths = get_path(path_algorithm, *origin, *destination, costing, options);
if (temp_paths.empty())
return false;

for (auto& temp_path : temp_paths) {
// forward propagate time information
if (origin->has_date_time() && api.options().date_time_type() != valhalla::Options::invariant) {
if (origin->has_date_time() && options.date_time_type() != valhalla::Options::invariant) {
auto destination_dt =
offset_date(*reader, origin->date_time(), temp_path.front().edgeid,
temp_path.back().elapsed_cost.secs, temp_path.back().edgeid);
Expand Down Expand Up @@ -563,23 +624,21 @@ void thor_worker_t::path_depart_at(Api& api, const std::string& costing) {

// Build trip path for this leg and add to the result if this
// location is a BREAK or if this is the last location
if (destination->type() == valhalla::Location::kBreak ||
destination->type() == valhalla::Location::kBreakThrough) {
if (is_break_point(*destination)) {
// Move origin back to the last break and collect the throughs
std::list<valhalla::Location> throughs;
while (origin->type() != valhalla::Location::kBreak &&
origin->type() != valhalla::Location::kBreakThrough) {
while (!is_break_point(*origin)) {
throughs.push_front(*origin);
--origin;
}

// Form output information based on path edges. vias are a route discontinuity map
if (api.trip().routes_size() == 0 || api.options().alternates() > 0) {
route = api.mutable_trip()->mutable_routes()->Add();
route->mutable_legs()->Reserve(correlated.size());
if (trip.routes_size() == 0 || options.alternates() > 0) {
route = trip.mutable_routes()->Add();
route->mutable_legs()->Reserve(options.locations_size());
}
auto& leg = *route->mutable_legs()->Add();
thor::TripLegBuilder::Build(api.options(), controller, *reader, mode_costing, path.begin(),
thor::TripLegBuilder::Build(options, controller, *reader, mode_costing, path.begin(),
path.end(), *origin, *destination, throughs, leg, algorithms,
interrupt, &vias);
path.clear();
Expand All @@ -590,7 +649,50 @@ void thor_worker_t::path_depart_at(Api& api, const std::string& costing) {
// if we just made a leg that means we are done recording which algorithms were used
if (path.empty())
algorithms.clear();

return true;
};

auto correlated = options.locations();
bool allow_retry = true;

// For each pair of locations
auto destination = ++correlated.begin();
while (destination != correlated.end()) {
auto origin = std::prev(destination);
if (!route_two_locations(origin, destination)) {
// if routing failed because an intermediate waypoint was snapped to the low reachability road
// (such road lies in a small connectivity component that is not connected to other locations)
// we should leave only high reachability candidates and try to route again
if (allow_retry && is_through_point(*origin) &&
!is_high_reachable(*origin, origin->path_edges(0))) {
Copy link
Contributor Author

@genadz genadz Mar 11, 2021

Choose a reason for hiding this comment

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

@kevinkreiser I think such condition

if (allow_retry && origin != correlated.begin() && is_through_point(*origin) && origin->path_edges_size() > 0 &&
!is_highly_reachable(*origin, origin->path_edges(0)))

should handle all cases

allow_retry = false;
// for each intermediate waypoint remove candidates with low reachability
correlated = options.locations();
for (auto loc = std::next(correlated.begin()); loc != std::prev(correlated.end()); ++loc) {
remove_edges_with_low_reachability(*loc);
// it doesn't make sense to continue if there are no more path edges
if (loc->path_edges_size() == 0)
// no route found
throw valhalla_exception_t{442};
}
// reset state and try to route again
route = nullptr;
last_edge = {};
vias.clear();
path.clear();
algorithms.clear();
trip.mutable_routes()->Clear();
destination = ++correlated.begin();
continue;
}
// no route found
throw valhalla_exception_t{442};
}
++destination;
}
// assign changed locations
*api.mutable_options()->mutable_locations() = std::move(correlated);
}

/**
Expand Down
Loading