a-b-street / osm2streets

Convert OSM to street networks with detailed geometry
https://a-b-street.github.io/osm2streets
Apache License 2.0
85 stars 6 forks source link

Turns / movements #67

Closed dabreegster closed 1 year ago

dabreegster commented 1 year ago

It feels like we're at a point to start figuring out how to represent turns / movements through intersections in osm2streets. (I've gone through this exercise before for A/B Street and made some questionable choices there, based on assumptions about how lane-changing should work in a traffic simulation. I'm viewing this as an opportunity to try again, with fresh eyes + experience + more discussion!)

How A/B Street does it: basic schema

A turn is uniquely identified as a (source lane, destination lane, intersection). The intersection seems redundant, but it's needed to talk about pedestrian movements. https://a-b-street.github.io/docs/tech/dev/formats/traffic_signals.html explains why.

Each turn has a type. For vehicles, it's straight / right / left / u-turn. There can be more nuance than that (5-way intersections, slight right). Distinguishing these types was partly helpful in writing heuristics to generate traffic signal configurations -- things like saying "at a 4-way intersection, the straight turns for the north & south roads can all go. The left turns have to yield." But defining these based on relative road angles was always brittle and hit edge cases, and I remember trying to come up with an alternate way to think about it... in a left-handed driving place, a left turn is one that conflicts with less turns than a right turn.

Turns conflict very "coarsely." A turn has a line-string of the movement through the intersection, and if those intersect, then they conflict. I think I've only needed the concept of conflicting turns in traffic signal configurations and traffic sim, but it may be a concept more widely useful.

Consequences

Defining turns at the lane level gets kind of weird. Screenshot from 2022-08-16 12-26-54

Changing lanes in the middle of an intersection is illegal (I think in the US, possibly everywhere, whatever). But from this left lane, A/B Street has a straight turn going to either lane. I did it this way to avoid the absolute mess of simulating lane-changing, but it led to other problems in the simulation anyway. There has to be a way to penalize changing lanes like this, so by default vehicles only do it if necessary.

Sometimes 3 lanes lead to 2 and someone has to merge though. Or vice versa, one turn lane opens up onto 3 lanes, and there's a choice. Something has to say what turns are legal, and maybe prioritize them.

Lane types

Things get weirder with roads that have bus or bike lanes. A/B Street looks like this: Screenshot from 2022-08-16 12-30-46 A bus in the right lane can turn left, cutting across a bunch of lanes. This is incorrect here and maybe in many cases, but... not always. Sometimes there are traffic signals configured to let bikes or buses start first and possibly do weird movements like this.

Movements

At some point, working at the lane granularity got really hard for traffic signals. It was better to think about the entire "directed road segment" -- aka a bundle of lanes pointed the same way. A movement was defined at this higher level, and it bundled together some turns. It looks like this:

https://user-images.githubusercontent.com/1664407/184869795-376d2be1-4b9a-4cae-b314-49965d5b6550.mp4

These probably need to be refined to talk about just the bus or bike movements too, in some cases.

How this is all generated

Messily! https://github.com/a-b-street/abstreet/blob/master/map_model/src/make/turns.rs does roughly this:

1) Look at every source and destination lane at an intersection, create a turn linking them 2) Do some bezier curve magic by Marcel to create geometry 3) Classify into straight/left/right/u-turn 4) Attempt to filter out turns based on different types of turn restrictions. Sometimes that process breaks, so the logic is complicated -- it'll give up on filtering and just keep the original things, instead of "orphaning" a lane. Every lane should lead somewhere, and should be reachable from somewhere.

There are 3 types of turn restrictions. 1) "Simple" ones are relations between OSM ways. Allow or ban turns from road1 to road2 2) "Complex" ones are relations involving multiple OSM ways. I only supported one "intermediate" way, but meant to support more. I can pull up some examples somewhere, but these're used for things like U-turns in complex intersections / dual carriageways. The consequences of these at a routing level is quite complex -- you have to replace part of the graph with "uber-turns" that describe a sequence of movements. 3) Lane-level restrictions. Once we know road1 can go to road2, specifically what lanes can do what?

Pedestrian movements

Most of the above is about vehicle turns. Pedestrians have "crosswalks" and "unmarked crossings" through intersections, the latter of which isn't well supported. Those're meant to be the difference between explicit zebra crossing type things, and movements which are not physically signed but are either legal or definitely performed in practice.

There's also "shared sidewalk corners" for lack of a better term, where the pavement extends through the intersection and doesn't cross any other turn. Screenshot from 2022-08-16 12-41-50

Testing

Done very poorly in A/B Street right now. Aside from the interactive turn explorer UI shown in some screenshots above, I have some snapshot tests recording all the generated turns for a few test cases, and I can tell when a code change modifies these. Then I'd use the UI and manually look.

So now what?

There's lots above, and it doesn't even cover things like conditional restrictions (no turns during peak hours, except for buses). Anybody have thoughts about what we should do for osm2streets or how to proceed?

BudgieInWA commented 1 year ago

