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
Suppress turns emitted while on ferry #3420
Conversation
the same should probably apply to other types of routes, like trains. |
Hm could we handle this in pre-processing instead of post-processing? Turning from a /cc @MoKob |
@TheMarex you are correct. If we assume for ferries always to be |
Sorry my mistake here. I implemented lane removal from roundabouts in post-processing and was under the impression @karenzshea could do the same quick fix there, too. Now as @MoKob today told me in the context of #2705 is that @karenzshea take a look at the changes in https://github.com/Project-OSRM/osrm-backend and how @MoKob handles not collapsing inside roundabouts and not assigning lanes there. Maybe this is a good start. |
@@ -51,6 +51,11 @@ TurnType::Enum IntersectionHandler::findBasicTurnType(const EdgeID via_edge, | |||
const auto &in_data = node_based_graph.GetEdgeData(via_edge); | |||
const auto &out_data = node_based_graph.GetEdgeData(road.eid); | |||
|
|||
// suppress instructions on edges that the can't control anyway | |||
if (!in_data.startpoint && !out_data.startpoint) |
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.
In my view, we shouldn't abuse is startpoint here (and below). We want to filter out Ferry
turns. As already stated in the discussion, we should use the TravelMode Ferry to do so. Someone might decide that starting on other roads is invalid, but we know that jumping ship is not possible. If someone disables motorways as start points, we might still want to give instructions.
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 disagree here. This would mean that you need to add explicit handling based on mode for every mode you want to support. That means the probability that we need to find and adjust this piece of code in the future is very high, which I think is undesirable and duplicates logic from the profile.
Using startingpoint
uses a concept that we already expose in the profiles: Flagging ways that should be threaded as a single 'unit' from a user perspective. The name might be good or bad but this is effectively what this does. I don't see any scenario where we would reasonably emit an instruction between edges of this type. Any division between steps is done by the routing engine for technical reasons not user intend.
I feel this will be easier to use from a user perspective and offer less surprises then explicitly needing to cycle back every time a user finds a use-case for having ferry
-like edges.
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.
The problem I see here is that what we snap to and what we consider as connections in forms of a turn are two entirely different concepts to me. Ferries are special in terms of that they often share ways in OSM data but don't allow switching from one item to another in between.
If we want to specify that snapping to is equivalent to does not allow turning, then you would be right. But as long as we don't make a distinction here, using startingpoint
would not capture the issue we see in ferries.
The concept we need are ways that share segments but are distinct (right now we don't handle these correctly in all cases, since we can right now actually switch ferries in between). Ferries will need specific handling anyhow. The main question for me here is: do we really want to tie snapping to turn instructions?
/cc @daniel-j-h @danpat for additional oppinions
Just a heads up. I've added a small test |
To explain the reasoning behind my test-case: I have the feeling that the two locations currently modified in this PR might not catch all ferry cases. In my view it would be easier to do a dedicated ferry handler in the beginning that checks for |
cdfc27a
to
a06f343
Compare
@@ -53,4 +55,7 @@ const constexpr osrm::extractor::TravelMode TRAVEL_MODE_RIVER_UP = 10; | |||
const constexpr osrm::extractor::TravelMode TRAVEL_MODE_RIVER_DOWN = 11; | |||
const constexpr osrm::extractor::TravelMode TRAVEL_MODE_ROUTE = 12; | |||
|
|||
// travel modes for which navigation should be suppressed | |||
const std::array<osrm::extractor::TravelMode, 2> constexpr SUPPRESS_MODE_LIST = {{TRAVEL_MODE_TRAIN, TRAVEL_MODE_FERRY}}; |
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.
The problem with using std::array here is the size has to be known at compile time. If you now add an additional mode you have to go through the code base and change all array<T, N> to array<T, N + 1>.
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 this could be solved using a type alias though. using SupressionModeArray = std::array<osrm::extractor::TravelMode, 2>;
aa0359d
to
2b2860b
Compare
Given the profile "car" | ||
Given a grid size of 20 meters | ||
|
||
Scenario: Collapse Steps While On Ferry |
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.
could you maybe directly add the ascii-art
to make the tests more readable to begin with? (here and on the other tests)
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.
all the tests are rendered in ASCII now
| waypoints | route | turns | modes | | ||
| f,j | land,sea,land,land | depart,notification right,turn left,arrive | driving,ferry,driving,driving | | ||
|
||
Scenario: Collapse Steps While On Ferry |
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 believe we could remove this test. It looks like the only difference here to the test above is an additional coordinate on the ferry route, which should not influence any turn decisions.
| bc | primary | ferry | pennydog-island-ferry | | ||
|
||
When I route I should get | ||
| waypoints | route | turns | modes | |
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.
misaligned, also I miss my monkey island reference...
const TravelMode &out_mode, | ||
const SuppressModeListT &SUPPRESS_MODE_LIST) const; | ||
|
||
bool SuppressModeNavigation(const EdgeID &via_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.
via_edge/connected are unintuitive to me. how about from_edge
and to_edge
? or in_edge
and out_edge
?
{ | ||
const auto suppress_in_mode = std::find(begin(SUPPRESS_MODE_LIST), end(SUPPRESS_MODE_LIST), in_mode); | ||
if (suppress_in_mode != end(SUPPRESS_MODE_LIST)) | ||
{ |
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.
minor nitpick: the check for in_mode == out_mode
is cheaper than the algorithm call. So doing it the other way around would be faster:
if( in_mode != out_mode )
return false;
const auto suppress_in_mode = std::find(begin(SUPPRESS_MODE_LIST), end(SUPPRESS_MODE_LIST), in_mode);
return (suppress_in_mode != SUPPRESS_MODE_LIST.end();
In addition: using end
like this requires the inclusion of iterator
and a using std::end
.
In this case, it's probably easier to simply call SUPPRESS_MODE_LIST.end()
.
@@ -37,13 +38,41 @@ IntersectionHandler::IntersectionHandler(const util::NodeBasedDynamicGraph &node | |||
{ | |||
} | |||
|
|||
bool IntersectionHandler::SuppressModeNavigation(const EdgeID &via_edge, | |||
const EdgeID &connected, | |||
const SuppressModeListT &SUPPRESS_MODE_LIST) const |
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 believe it would be cleaner to not pass the mode list as a parameter.
Since you already define it globally in travel_mode.hpp
, you are essentially shadowing the name.
Shadowing describes a situation where you use the same name (here SUPPRESS_MODE_LIST) for two variables that are available at the same time. (https://en.wikipedia.org/wiki/Variable_shadowing). Simply accessing the global variable would be cleaner, in my opinion, since I don't see a reason where we would require greater flexibility at some point.
TurnType::Enum IntersectionHandler::findBasicTurnType(const EdgeID via_edge, | ||
const ConnectedRoad &road) const | ||
{ | ||
|
||
const auto &in_data = node_based_graph.GetEdgeData(via_edge); | ||
const auto &out_data = node_based_graph.GetEdgeData(road.eid); | ||
|
||
const auto in_mode = node_based_graph.GetEdgeData(via_edge).travel_mode; | ||
const auto out_mode = node_based_graph.GetEdgeData(road.eid).travel_mode; | ||
if (SuppressModeNavigation(in_mode, out_mode, SUPPRESS_MODE_LIST)) |
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.
As per #3420 (comment), I've added a test. Using the three fallbacks is not sufficient to suppress all turns in ferries. I've added a test that still emits a turn on a ferry.
To fully solve this, I'd propose a dedicated handler (see previous comment). The handler should check all pairs of roads for same travel mode (e.g std::all_of(intersection,SuppressModeNavigation(first_mode,road_mode)) to check if it is applicable and simply assign NoTurn
to all turns in its operator()
.
return node_based_graph.GetEdgeData(way.eid).travel_mode == in_mode; | ||
}); | ||
|
||
if (all_share_mode) |
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'm not sure I understand the reasoning for only suppressing when all outgoing edges share the mode.
The scenario I'm thinking of is a multi-stop ferry journey like this:
Should we suppress the instruction at the stopover point? I'm kind of torn.
What you've got should work great for ferry route splits in the middle of the ocean 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.
Originally I'd been selectively suppressing only instructions onto ways that involved modes on the mode blacklist, but this failed a test added by @MoKob wherein there is an intersection with a new ferry line and a land-based way.
The test is written such that the instruction at the stopover point is expected to be suppressed.
My thinking on this is that without a destination
based instruction when the first ferry line is boarded, it's not exactly useful to emit an instruction at the "stopover" if there really is a stopover at all or just a change of direction? Do we have a way of knowing like in the test above whether or not a switch to a new ferry involves user action? I actually would kind of assume that switching to a new ferry line necessitates user action, but I defer to @MoKob who wrote the original test.
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.
Ah, ok. I guess there's a larger discussion to be had around route relations, ferries/trains changing name, stopovers, etc, but I think the logic here handles the egregious cases that the original ticket identified. 👍 on this approach for now.
namespace guidance | ||
{ | ||
|
||
class SuppressModeHandler : public IntersectionHandler |
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 you can make it SuppressModeHandler final :
- all handler could be final except the base class IntersectionHandler
@@ -53,4 +56,7 @@ const constexpr osrm::extractor::TravelMode TRAVEL_MODE_RIVER_UP = 10; | |||
const constexpr osrm::extractor::TravelMode TRAVEL_MODE_RIVER_DOWN = 11; | |||
const constexpr osrm::extractor::TravelMode TRAVEL_MODE_ROUTE = 12; | |||
|
|||
// travel modes for which navigation should be suppressed | |||
const osrm::extractor::SuppressModeListT constexpr SUPPRESS_MODE_LIST = {{TRAVEL_MODE_TRAIN, TRAVEL_MODE_FERRY}}; | |||
|
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.
Should this be in the SuppressModeHandler then?
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.
ohh good thinking, this doesn't need to be a global anymore
@@ -44,6 +45,9 @@ TurnType::Enum IntersectionHandler::findBasicTurnType(const EdgeID via_edge, | |||
const auto &in_data = node_based_graph.GetEdgeData(via_edge); | |||
const auto &out_data = node_based_graph.GetEdgeData(road.eid); | |||
|
|||
const auto in_mode = node_based_graph.GetEdgeData(via_edge).travel_mode; | |||
const auto out_mode = node_based_graph.GetEdgeData(road.eid).travel_mode; |
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.
Hm? Dead code?
Intersection SuppressModeHandler:: | ||
operator()(const NodeID /*nid*/, const EdgeID source_edge_id, Intersection intersection) const | ||
{ | ||
const auto in_mode = node_based_graph.GetEdgeData(intersection[0].eid).travel_mode; |
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 travel mode for the UTurn road?
|
||
// if all out ways share the same mode as the approach way, suppress all the turns | ||
const auto all_share_mode = | ||
std::all_of(begin(intersection) + 1, end(intersection), [this, &in_mode](ConnectedRoad way) { |
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.
const auto& road
std::for_each(begin(intersection) + 1, end(intersection), [&](ConnectedRoad &out_way) { | ||
out_way.instruction = {TurnType::Suppressed, getTurnDirection(out_way.angle)}; | ||
}); | ||
} |
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.
If you
auto first = begin(intersection) + 1;
auto last = end(intersection);
above you can re-use them 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.
👍
{ | ||
intersection = suppress_mode_handler( | ||
node_prior_to_intersection, entering_via_edge, std::move(intersection)); | ||
} |
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.
Your handler should come first, since it could influence others.
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.
Also if your handler fires you have to disable all other handlers. Otherwise it could be that e.g. your handler changes instructions to NoTurn and now the Sliproad Handler accidentally is getting confused with the NoTurn and classifies this as a Sliproad.
Make the handler come first and if it triggers skip all other handler.
Mason macOS build is failing due to @springmeyer you looked into this back then, any ideas why it is failing only now on this branch and e.g. not on master? The file failing to compile is the same as on master after all. Build is https://travis-ci.org/Project-OSRM/osrm-backend/builds/183957985 and error is at https://travis-ci.org/Project-OSRM/osrm-backend/jobs/183957988#L1179 |
@daniel-j-h just rebase is needed. It was "fixed" by b1aced3#diff-4ff77436d81ab7e8c0c026734a2d79cdR6 |
8159a94
to
59edabc
Compare
Rebased and refactored a bit. @MoKob could you have a final look over this. Note: needs to be squashed before merging. |
Taking this over - dismissing my own review here.
59edabc
to
b8b0a64
Compare
b8b0a64
to
6fe54a9
Compare
- Bugfixes | ||
- Fix #3475 removed an invalid `exit` field from the `arrive` maneuver | ||
- Guidance | ||
- No longer emitting turns on ferries, if a ferry should use multiple docking locations |
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.
Hm @MoKob, I just wanted to add one but it looks like you already added a changelog entry here - what's missing then?
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. I just added one, after I said it in chat. Sorry for not updating you. Other than that, if you are OK with my last changes, it should be good to go.
Issue
Fixes #3225
Tasklist
Requirements / Relations
cc @MoKob @daniel-j-h