mapbox-gl-js
mapbox-gl-js copied to clipboard
Add text clipping option on symbol layers
Hi everybody !
I have a problem with my symbol layer : my text is not hidden by other symbols, how to do like on the second screenshot (screenshot from https://roadtrippers.com who is using Mapbox too)? FYI, my layer looks like :
var layer = {
"id": "marker",
"type": "symbol",
"source": "markers",
"layout": {
"text-font": ["Lato Black"],
"text-padding": 20,
"text-anchor": "top",
"text-offset": [0, -2.3],
"text-size": 14,
"text-allow-overlap": true,
"text-field": "{index}",
"text-justify": "center",
"icon-offset": [0, -22],
"icon-image": "marker",
"icon-allow-overlap": true
},
"paint": {
"text-color": "#FFFFFF"
}
};
Hi @pixnlove,
I would try setting "text-allow-overlap": false
and "text-optional": true
. Also, Roadtrippers is using the Marker
class, not a symbol layer.
This doesn't appear to be a bug or feature request, and unfortunately we don't have the bandwidth to offer support on Github. I suggest you post future questions like this on StackOverflow.
Thanks for your help @mollymerp. I think it's a feature request but maybe I'm doing something wrong. When I'm adding these two properties, I'm losing some text values and text index behaves badly. My map looks like now:
And my layer is now:
var layer = {
"id": "marker",
"type": "symbol",
"source": "markers",
"layout": {
"text-font": ["Lato Black"],
"text-padding": 20,
"text-anchor": "top",
"text-offset": [0, -2.3],
"text-size": 14,
"text-allow-overlap": true,
"text-field": "{index}",
"text-justify": "center",
"icon-offset": [0, -22],
"icon-image": "marker",
"icon-allow-overlap": true
},
"paint": {
"text-color": "#FFFFFF"
}
};
Using marker like Roadtrippers is not very powerful with a large dataset, I would like to do the same thing with a symbol layer but you seem to consider the text as an other layer. It will be cool to have a layout property "text-clip-icon".
@pixnlove I see what you mean. I think this might be a technical limitation of our current implementation of symbol layers – we draw all icons in a layer, and then all text on top of that, so there is no maintained relationship between a single icon and its text label in the render order. I agree this would be a great enhancement, but it would require a significant refactor.
@mollymerp, do you have some news about this feature request ?
@pixnlove this is not on our immediate roadmap unfortunately.
I have the same undesired behavior but with a symbol layer. Using an icon with some inner text, I'm using that workaround with text-optional
. But that isn't a really neat result. I wish the text and icon/marker wouldn't appear to be drawn on two different layer.
An example here, 10 collides with 9 and therefore is hidden, but 9 is still above the icon of 10 😑.
Equivalent issue for native: https://github.com/mapbox/mapbox-gl-native/issues/8235.
I'll go ahead and upvote this feature.
For those interested, we also ran into this issue and we "solved" it by generating icons on the fly on a off-screen canvas and using token substitution to match the correct image with the correct point.
Roughly what we did is:
prices.forEach((price) => {
const imageData = createIcon(price);
map.addImage('house-' + price, imageData);
});
map.addLayer({
layout: {
'icon-name': 'house-{price}'
}
});
Oh that's a very interesting approach! Thanks @sjorsrijsdam
@sjorsrijsdam – I was looking at the API doc for map#addImage
and I noticed the following :
An Map#error event will be fired if there is not enough space in the sprite to add this image.
Then I found this information:
Sprites can have a maximum size of 1024x1024 pixels (2048x2048 for high DPI displays) – that means the whole sprite containing all icons must be smaller than 1024x1024 pixels.
Did you ever encountered that issue, what type of scale do you have experience with? Could you confirm that 1024x1024 is the limit?
Not sure this limit is actually enforced... I can't spot anything on Map.addImage on Style.addImage nor the ImageManager.addImage
🤔
@nrako We never ran into limits of the sprite size. We did have to abandon this idea though because of performance problems on mobile. Every time you would move or zoom the map, Mapbox would need to transfer the icon data of each icon to the WebGL context. That turned out to be a bottle neck.
@sjorsrijsdam – Thank you for sharing your experience. I'm not totally sure I understand why Mapbox would need to transfer all images data to the WebGL context on all "camera change" – my quick research on that topic was vain. I might just try and inspect that by myself.
Could you share what approach you ended up taking?
Mapbox GL JS does not transfer all images data to the WebGL context on every camera change -- only when a tile is loaded or the image is changed.
We ended up using just a single image for all items on the map and used a popup to show the price.
I probably should have added that we also fetched new data for that layer on every moveend
en zoomend
. So, it was probably the setData
call that would trigger a regeneration of tiles.
At the moment it turns out that there are 3 possible options (to create behavior as in the image in the first post): 1. Use the markers https://bl.ocks.org/stevage/23d881a66e2bcca385d8cc074691b674 2. Generating icons on the fly https://jsfiddle.net/gxc5ca9d/15/ 3. Adding each icon to a new layer https://codepen.io/Sirpion/pen/MXLvbO/
Suppose there is a need to create> 400 labels with texts that will be moved on the map in real time. Which of these options will cause less damage to performance? Or there were new options to implement this behavior (without hiding the text in a collision)?
One solution to this may be to group these layers in a way that tells the renderer to draw these on a per feature level first. ie. feature A layer 1,2,3 . then render feature B layer 1,2,3, then composite those two.
This is a ridiculously simple and common use case that I can't believe is not supported. Have people found any practical workarounds for this?
Almost 3 years after this issue was opened, I still can't believe there is no proper solution from Mapbox to this basic issue. This is causing us major grief and is a huge blindspot for Mapbox

Please tell me this is already fixed and I'm just looking in the wrong place
There seems to be a symbolTextAndIcon
shader present. It draws the text and the icon at appropriate times but is not used for the purposes of our symbol layers.
https://github.com/mapbox/mapbox-gl-js/commit/1215bf138dc4982d8bcbc9c61dd3995ae4df3c0f
I've tried it out, drawing an image in the text-field
and it does work. Although, it would be nice that something like below works:
'text-field': ['format',
['get', 'number_field'], { text-offset: [0, 0.5] },
"\n", {},
['image', ['get', 'icon_field']], { text-offset: [0, 0.5] }
],
I guess, given the shader code one could make their own renderer/painter/customlayer for drawing text and icon in a single pass.
Hi, any news on this feature request? Any possible workaround?
Sorry, repeating the question: has anyone found reasonable workaround?
@gotestrand @gitcatrat We ended up generating icons on the fly with #map.event:styleimagemissing as suggested above.
Have a look at Map.vue#L258. We did not notice any performance problems, but keep in mind our icons are limited to only around 200 different ones. You can see the result of this at https://kiel-live.github.io/map
@lukashass Not really an option because text-field
contains a float that can be anything from 0 to millions.
I don't know if this will work for everyone, but this worked for me. On the bottom you can see a number of the circles do NOT have the text overlay. On the top you can see it is fixed. The way I did it was:
- set the text symbol layers 'text-padding': 0. The text padding defaults to 2, so it makes the text clustering a little over-sensitive
- 2 and 3 digit numbers have a wider radius than the single digit numbers (obviously). So for the 2 digit numbers I made the text smaller than the single digit numbers. And I made the 3 digit numbers slightly smaller than that, using an expression.
I wish that I was able to keep the text the same size but this was my work around. And that's it. Hope it helps someone.

this seems to be similar to the issue #10002 and could also be helped by #10123
let markers = {};
this.realEstates.features.forEach(realEstate => { let el = document.createElement('div'); el.innerHTML = "<span style=\"font-family:quicksand\" class='text-body-1'>" + this.$n(realEstate.properties.price, 'currencyNoCents') + "</span>" ; el.className = 'rounded-label'; markers[realEstate.id] = new Mapbox.Marker(el) .setLngLat(realEstate.geometry.coordinates) .addTo(this.map); })
.rounded-label { background: white; padding: 0.25em 0.5em; font-size: 16pt; border-radius: 1em; border:1px solid lightgrey;
}
Here another version of this here https://bl.ocks.org/stevage/23d881a66e2bcca385d8cc074691b674 Makers really have a nice behavior. Maybe this helps you.
@julianmlr markers aren't as performant as symbols when you have to draw hundreds of them
Any updates? MapBox still has this issue, really?