-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Style spec: provide a mechanism for composing styles #4225
Comments
Nice problem statement. I run into variations onto this theme a lot. A couple of awkward issues:
I guess I have issue 1 (knowing where to add my dynamic styles) but I never really noticed or bothered trying to solve it. I just stick mine at the end, over the top of other layers. Maybe a convenient interface would look something like:
Would it make sense to pre-define a couple of standard layer breakpoints that all maps are expected to conform to? Maybe that's too crazy. :) |
cc @mapbox/gl |
Standard “bookmark” layers would have to be limited to cross-platform concepts, so they wouldn’t for instance be usable for a user dot/puck, route lines, and GL annotations, features that would benefit from a bookmark and are supported in the mobile SDKs but not GL JS. |
"Nested" styles helps solve the common preserve-my-custom-layers-while-switching-"basemaps" problem. It may also help the carto team reuse some design elements across map styles (like label layers). |
The general concept behind #4225 (comment) would also help us achieve feature parity with competing SDKs on the native platforms: mapbox/mapbox-gl-native#8071. |
(Thanks @andrewharvey for pointing me here.) Here is an example where I needed to nest custom traffic data between town labels, but on top of roads: I imagine there are heaps more different use cases where inserting custom layers are necessary, but between different layers. Per #4690, I incorrectly used dataset On this stackexchange post, the order of map items are well defined, and I am sure many people would agree that this is largely correct in order of rendering:
However, Mapbox allows you to import individual layers from datasets, so these names can't be used without manually adding in metadata. Using the dataset names directly (like That said, I support @stevage's suggestion for 'breakpoints' that are very common in use. For instance, in this ascending order:
Most data will fit onto In order to avoid confusion with
This proposed If the style does not define breakpoints, this proposed property issues a warning in the browser. Existing styles would need to be modified to have these breakpoints defined. For users, they can drag layers in between these breakpoints. For existing styles, they could come with breakpoints. This also means, hopefully, that when you import new dataset from existing styles, it would import into the correct breakpoints. Empty styles would come with these breakpoints predefined, although they would be empty. Suggestions? |
In this StackOverflow post, I outlined a simple way to insert layers between roads and labels/shields/icons, which is a pretty common use case. That SO post was written in Swift for the Mapbox iOS SDK, but the same approach should be feasible in GL JS as well: iterate over all the layers in the map style, inserting the route layer above (after) the first non-symbol layer you find – that is, below (before) the last symbol layer you find. |
@1ec5 my understanding from documentation and trying this on GL JS is that the current behavior of addLayer(before) depends on map state. Specifically if no buckets have been created for the layer before, the layer is added at the end. This makes it really difficult to use addLayer (before) to reliably model scenarios like this without manually modifying style.json. AddLayer (before/after) works reliably for modeling only when working with Geojson sources. Is GL native implementation of addLayer more reliable when working with vector sources!? |
As far as I know, in the native SDKs, inserting a layer before or after a vector layer should work reliably as long as you do so after the style finishes loading. (In fact, the SDKs actively prevent you from adding layers before that point.) However, the native SDKs’ events don’t always correspond to the events in GL JS, so I don’t know if there’s an additional requirement that buckets are created beforehand. |
@mb12 if you experience inconsistencies in how layer order is handled, could you please set up a minimal JSFiddle test case that reproduces this? Sounds like a bug. |
With nested styles or “style components”, we’d be able to revisit mapbox/mapbox-gl-native#5665 so that only components affected by runtime styling would become static, while other components would continue to refresh periodically. It turns out that some developers have expected the style to continue to refresh despite making runtime styling changes. /cc @eschow |
@lucaswoj Is there a workaround for this? |
@pablo-slingshot Not really. My best recommendation is create a |
@lucaswoj I am considering something like your suggestion for merging my custom style (using my own layers and sources) with a standard basemap style. The potential issue I see is that I have a custom sprite and the style spec allows only one sprite. The idea of dynamically composing two sprites into one does not appeal (although I could potentially do it in the server code that generates my custom sprite on demand). Do you have any suggestions? Perhaps the sprite property of the style could accept an array of URLs and then either rely on the image names being unique across sprites, or add a layer property icon-sprite to disambiguate where needed (where icon-sprite specifies the last part of the URL path). |
@rogeraustin Merging sprites and glyphs is definitely a larger engineering problem but possible with the existing APIs ( |
@lucaswoj Are you able to elaborate on that? A recent question came up on StackOverflow on this topic: https://stackoverflow.com/questions/59738350/combine-more-than-one-style-in-a-map Would you mind spelling out the specifics of combining two font glyphs or two icon sprites? |
@leogermani - my advice would be:
setStyle is 'smart' and will do the minimal updates necessary to correctly update the map. Note that both styles must use the same sprite. If not, icons from the second style will not display (unless they also happen to be in the first sprite). This may be what you are seeing. If you have different sprites, then you should be able to add the images from the second sprite to the map using Map.addImage as @lucaswoj suggests above (I have not tried this). This would require you to load the sprite image, load and parse the corresponding sprite.json file to find all the individual images within it, carve these out of the sprite image using createImageBitmap, and add them using Map.addImage, renaming them as needed for uniqueness (and updating any style layers that use them to use the new names). You would also need to decide whether to use the normal or hi-res sprite. As I say, I have not done this and it seems it would be pretty painful - hence my suggestion above that the style spec could be updated to allow the sprite property to take an array of sprite urls and allow an icon-sprite property to be set on a style layer to disambiguate image names (if needed). This would enable proper merging of styles at the 'plain old data' level. |
Hi @rogeraustin , Thank you for your reply. I tried what you suggested and got exactly the same outcome. Same behavior, same object as my original screenshot. Here is some code: // styleDefinition is the response from the API
let currentStyle = map.getStyle();
Object.entries(styleDefinition.sources).forEach( ([source_key, source]) => {
//map.addSource( uniquePrefix + '_' + source_key, source );
currentStyle.sources[uniquePrefix + '_' + source_key] = source;
});
styleDefinition.layers.forEach(layer => {
layer.id = uniquePrefix + '_' + layer.id;
if ( layer.source ) {
layer.source = uniquePrefix + '_' + layer.source;
//map.addLayer( layer );
currentStyle.layers.push(layer);
}
});
map.setStyle( currentStyle ); How can I check and make sure "they are using the same sprites". Both styles have the same value in Thanks! |
|
tl;dr: The style spec needs a mechanism that allows for 'composing' styles / style layers.
A common use case for GL JS and GL Native is dynamically annotating an otherwise static "basemap" style with markers, routelines, areas-of-interest, etc. This poses difficulties at present, because the dynamic layers usually need to be placed somewhere in the middle of the "basemap" layers, which means:
addLayer()
,set{PaintLayout}Property()
APIs can lead to spaghetti-like 🍝 code."Smart
setStyle
" could provide some relief on number 2, since it allows authors to just write a function that produces the style they want. However, without a mechanism for composing styles, it only moves the problem, it doesn't solve it: a 'reactive' style-building function would still have to do ad-hoc surgery on the basemap to produce the desired style.Existing proposals:
addLayer()
(and maybe things like toggling visibility).style
layer type that recursively includes the layers from a different style, so that basemap styles could be shipped in multiple pieces (mapbox-streets-v9-bottom
,mapbox-streets-v9-top
) that authors could combine with their own layers.The text was updated successfully, but these errors were encountered: