Determine road geometry before path geometry
Closed this issue · 4 comments
I think Road
and Intersection
and the first pass of building and transforming a StreetNetwork
should be vehicle centric. Not because cars are more important or because osm2streets should aim to support them better, but because roads and adjacent infrastructure are designed first to influence the behaviour of the bigger, faster road users.
The shape of the roadway is designed to communicate what cars should be doing, with curbs following smooth lines to match the driving lines and stuff like that. When other road users are prioritised in the design, this is achieved by routing the other users around the car infrastructure, or changing the path that the cars take (and thus changing the shape of the road).
If we know where the cars go, we can predict where the curbs are (or should be) and thus where the pedestrians go. This might hold more true in some places (like where I live in Perth) than others, but I think the model is the best way to construct shapes anyway.
Bikes complicate the matter a bit, but I think about it as three tiers: Car, Bikes then Pedestrians. Pedestrian infrastructure often fits around dedicated bike infrastructure in the same way it does around car infrastructure. And bike infrastructure sometimes fit around car infrastructure in the same way. But sometimes bikes are treated as part of vehicle traffic (such as bike lanes), which complicates things a bit.
In fact think about railways: They are a tier higher than cars. That is, the shape of a railway tracks isn't determined by any roads, bike paths or footpaths; trains are gonna train, and everyone else is gonna respect that. Instead, features of the railway are going to be positioned to leave enough room for the other tiers to fit around it, but those railway features will be represented in the railway data. This is the idea I'm seeing with roads vs pedestrians.
Actionable takeaways
The "road width" should be the width of the whole carriageway (or both carriageways plus the median) "curb to curb". The "center" of a road should be the center of that width. This matches up with the OSM definition (as best as I can tell; we'd have to do a survey to confirm that mappers place the way at the center of the road width instead of the dividing line, when those don't coincide).
Road trimming and intersection shapes should be calculated by looking just at vehicle lanes. Paths should then be stuck on the side of road shapes. In the first instance, I think A/B street will just need to adjust where geometry is positioned (with then help of a width()
and total_width()
probably). Eventually, footpaths around intersections could be calculated with a shift
operation.
The intersection kind calculation already looks at only driving traffic. (As a consequence, only driving movements are calculated. Bike movements will need to be calculated at some point, and maybe factored into Kind
in some way.)
Thoughts?
Does this tiered approach make sense to you? Or are there implementation details that I'm glossing over that render the idea unhelpful? Does the idea of defining "road width" and "center" relative to the curbs, not the edge of the footpaths sound ok?
Road trimming and intersection shapes should be calculated by looking just at vehicle lanes.
Nitpick: More specifically, it should ignore non-vehicle lanes on the outer sides of the road. Sometimes a cycle lane is in the middle of the regular paved road (like when a turn lane is the outermost lane, and the cycle lane isn't). I feel I've also seen some kind of exotic footpath in the middle of a road before, or at the very least, when we zip parallel roads together, boulevards with center paths on grass medians, we might see this.
Does the idea of defining "road width" and "center" relative to the curbs, not the edge of the footpaths sound ok?
I'm warming up to it after understanding your point in #130. It feels like it adds complexity to the data model and API. It would make lane_specs_ltr
less easy to understand geometrically, since some of the outer lanes would be special-cased. I guess nothing stops us from writing curb_to_curb_width()
, right_of_way_width()
, get_left_curb() -> PolyLine
, etc. It would also make non-driveable roads more confusing, because some of these definitions wouldn't make any sense. But that needs to happen already for some cases.
I guess I don't yet see what the advantages to this shift in thinking buy us yet. The intersection geometry routine could also be the one place that handles this special case of only matching up the outermost vehicle lanes for calculating trim.
This matches up with the OSM definition (as best as I can tell; we'd have to do a survey to confirm that mappers place the way at the center of the road width instead of the dividing line, when those don't coincide).
I believe what untrimmed_road_geometry
is doing with sidewalks is trying to account for this, based on some examples I checked in OSM (and didn't comment...). We can pick the simplest representation to work with internally, and map whatever's in OSM to that. So, maybe this could make sense
Thanks for your input.
The intersection geometry routine could also be the one place that handles this special case of only matching up the outermost vehicle lanes for calculating trim.
I am definitely convinced now that we should implement helpers to work with the curb-to-curb "roadway" while leaving the actual representation alone. It sounds like you've understood the general thrust of the idea, and incorporating it into the geometry calculation code in isolation sounds like the best way to explore further.
I'm right in the guts of implementing the shifting logic for the placement which is one place curb-to-curb is relevant. The incoming placement PR will let us explore this in context. Here is an introduction to that particular can of worms:
Keeping the untrimmed, unshifted geometry as the source of truth, and reapplying the placement gets more difficult when we have zipped together multiple roads. Maybe we can use the concept of "curb-to-curb roadway" in the early stages of the process (for placement shifting and trim calculations), then switch to treating the trimmed geometry as the source of truth before we do certain transformation like zipping. The zipping transformation would then need to update the trimmed geometry properly, instead of updating the untrimmed geometry.
Maybe we go for a Corridor
type that represents a list of adjacent Road
s so that we can keep the scope of a single Road
smaller (similar to Junction
vs Intersection
). If we're trying to represent a big boulevards as a single Road
then it feels like we need to treat the median area as a special case. But if the median is wide enough, with winding paths, benches etc, it feels like working with two Road
s and letting the median be a normal "non-road" area might be easier. Depends how median handling turns out I suppose. I'm worried about being forced to merge Intersection
s that shouldn't be merged, that sort of thing...
Right, the question of Corridor
and Junction
goes back to earlier questions about whether we should try to merge the raw/primitive Roads
and Intersections
together, or add additional structure and abstraction on top. This also feels relevant in the dog-leg PR conversation. I'm still not sure the right approach, and it almost feels early to figure it out... the cycleway snapping transformation barely works.
It sounds like your work with placement will reveal more info about the pain of working with current assumptions. Maybe it'd also be worth trying to group/label corridors and junctions without any attempt to merge or transform them yet. That'll give us more understanding about what raw data the lowest layer should store and about any difficulties maintaining it during more complex operations.
It feels like IntersectionKind::Intersection
needs to be represented by an atom of nodes, so merging feels useful, but maybe it is easier to split into RoadNode
with Intersection
referencing a subgraph of Roads
and RoadNodes
, so merging becomes a set union on references, not a process of merging underlying data.