a-b-street / osm2lanes

A common library and set of test cases for transforming OSM tags to lane specifications
https://a-b-street.github.io/osm2lanes/
Apache License 2.0
34 stars 2 forks source link

Represent dividers semantically #100

Closed BudgieInWA closed 2 years ago

BudgieInWA commented 2 years ago

I want to propose that dividers be represented internally by their semantics, instead of their visual/physical manifestation, and be translated into specific markings in a separate step. My initial feeling is that it would shift most or all locale dependent questions about dividers out of the tag parsing step, into its own divider to markings step. Providing the divider semantics in the output would also be useful for users.

Semantic divider types

Dividers would be broadly categorised something like:

with additional semantic properties such as

dividers_to_markings

A separate function concerned with just the problem of converting semantic dividers to markings in the context of a given road would be a easier for new contributors to contribute to, which would be valuable for capturing global nuances. (I would be interested to see how static the mappings turn out to be.)

Having the markings be a separate step would also be a useful to allow additional information to be added to the road after examing the semantics. E.g. I have two connected OSM ways: 2 lanes into 3 lanes. I get the semantic lane info, compare them and decide that first lane in the 3 lane way is an added lane. I annotate the appropriate divider as "merging" before getting markings, and receive a beautifully appropriate dotted line (here in Oz).

Exposing the function as part of the API would also be a good way to query the lane marking database, because describing a situation from scratch in the output schema is much easier than describing it in the OSM schema!

Divider widths

Given that different locales might have different width markings for the same semantic divider, semantic dividers would have zero width: their markings fit within the the width(s) of the adjacent lane(s). (I'm not even sure this isn't how it already works). This is how I would prefer to receive osm2lanes output in semantic mode as a user.

Medians

This makes the "median" divider the odd one out, as the only divider with width of its own. Perhaps this suggest that the median itself should be a "lane" like a shoulder or a verge. One benefit is that the markings that divide the road from the median can be described too. In Australia, a median will often have a continuous solid line surrounding it, even as it transitions from a solid colour to a raised median to a diagonal hatching. The following output to describe the median would make sense to me:

A continuous line (divider) surrounds the median (buffer)

    {"type": "travel_lane", "direction": "forward"},
    {"type": "separator", "markings": [{"style": "solid_line"}]},
    {"type": "median", "kind": "raised"}, // or "colored" or "striped" or whatever
    {"type": "separator", "markings": [{"style": "solid_line"}]},
    {"type": "travel_lane", "direction": "backward"},

Medians are not well represented in OSM at the moment (divider= is the best I could find), but that is no reason not to represent then in this schema.

dabreegster commented 2 years ago

I'm in favor of this idea. Additionally, the step that infers lane separating lines and the center line may need to be teased apart into its own post-processing method, to support a Streetmix-style editor. A user shouldn't be able to drag around lane cards representing dividers that're implied by the other lanes -- there are many invalid states they could accidentally produce. Raised curbs and medians and such are the exception -- direct control there is good.

droogmic commented 2 years ago

To summarize, we want to state:

I think I agree with this.

Maybe I missed it, but would we still keep curbs, lines, cats-eyes, etc. into one top level type as these are generally always inferred? I can imagine some country may have between a travel lane and foot/pavement/sidewalk:

{"type": "collection_thing", "things": [{"style": "solid_line"}, {"style": "cats_eye"}, {"style": "curb_up"}]},
BudgieInWA commented 2 years ago

Yep, that makes sense to me. brainstorming in my mind:

3 top level types: travel lane, buffer, and divider. Only the first two can be inputs and can have width. The subtypes of each adjacent pair of lanes defines which (zero width) divider type(s) is between them, but dividers can have their attributes modified. So, an editor would "replace the center line with a median" by adding a buffer lane where the center line was. Normalising that would result in an "inner road line" on each side of the buffer.

