protomaps / basemaps

Basemap PMTiles generation and cartographic styles for OpenStreetMap data and more
https://maps.protomaps.com/
Other
347 stars 44 forks source link

sort_rank and highroad-style layering #72

Open bdon opened 1 year ago

bdon commented 1 year ago

In order to make correct casing ordering on complex overpasses, we may need to change Tilezen - Tangram relied on sort_rank with casings to overlay an arbitrary number of layers. For MapLibre GL we have sort_key in the style but that only works per-layer: casings needs to be drawn as separate layers.

In my experience the only solution is to have a fixed set of layers each with their own filter e.g. layers -2, -1, 0, 1, 2. @nvkelso

nvkelso commented 1 year ago

My work around recommendation is to have -1, 0, 1, and a single new MapLibre style layer for "even higher bridges" (level = 2, 3, 4, 5, 6).

That way you avoid parsing the MVT in MapLibre "again, again, again" looking for features at those levels per style layer – when mostly no MVT data layer features are found to be eligible for the GL style layers.

Ideally the "inline" and "outline" are in the same GL style layer and could use the existing sort_key from Tilezen (which would need a port over to this repo). But the explosion of MapLibre style layers for each kind and the levels is already burdensome...

bdon commented 1 year ago

@nvkelso I don't think there is still any solve for what you described like tilezen sort_key because of intersections? https://github.com/maplibre/maplibre/discussions/164#discussioncomment-4925345

So we need have 4 levels, say 5 road classes, and casings/inner strokes, 4x5x2 = 40 layers...

Unless you think we can combine all road classes into a single layer with expressions

wipfli commented 1 year ago

Not so flexible alternative is this: https://github.com/wipfli/single-highway-layer

bdon commented 1 year ago

@wipfli what if styles weren't embedded in the properties, but you had a single layer for all classes, and the paint properties were a (very big) case statement? So a complete stack would be like

tunnels_casing
tunnels
ground_casing
ground
bridges_casing
bridges
bridges_2_casing
bridges_2

Instead of having motorway, primary, etc.

wipfli commented 1 year ago

Yes the case approach works. A limitation is that MapLibre GL JS currently does not support data-driven line-dash arrays and some other properties like line-caps. Maybe worth talking to @zelonewolf and others from the American map style. They run into the same problem.

ZeLonewolf commented 1 year ago

Agreed, it's capability gap in the current maplibre-gl-js and we've resorted to very inefficient techniques to resolve it in Americana.

bdon commented 1 year ago

So if we designed around the constraint that there are no variation in linecaps or dashes, we can get away with the total set of ~8 road layers I listed above - it would be cleaner in the number of layers, and have beastly match/case expressions - is this really better for performance than having 40 layers, or about the same?

wipfli commented 1 year ago

regarding performance cc @msbarry

msbarry commented 1 year ago

Currently most of the processing time for each tile is evaluating each filter expression on each feature. I want to make a change that will make that more of a constant time for each feature regardless of how many layers there are. I need to set up new benchmarks in maplibre first though https://github.com/maplibre/maplibre-gl-js/issues/982

bdon commented 1 year ago

Looking at tag info for stats: https://taginfo.openstreetmap.org/keys/layer#values

96.99% of layer tags are -1, 0 or 1.

We can try a hybrid approach of @wipfli 's for the other 3.01% where a casing feature is duplicated in the tileset - this should have minimal bloat

So the complete stack of "normal" road symbology for all kinds is

roads_tunnel_2_minus
roads_tunnel_1_casing
roads_tunnel_1
roads_0_casing
roads_0
roads_bridges_1_casing
roads_bridges_1
roads_bridges_2_plus

For a total of 8 road MapLibre GL layers supporting an arbitrary # of flyover levels and tunnels.

bdon commented 1 year ago
Screenshot 2023-08-14 at 12 15 56

A style can already support an arbitrary # of layers by using data-driven line-color to visually distinguish each layer. This looks a bit weird aesthetically, and using casings is better in the majority of cases...

"line-color":["rgb",["-",255,["*",20,["to-number",["get","layer"]]]],255,["-",255,["*",20,["to-number",["get","layer"]]]]],
bdon commented 1 year ago

proof of concept working:

Screenshot 2023-08-14 at 14 23 51

The main problem now is the lack of data-driven line endings, because using butt will show seams, and using Round will end up like this:

Screenshot 2023-08-14 at 14 25 02
bdon commented 1 year ago

Planet build proof of concept live: 5 road classes, unlimited stacking, 8 MapLibre layers:

Screenshot 2023-08-14 at 20 57 52
bdon commented 1 year ago

Relevant MapLibre issue: https://github.com/maplibre/maplibre-gl-js/issues/2108