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

Mitigation for more than one bus lanes scheme used #136

Open dabreegster opened 2 years ago

dabreegster commented 2 years ago

By far the most common problem I'm seeing with real data is more than one bus lanes scheme used. One example is https://www.openstreetmap.org/way/679636490. Glancing at this, I can't help feeling like though it uses two schemes, they both agree -- there's exactly 1 backwards lane and it's bus-only.

For #71, I'll probably be tactical in the short-term, detect these cases, and just arbitrarilyish rip out keys for one of the schemes. But I had another idea -- if we could parse each bus scheme independently, clone the road builder, and "try out" both schemes, then we could check if the results match. If so, just a warning about redundant tags could suffice -- no need to totally give up?

droogmic commented 2 years ago

I actually already allow busway + either bus:lanes or lanes:bus in https://github.com/a-b-street/osm2lanes/pull/134/files#diff-22ad929b7a488fd33b93d20054e80bb72678cebda592203907bb58188c8608b7

allowing for both bus:lanes and lanes:bus may be possible, but we have yet to implement lanes:bus, so it doesn't really matter yet

BudgieInWA commented 2 years ago

Each scheme describes a slightly different aspect of the road, and they should be reconciled. I think the XScheme types in #111 allow for this reconciliation to be implemented in a sensible way. If the tags don't agree with each other, then they are invalid and we have to decide which tags to ignore, which we could do simply by preferring one schema over another, or we could get as fancy as we like. I feel like a valuable usecase for osm2lanes is detecting these incompatibilities and suggesting the simplest fix, so maybe we do end up getting pretty fancy about it in the long run.

The situation is exactly the same with lanes and lanes:{forward,backward}: If forward and backward are provided, lanes is redundant, but it is convention to include it for less sophisticated consumers. If the tags disagree, we would have to choose one tag to ignore in order to produce an answer.

With that understanding we can look at some example implications:

And also a strategy for tagging arbitrary roads :

  1. Set lanes to the total number or vehicle lanes.
  2. If all vehicle lanes are in the same direction, add oneway=yes, otherwise add lanes:forward and lanes:backward.
  3. For each direction of travel, describe to following details about each lane in a | separated list with empty elements for "default" lanes, using <tag>:lanes:<dir> (where <dir> is omitted for oneway roads). e.g. bus:lanes:forward=designated||
    • access: no for lanes where only one thing is allowed (tag that thing next)
    • bus: designated for lanes signed as bus, no for lanes that buses aren't allowed in, empty for normal lanes
    • psv: same, but for "public service vehicle" (buses and taxis together)
    • turn for turn markings, etc.
droogmic commented 2 years ago

I had a slightly different strategy in mind, given that busway < lanes:bus < bus:lanes

  1. start with no lanes
  2. add lanes to satisfy busway
  3. add lanes to satisfy lanes: bus
  4. add lanes to satisfy lanes, lanes:* and access lists like bus:lanes, psv:lanes and access:lanes
  5. apply bus:lanes and similar per lane
BudgieInWA commented 2 years ago

I had a slightly different strategy in mind, given that busway < lanes:bus < bus:lanes

  1. start with no lanes
  2. add lanes...

I like following that process to reconcile, check for inconsistencies, and come up with likely guesses, all at once.

I guess that means that parsing the individual schemes independently should be done ignoring potentially conflicting tags, (but using dependencies like oneway), and producing Infer::Defaults, not Infer::Guesses. That process can be the one that looks at the full context and makes Guesses.

e.g. When it gets to the lanes schema, if the tags are missing then the scheme will be lanes: Infer::Default(2). Because it is the default we add those 2 lanes (because that is the guess we make), and set lanes = Infer::Guessed(road.num_lanes) (if num_lanes isn't 2). I like it.

droogmic commented 2 years ago

That works as well, maybe slightly clearer overall with less implicit knowledge of individual tagging schemes