I really like the idea of movements as an abstraction for working with these, while also enumerating the individual paths through the intersection, what you're calling "turns".

Sometimes 3 lanes lead to 2 and someone has to merge though. Or vice versa, one turn lane opens up onto 3 lanes, and there's a choice. Something has to say what turns are legal, and maybe prioritize them.

I think we should be opinionated in our assumptions. OSM has the ability to tag these explicitly using relations when something unexpected or ambiguous is happening, but there is a clear understanding that every driver comes to when they approach the intersection. That understanding is assumed and left untagged at the OSM level, and it is the understanding that we need to come to in order to understand these intersections, I think.

Stuff like, "don't change lanes in an intersection" practically means "allocate lanes in order, starting at the inside". We saw an example of the guide lines they paint when there is any ambiguity; they would put a single line between two adjacent turning lanes of traffic -- to keep them both in their lane -- to reinforce the "don't change lanes in an intersection" rule.

In fact, doing that with the northbound bus lane turning left or right, we see that we run out of destination lanes before we get to the bus lane. That tells us that the bus lane needs to merge into another lane while performing that turn. If you're merging, I guess you get to choose your lane, right? Maybe we should flag all turns that give priority while merging... Certain algorithms would love to ignore them.

BudgieInWA commented 1 year ago

The term Manoeuvre came to mind. Is that a specific turn? "from the bus lane on the right, left turn into lane 1"

BudgieInWA commented 1 year ago

I noticed this at an intersection the other day: This set of lights has helpful turn guides marked on it, including a diamond (painted in "road edge" style) right in the middle, where no turn paths cross. There was a collection of bluestone gravel from construction that has collected, and remained undisturbed in the centre of the road, showing how accurate the markings are! You can see all of the turn paths that I would expect anyone to make through that intersection (even making assumptions based on the lack of turn arrows in the two main lanes. I guess the left lane turning left without an arrow is always an option they have, and never an option anyone else ever has...).

See how it predicts exactly what the intersection looks like? And that island in the middle of the OSM "#" has a large enough area that rocks can collect, so maybe it's big enough to spawn a buffer area with edge markings. Much bigger and you would consider putting a traffic island in there...

If we can consolidate these intersections through whichever transformations are easiest, then generate some sort of turn paths (starting at some sensible location, maybe the stop line, or some guessed setback from the intersection), that would be awesome. Maybe all the optional turn paths are included at first, until some transformation makes a guess at what the legal turn paths are.

(I am interested in seeing what we can interpret from the shapes of the turns too. You usually get pointed directly at your exit lane for "through" manoeuvres, e.g.)

turn paths

dabreegster commented 1 year ago

That understanding is assumed and left untagged at the OSM level, and it is the understanding that we need to come to in order to understand these intersections, I think.

Agreed, we should algorithmically generate this and also handle when it's tagged explicitly. https://github.com/a-b-street/abstreet/blob/a791a5cca754ff9ec3a07b1270865ec7cfa171cc/map_model/src/make/turns.rs#L308 is my attempt at the first thing. I don't offhand remember the tag used for the second.

If we can consolidate these intersections through whichever transformations are easiest, then generate some sort of turn paths

Indeed interesting how turn paths and the intersection geometry can both influence each other a bit. Right now the turn path uses the set-backs calculated by the intersection polygon algorithm. The turn path is here, written by @mdejean. There are cases when the turn path "leaks outside" of the intersection polygon -- I can't find any offhand right now, but I think it happens with really narrow service roads.

The images and idea of tracing where cars go reminds me of https://en.wikipedia.org/wiki/Sneckdown

mdejean commented 1 year ago

There are cases when the turn path "leaks outside" of the intersection polygon -- I can't find any offhand right now, but I think it happens with really narrow service roads.

This can happen with the exception cases listed, for example when a wide road intersects a narrow road, if the u-turn is wider than twice the narrow road width, it will go outside the intersection. It can also happen with intersections that are very far from convex. I was thinking about how hard it would be to eliminate the 'all lanes of a road are the same length' rule, which would make the intersection geometry generation much simpler when I got distracted by life.

On Thu, Sep 15, 2022 at 7:42 AM Dustin Carlino @.***> wrote:

That understanding is assumed and left untagged at the OSM level, and it is the understanding that we need to come to in order to understand these intersections, I think.

Agreed, we should algorithmically generate this and also handle when it's tagged explicitly. https://github.com/a-b-street/abstreet/blob/a791a5cca754ff9ec3a07b1270865ec7cfa171cc/map_model/src/make/turns.rs#L308 is my attempt at the first thing. I don't offhand remember the tag used for the second.

If we can consolidate these intersections through whichever transformations are easiest, then generate some sort of turn paths

Indeed interesting how turn paths and the intersection geometry can both influence each other a bit. Right now the turn path uses the set-backs calculated by the intersection polygon algorithm https://a-b-street.github.io/docs/tech/map/geometry/index.html. The turn path is here https://github.com/a-b-street/abstreet/blob/a791a5cca754ff9ec3a07b1270865ec7cfa171cc/map_model/src/make/turns.rs#L218, written by @mdejean https://github.com/mdejean. There are cases when the turn path "leaks outside" of the intersection polygon -- I can't find any offhand right now, but I think it happens with really narrow service roads.

