mapbox-gl-js icon indicating copy to clipboard operation
mapbox-gl-js copied to clipboard

Add text clipping option on symbol layers

Open pixnlove opened this issue 8 years ago • 37 comments

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"
        }
     };

issue_mapbox

pixnlove avatar Jan 29 '17 23:01 pixnlove

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.

mollymerp avatar Jan 31 '17 22:01 mollymerp

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:

capture d ecran 2017-02-01 a 18 05 24

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 avatar Feb 01 '17 18:02 pixnlove

@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 avatar Feb 01 '17 18:02 mollymerp

@mollymerp, do you have some news about this feature request ?

pixnlove avatar Mar 23 '17 12:03 pixnlove

@pixnlove this is not on our immediate roadmap unfortunately.

mollymerp avatar Mar 23 '17 16:03 mollymerp

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.

screen shot 2017-04-10 at 09 34 06 An example here, 10 collides with 9 and therefore is hidden, but 9 is still above the icon of 10 😑.

nrako avatar Apr 10 '17 07:04 nrako

Equivalent issue for native: https://github.com/mapbox/mapbox-gl-native/issues/8235.

jfirebaugh avatar Aug 10 '17 16:08 jfirebaugh

I'll go ahead and upvote this feature.

wthorp avatar Oct 12 '17 15:10 wthorp

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}'
    }
});


sjorsrijsdam avatar Nov 16 '17 17:11 sjorsrijsdam

Oh that's a very interesting approach! Thanks @sjorsrijsdam

nrako avatar Nov 16 '17 17:11 nrako

@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?

nrako avatar Mar 15 '18 13:03 nrako

Not sure this limit is actually enforced... I can't spot anything on Map.addImage on Style.addImage nor the ImageManager.addImage

🤔

nrako avatar Mar 15 '18 14:03 nrako

@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 avatar Mar 15 '18 15:03 sjorsrijsdam

@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?

nrako avatar Mar 15 '18 15:03 nrako

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.

jfirebaugh avatar Mar 15 '18 16:03 jfirebaugh

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.

sjorsrijsdam avatar Mar 15 '18 16:03 sjorsrijsdam

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)?

Sirpion avatar Jul 01 '18 12:07 Sirpion

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.

andrewharvey avatar Sep 23 '18 14:09 andrewharvey

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?

kapone3047 avatar Mar 12 '19 22:03 kapone3047

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

Screen Shot 2019-10-27 at 17 45 10

Please tell me this is already fixed and I'm just looking in the wrong place

mushon avatar Oct 27 '19 15:10 mushon

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.

vjeranc avatar Dec 13 '19 16:12 vjeranc

Hi, any news on this feature request? Any possible workaround?

gotestrand avatar Dec 11 '20 15:12 gotestrand

Sorry, repeating the question: has anyone found reasonable workaround?

gitcatrat avatar Dec 28 '20 02:12 gitcatrat

@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 avatar Dec 28 '20 10:12 lukashass

@lukashass Not really an option because text-field contains a float that can be anything from 0 to millions.

gitcatrat avatar Dec 28 '20 13:12 gitcatrat

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:

  1. 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. 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. Screen Shot 2021-01-26 at 1 07 55 AM
Screen Shot 2021-01-25 at 2 56 05 PM

tristyntech avatar Jan 26 '21 07:01 tristyntech

this seems to be similar to the issue #10002 and could also be helped by #10123

xabbu42 avatar Jan 28 '21 14:01 xabbu42

      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.

image

julianmlr avatar May 04 '21 18:05 julianmlr

@julianmlr markers aren't as performant as symbols when you have to draw hundreds of them

pratik-kanthi avatar Sep 24 '22 09:09 pratik-kanthi

Any updates? MapBox still has this issue, really?

Merynek avatar Apr 26 '23 11:04 Merynek