(All this coming from someone who hasn't understood the existing schema yet 😛)

tordans commented 2 years ago

(Disclaimer: I am not sure I fully understood the conversation and how osm2lanes works in this area, yet.)

How would osm2lanes present buffers like this: Mapillary

The current mapping idea in OSM is to use buffer=Number<m> | 'no' | 'yes' (or prefixed on the highway), example https://www.openstreetmap.org/way/764003876#map=19/52.48503/13.42733

This mapping idea is part of https://wiki.openstreetmap.org/wiki/Proposed_features/cycleway:separation

Based on this tagging, the Straßenraumkarte can show the buffer like visible at https://supaplexosm.github.io/strassenraumkarte-neukoelln/?map=micromap#19/52.48520/13.42679

droogmic commented 2 years ago

Given the multiple ways, osm2lanes at the moment cannot do much with this.

In the future: if we were to merge multiple ways to get one sequence of lanes, the schema would be something like:

...
{type: "travel"}
{type: "separator", markings: [{type: "kerb_up"}, {type: "median"}, {type: "kerb_down"}],
{type: "travel"},
{type: "separator", markings: [{type: "solid_line"}, {type: "diagonal_hatched"}, {type: "bollard"}, {type: "solid_line"}],
{type: "travel", designated: "bicycle"}
...

But this issue proposes something more like

...
{type: "travel"}
{type: "marking", markings: [{type: "kerb_up"}],
{type: "separator", "median"],
{type: "marking", markings: [{type: "kerb_down"}],
{type: "travel"},
{type: "marking", markings: [{type: "solid_line"}, {type: "diagonal_hatched"}],
{type: "separator", "bollard"],
{type: "marking", markings: [{type: "solid_line"}],
{type: "travel", designated: "bicycle"}
...

I see some problems with both specs...

tordans commented 2 years ago

Given the multiple ways, osm2lanes at the moment cannot do much with this.

I did not find an example with a buffer mapped on the lane when I wrote the above. However, the buffer tagging would work on the main lane as well with cycleway:buffer (that is what I meant with "or prefixed on the highway").

However, even on a separate way and without a-b-street/osm2streets#4 being solved, I should still be able to apply osm2lanes to the highway=cycleway from above and the given buffer width should be represented in the result, right?

BudgieInWA commented 2 years ago

My intention with this issue is to work with semantic descriptions of markings/separations instead of visual descriptions of markings until the last possible moment, so perhaps we should not try to solve buffers here. (I am also not familiar with how osm2lanes currently represents buffers or medians, and I have no idea how to tag them on a road way in OSM.)

At risk of straying too far from the issue of semantic dividers, let me try this example. If osm2lanes was to represent that roadway from the OSM tags (barrier=bollard, barrier=3, ???=median) (if they were on a single way, or if there was a way to combine ways), this issue proposes that there is an intermediate representation of the schema that I have called "semantic mode" that looks something like this:

...
{type: "travel"}
{type: "separator", elements: [{type: "inner_edgeline"}, {type: "kerb"}, {type: "median"}, {type: "kerb"}, {type: "inner_edgeline"}],
{type: "travel"},
{type: "separator", elements: [{type: "bike_buffer", width: 3}, {type: "bollard"}],
{type: "travel", designated: "bicycle"}
{type: "separator", elements: [{type: "bike_edgeline"}, {type: "kerb"}],
...

This representation would then be transformed (in locale=DE) into:

...
{type: "travel"}
{type: "separator", markings: [{type: "kerb_up"}, {type: "island"}, {type: "kerb_down"}],
{type: "travel"},
{type: "separator", markings: [{type: "solid_line"}, {type: "diagonal_hatched"}, {type: "bollard"}, {type: "solid_line"}],
{type: "travel", designated: "bicycle"}
{type: "separator", elements: [{type: "kerb"}],
...

I don't know how we decide that the bollard is inside the hatching with that semantic representation, but you get the idea.

tordans commented 2 years ago

(Sorry for not responding to the examples in the messages; I don't understand them, yet, to provide an opinion.)

I was thinking more about the topic of markings width:

Given that different locales might have different width markings for the same semantic divider, semantic dividers would have zero width: their markings fit within the the width(s) of the adjacent lane(s).

Thinks we should consider IMO

I think, to be able to describe those cases, markings should have a width that we take from a ruleset of local standard-widths if not mapped explicitly (which will be nearly always). Otherwise I don't see how we could represent the edge cases on the road properly.

We still need to specify if this width is subtracted from the lanes or not. Eg: With a total width of 10m and 2 lanes, 1 line (50cm). Is it lane1=5m or lane1=4,75m.

BudgieInWA commented 2 years ago

I have thought about this some more while I have been exploring how the existing schema actually works, and how the code interacts with it (a bit), and based on your input.

I still want separators to be semantic for the first part of the process, but that is probably best done with just one separator between lanes, and semantic separators each describing more of the road not less. Represent the whole median as one separator and let the locale specific "separator to markings" function determine the curbs and road edge markings based on the properties of the road, lanes and median. That's much closer to how people think about separators I think.

{type: "travel"},
{type: "separator", separator: "marking"},
{type: "travel"},
{type: "separator", separator: "median", kerb_height: 0.2 },
{type: "travel"},
{type: "separator", separator: "buffer", width: 0.8, features: [{type: "bollard"}] }, // marking_style: "diagonal" if it was tagged as such
{type: "travel", designated: "bicycle"},
{type: "separator", separator: "verge", kerb_height: 0.2 },
{type: "travel", designated: "foot"},

There are still awkward aspects, like: if a kerb is a property of a separator, what is the separator between a sidewalk and travel lane that gets the kerb_height attribute? But, my point is: {separator: "centerline", overtaking: "none"} not {markings: [{style: "solid"}, {style: "solid"}]}.

droogmic commented 2 years ago

I need to take more time to read all this, but I can make a comment about:

my point is: {separator: "centerline", overtaking: "none"} not {markings: [{style: "solid"}, {style: "solid"}]}

I think we want both (maybe configurable). Why:

Do I understand correctly?

BudgieInWA commented 2 years ago

I think we want both (maybe configurable).

I agree! I think semantic separators => actual markings should be part of this project, just as a distinct step in the process. Internally, we can test tags => semantics and semantics => markings separately from each other, expose lanes_to_markings in the API and provide a configuration option to get markings straight out of tags_to_lanes.

droogmic commented 2 years ago

This issue got closed, but feel free to reopen it or create a new one if you think more discussion is needed.

There is a lot of work to do still (such as exposing this to the API, but internally the split has now been made.