The images and idea of tracing where cars go reminds me of https://en.wikipedia.org/wiki/Sneckdown

— Reply to this email directly, view it on GitHub https://github.com/a-b-street/osm2streets/issues/67#issuecomment-1247988392, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA6VNBU3WLE7LBOPBKBWEQTV6MDRDANCNFSM56VP546A . You are receiving this because you were mentioned.Message ID: @.***>

dabreegster commented 1 year ago

eliminate the 'all lanes of a road are the same length' rule

Curious what you mean here (because it might also be a good time to re-evaluate this assumption). Something like ? Every lane hits the intersection at 90 degrees, but some lanes can extend farther in?

BudgieInWA commented 1 year ago

Something I picked up looking at the JOSM Lanes plugin implementation, is a way of representing these angled "end caps" for roads like the "miter" joins between road segments.

image

The desired road/lane end angle (where the stop line is often found) is just like the line of reflection that would be found running through a miter, if the road continued along the grey markings. Perhaps these "angled end caps" should be implemented as a capability of our "thick line strings". Seen angStart and angEnd arguments in getParallel

Lanes on the outside of turns already have a longer length, so we might have to look at individual lane lengths at some point anyway...

dabreegster commented 1 year ago

I see, this gets rid of the assumption that the road hits the intersection at 90 degrees, without doing something silly like the jagged teeth of https://a-b-street.github.io/docs/tech/map/geometry/index.html#desired-output. I'm in favor of trying this out!

It indeed sounds like we need more state per lane. It shouldn't be too crazy a change downstream... already there are methods to get the center line of each lane

BudgieInWA commented 1 year ago

I have had a crack at calculating turns through an intersection in the street_network crate, and also tried to detect MultiConnection intersections, which was interesting.

Instead of starting with the lane-specific turn paths being calculated and grouped into movements, I feel like the valid movements should be calculated first, and turn paths can come a bit later in the process. This is because way-to-way routability is critical in understanding what type of intersection we are working with (e.g. for deciding which pieces of OSM geometry are non-sense in sausage links). OSM tries really hard to make it clear what the routability through an intersection is at the way-level, but the lane-level stuff is underdeveloped. Then maybe the individual turn geometry will be useful in the later stages for making tweaks (like rounding the corners, adjust the setbacks etc.)

For example, an intersection with a two-way road and two one-way roads could be a "sausage link" where a dual carriageway joins back up, or it could be a side road on a one-way road. It all depends on if you are able to travel from the inward one-way road to the outward one-way road or not. Some mappers would put a "no u-turn" relation in the dual carriageway case, but there is advice out there that it is not needed and that data consumers will figure it out -- which we should be able to learn from those who have implemented OSM routing engines.

image

If we can represent movements right at the core of our Intersection type (preserving them through merges) and calculate them from raw OSM and simple heuristics up front (without needing much context), I think we will be a whole lot closer to intersection road markings and the like.

dabreegster commented 1 year ago

So if I'm understanding correctly, the idea is to work at the movement (direction + road segment) level as long as possible, then make specific turns (lane-level) later? We first generate all possibilities and filter using turn restriction relations, and maintain those through transformations? In your turn paths branch, you're working at the lane level. I'm not sure which approach is appropriate yet or not, just trying to follow the idea

BudgieInWA commented 1 year ago

Yes, I would like to see how far through the process we can get before lane-level turns become needed. The turn-paths branch is where I decided this: I was trying to generate only valid turns (that don't change lanes through an intersection, etc.) which is a complicated problem itself that wants to know what movements are valid to begin with. I started down the path of calculating the geom for the turns right away, but a lot of that stuff lives in an abstraction a layer or two above. The turns seemed useful even without geom, but movements are a better fit I think.

dabreegster commented 1 year ago

Got it, so the overall flow is something like...

1) Generate all possible movements between directed roads 2) Filter based on turn restriction relations, and maybe inferred U-turn cases 3) Classify intersection complexity here (or maybe later?) 4) Generate lane-level turns, using turn lane tagging and heuristics about which lanes have to merge with each other

Once we get some of the concepts/terminology settled a bit, we could start wiring this up in the UI and record test data for it, to make iterating faster

BudgieInWA commented 1 year ago

That sounds about right. Maybe it will be best to merge certain intersections (like dual carriageway # intersections) before trying to figure out the movement, but maybe not.

At step 3, I'll try to classify complexity, but also detect road pairs that continue through the intersection uninterrupted, and other "features" of the intersection that can't be captured by a single enum. These features effect the intersection geometry which lets us straighten out sausage links, for example.

A debug visualisation of movements will be useful as soon as movements are represented, I think. Even straight lines connecting the ends of the trimmed centerlines of the relevant roads should be a good start.