-
-
Notifications
You must be signed in to change notification settings - Fork 756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic z-ordering across groups of style layers #2108
Comments
The fact that the text is 1 Mb doesn't mean the traffic is 1Mb and the fact that there are similar text is that file means compression will work better. Saying it's "probably" the reason is not data driven, can you please try and measure? Regarding the duplication and ordering there are two possible solution from my point of view
|
Sure, here are some cursory statistics (and of course I could generate more as needed):
This doesn't make much sense, and it suggests that I haven't described the problem well enough for people not familiar with the challenge of styling complex features like layered roads. Obviously I could change the loop value and generate copies of the bridge layer that run from 1 to 100, but we've found that performance degrades significantly as the number of layers increases. We used to have over 1,000 layers and it would crash web browsers trying to load. We've gotten it down to a bit over 300 layers with some creative use of the style expression. The approach taken by trivial stylings is just to give up and not even pretend that bridges can overlap, as shown in this OSM Bright rendering of the bridge ramps north of Boston: Now, if maplibre/maplibre#167 and/or maplibre/maplibre#164 were solved, that would certainly reduce the size of the 119K road layer by quite a bit, since each iteration of casing/fill/dashes means the creation of a new layer. However, it doesn't fix the fact that I'd still have to generate potentially infinite numbers of copies of the bridge layer in order to handle unbounded values of The layer tag in OSM is used as a sort ordering key and tends towards zero (most cases in the map of course are things like a single bridge,
I'll take the heat for not describing the problem well, but I hope you can appreciate that a team of pretty smart people haven't been able to solve this beyond kludgery, and that I'm not making up the fact that we have this problem. I'm also not making up the fact that osm-carto (really, CartoCSS stacks generally) are able to solve this problem for arbitrary values of Also I don't really think that sort key ordering of a group is conceptually complicated. I want to render ALL bridges of any type at Does maplibre intend to be powerful enough to match the features in CartoCSS? Hopefully the answer is "yes" and we're not just targeting lightweight use cases.
Yes, that's maplibre/maplibre#164, which would certainly help reduce our layer count though it wouldn't solve arbitrary layering, which is what I'm attempting to describe. To be clear -- I don't have the solution handy for how to do this, I'm just looking for the project to acknowledge that this is currently a gap that prevents it from matching other tech stacks feature-for-feature. |
I know it is not ideal, but packing all higways into a single layer with level instructions can solve this problem. https://wipfli.guthub.io/single-highway-layer. For this to be complete, we would need data-driven styling of line caps and dash arrays. |
Link above has a typo, should be https://wipfli.github.io/single-highway-layer instead of guthub. ;-) |
Thanks @adamfranco. But just to understand the problem, which one do you think is more problematic for you?: a) The style.json gets large (slow transmission, lot of mobile data usage) and can only handle a finite number of levels. |
To truly solve the problem it would have to be not just highways, but waterways, pipelines, buildings, and any other features that can have a |
Definitely this one. Even with a very fast connection rendering is slow and depends heavily on CPU speed. |
For me the bigger issue is (b). If one interacts with a complex map rendering using feature-rich OpenMapTiles (like Americana) and zooms at all, then the vector tiles may include ~1MB of data per zoom level. With just a bit of interaction the data loading of the vector tiles will greatly exceed the data-transfer of the style itself. |
Thanks for the detailed answer, but I'm still missing a clear problem definition as @wipfli mentioned: |
I noticed that Google Maps shows physically correct level ordering including different styles for tunnels and bridges only from something like z14. At lower zooms, they just do |
The main issue is the ability to render complex layerings at high zoom (13 or 14 is where the distinctions are generally visible). The discussion of performance is to demonstrate the pitfalls of naïve solutions such as "just replicate the layers multiple times". If we are able to jam our entire highway style into a single layer, that might minimize the problem, but it also kick the can down the road on more complex combinations like roads above or below buildings and that sort of thing. |
Maybe then the scope or goal of this issue is actually to implement dynamic z-ordering across multiple MapLibre GL JS style layers... |
Yes, that's exactly right, sorry if I didn't describe it well. |
OK I don't know rendering stuff, but maybe if we had some sort of off-screen rendering we could first draw lines and polygons to the off-screen render target and only then decide how to overlap them in a final render step based on some sort of global z-sort-key |
This is also apparently a duplicate of a Mapbox issue: mapbox/mapbox-gl-js#1349 |
This was my intent in opening this issue. Granted there are a number of related performance side effects that @zekefarwell and @adamfranco are laying out, but ultimately I can't z-order a group of layers to an arbitrary depth. |
I'm lost... :-( |
I'll mock up some illustrations which will hopefully better express the problem. |
Suppose we have a stacked interchange as follows, with 4 different levels of bridges. In other words, the lowest level (layer=1) is a motorway viaduct, and there are additional bridges at higher levels, tagged layer=2,3,4. It would look something like this in the data: We would desire a final result that looks something like this (casing and fill). Note how the layering between the different levels is discernible: For example, in this stacked interchange in Boston, you can clearly see which bridge is on top of which other bridge: So, in order to achieve the effect in our mock example, you would need 8 passes as follows: |
This describes the current way things work, I'm falling to understand the problem statement... :-/ |
The problem is that I have to anticipate the highest possible value of |
Is the problem the replication or that you don't know what N is? |
The problem is that I don't know what N is. Replication is easy, it's just a for loop! When we implemented this originally, we crawled the map looking for worst-case examples of stacked bridges and found that N=5 seemed to cover all the worst cases for roads. There's a few examples of layer>5 once you start including things like pedestrian overpasses over viaducts. So in other words, there's a copy of the road layer for 1 through 4 and then a final layer for all layer>=5. The moment someone tags two overlapping roads with layer values >=5 (for example, a layer=6 road on top of a layer=5 road), it'll render like an intersection instead of an overlap. While the wiki does recommend that people use the smallest possible values of Now at this point you are probably saying "who cares, you've got your five copies of your road layer, it's rendering the things, and it covers all the real world examples thrown out." The problem now becomes that:
I've already provided the numbers on the style.json size, and it should be obvious that a smaller style.json will load more quickly. We've also identified a very clear correlation between the number of layers and style performance, where >1000 layers means "crashes some web browsers" whereas a few hundred layers means "is noticeably laggy but still gets there eventually". So you can take your pick from the following problem statements that are all equally valid here:
Of all the problem statements, the number 2 is the most in-your-face, number 1 is dependent on both the decisions made by mappers and the complexity of the real world and will cause wrong rendering for cases of valid mapping, and number 3 is just unnecessary bloat that competes with tile data for network bandwidth. |
Thanks for the illustrations. I guess maplibre has just a limitation here that it cannot handle arbitrary ordering between style layers. |
The way I understand this issue is that it is not really a feature request, but more a description of the architecture that Mapbox designed. Allowing sorting only within a single layer is maybe just a limitation of the design they chose. And I think it is important for developers using MapLibre GL JS / Native to understand this limitation when designing map styles. @ZeLonewolf can you maybe condense the insights from this thread in the opening paragraph of the issue? I think we have a nice documentation here now of the current system and it would be great to take out the most relevant info and put it at the top of the thread. Like that, new people that come here and read this thread understand quickly the limitations of MapLibre. |
Looking into the future we can also think about what would be useful modifications to the style specification https://maplibre.org/maplibre-gl-js-docs/style-spec/, but I would like to separate such an enhancement discussion from the description of the current state. |
You could just generate vector tiles where everything road, tunnel, bridge related is in the same layer? |
I've updated the summary, hopefully that's more clear. |
That's already the case and is irrelevant to this issue. Here's a location with stacked bridges in OpenMapTile's transportation layer. I think perhaps you meant to suggest that all roads be generated in one style layer, which is not currently possible due to the lack of support for casing, casing dash patterns and/or fill dash patterns, all of which we would need in order to condense our road style down to a single layer. Then we'd be able to use line-sort-key. |
I'm a bit surprised at the hesitance I'm reading in this thread. My understanding is that MapLibre aims to be the best open source vector tile renderer for OSM data (as well as other sources of course). With that in mind I had thought there would be a general openness to expanding functionality to match what is possible in Mapnik based raster maps. The feature we are discussing in this thread is necessary for any OSM based map that aims to live up to the standard set by OpenStreetMap Carto. I highly recommend reading this post that dives into layered feature rendering in detail: https://imagico.de/blog/en/navigating-the-maze-part-1/ . The sense I've gotten from this thread is that improvements in this problem space are not welcome in MapLibre. Perhaps I've just gotten the wrong impression though. |
Divergence alert!Before using Mapbox-GL, the Israel Hiking Map was produced as a raster map using the no-longer-maintained Maperitive program. When writing the style definitions for Maperitive we didn't need to bother ourselves with OSM For example, this interchange was correctly rendered while the style described just how each The approach taken by Maperitive is this:
If I may proposed a similar approach for MapLibre, it could be this:
An interesting implication of this idea is that approach does not need any spec change. It can be an non-default option for Maplibre-GL-JS. Maps that want to leverage it will need to turn it on explicitly, while the rest of the world maps continue to work as before. Clearly this is only a rough direction that needs further refinement before it can turn into an implementation. |
This would not work for tunnels, which typically have a layer value <0. Generally you want tunnels to be drawn on top of surface features even though they're logically underneath them. |
You're right. This idea was too simplistic. Maperitive has a higher-level "drawing priority" on top of the OSM
In addition, the default "drawing priority" of a layer can be overwritten. For example, some terrestrial contour lines do not align well with the outlines of water bodies and we want water areas to be drawn above the contour lines while the contour lines need to be drawn above other fill area, such as land use. |
I'm exploring a mechanism where a layer can have a key that means "sort with previous", which indicates that the sort key should apply across multiple layers. It would reduce the size of Americana's style JSON by 2/3 and drop the layer count from over 300 to 142. However, it will be a fair amount of code to implement, and I don't know how it will perform yet. The mechanism I'm envisioning would have a version of the drawLine() function that can accept an array of layers and then slice and dice the layer buckets to get the desired draw ordering. Of course, this is all apocryphal until I can get it working. |
By the way, Mapbox GL JS started work on a line casing property: mapbox/mapbox-gl-js#4162 (comment). However, it doesn’t seem to be documented or usable yet, and it doesn’t address the need for other line components such as dash overlays. Developers also want to be able to group style layers to implement visually complex markers. For example, it’s very common to want to put a solid colored disc behind a marker icon to signify that it’s clickable. Of course, one can redesign the icons themselves to include the disc, but many developers need more flexibility. For example, the icon’s image may depend on one property while the circle’s color or size depends on another; combining the two would bloat the sprite sheet. Instead, a common workaround is to put a circle layer behind the symbol layer. This works until the point features end up too close to each other, causing the discs to overlap with each other without overlapping symbols in the symbol layer: mapbox/mapbox-gl-js#10002 mapbox/mapbox-gl-js#10021. With symbol collision enabled, discs and icons collide independently, ruining the illusion of these elements being grouped: mapbox/mapbox-gl-js#6025.1 Developers can avoid these issues by eschewing the style specification entirely, replacing their layers with DOM-backed markers or view-backed annotations (on iOS/macOS). These alternatives aren’t well-integrated into the style: they can’t underlap any style layers, and tilting the map ruins the illusion of these overlays being part of the map. Drawing images on demand using Footnotes
|
I made a proof of concept of arbitrary depth stacking that requires tileset-side support but no additional features in MapLibre GL. The essence is that for As a bonus, we combine 5 different road classifications into one layer using data-driven styles. For the equivalent "traditional" styling we would need 8 (-2,-1,0,1,2,3,4,5 typically) levels, * 5 classifications, * 2 casing or 80 layers. This style is 8 layers, with some caveats you can read about here: https://github.com/bdon/HighRoadPlus Sydney (tunnels to -5): link to map Boston 1/I93: link to map anyone have another favorite extreme case for testing? |
Thanks for documenting and demonstrating this workaround. Duplicating the feature to represent the casing does work, but it’s less than ideal for the reasons you’ve laid out. It reminds me of how the Mapbox Streets source duplicates the union of all waterbodies/waterways to allow for seamless water outlines. Or of how OpenMapTiles includes both the 3D building parts and a redundant building outline from OSM data. In both cases, the redundant features are useful or not depending on the style’s design needs. OSM Americana has followed some of the documented approach, in terms of minimizing the number of line layers per
While some styles color-code roads by road classification, others also implement things like surface or access restrictions using dash arrays. Moreover, users intuitively expect features to layer based on physical elevation, even on a 2D map. Railroads can go over or under roads; so can buildings and in some cases waterways. But tunnels often still need to draw over the layer that obscures the tunnel in physical space (typically parks and waterbodies, not other roads). It’s a bit of a conundrum. |
@1ec5 I think the tunnels over parks/waterbodies thing is addressed already by the majority of map styles -I'm more interested in the railways case. The 8-roads-layer approach I described above can still admit railways in separate layers (not included in those 8) with Do you have any specific places in OSM to point to here? I'll go spelunking for these situations, but it seems the common case of intense highway flyovers is negatively correlated to places that have lots of rail ... Best place to start may be Japanese monorail systems! |
I think this case is handled correctly So we need to look for
This finds all layer >=3 railways, then scrub around to find the ones that intersect or run concurrent to roads. |
The biggest complication regarding railways is that every distinct type of railway needs a different dash array: spurs and sidings, disused, monorails, narrow gauge, etc. Each of these types needs a separate line layer, which then gets duplicated per physical layer. Once |
We're hitting the same issue in one of our projects where line-sort-key is limited to a layer. |
I believe a nice interface would be
Adjacent layers with matching |
Motivation
The OSM Americana project would like to efficiently and properly render "stacked" bridges and tunnels, in which multiple bridges or tunnels overlap each other. This occurs in large dense cities.
While there is currently a line-sort-key in the style specification, this only works on a single layer. In order to draw the various combinations of road, bridge, and tunnel, we are rendering a casing, a fill, sometimes a casing dash pattern, and sometimes an inner fill dash pattern. It is not currently possible in the style spec to merge all these together into a single layer, and thus each component of the style must be rendered as a separate layer.
What is needed is a group-line-sort-key in order to apply the sort-key logic across a consecutive group of layers.
For example, this location in Boston has several overlapping bridges:
The overlap is indicated with tagging of
layer
. So the first bridge might belayer=1
, the second bridge islayer=2
and so forth.Stacked roads are represented in vector tiles with an attribute that indicates the z-order of each road:
The desired end-state looks something like this, where the roads are drawn casing-fill-casing-fill-casing-fill for each layer. This is outlined in #2108 (comment):
Currently, the only way to do this is to repeat the entire road layer for
layer=1
,layer=2
, etc., up to some value of N where nobody has mapped two or more overlapping roads with a value oflayer
that's greater than N. In practice, 5 layers should cover all cases on earth for roads (as of 2023). However, there's no guarantee that mappers use small values oflayer
-- the only thing that you can count on is the ordering. This is the hack currently used in the OSM Americana project.In Sydney, Australia there's a series of similarly overlapping tunnels. However, these do not overlap properly (ZeLonewolf/openstreetmap-americana#27) -- the layer values descend to -4, -5, etc., well beyond that style's assumed limit of -2 for tunnels (after all -- how stacked could tunnels possibly be?).
Duplicating the entire transportation layer code to achieve bridge/tunnel layering is, a MASSIVE inflation of the layer count, and one of the reasons that the OSM Americana style.json is nearly a megabyte in size and most certainly is a performance problem.
Usage
Ideally, We would like to be able to specify that a group of layers should obey a sort-order key, where multiple passes are made across the group of layers, rendering one layer value at a time until all layer values are exhausted. It's sufficient to assume that the group of layers are all consecutive.
The text was updated successfully, but these errors were encountered: