10
votes

I am using mapbox-gl-js to render points from a geojson file onto a map.

For each point I also show a label underneath the marker icon. I currently do this with the following code:

map.addSource("mypoints", {
    type: "geojson",
    data: "mypoints.geojson",
});

map.addLayer({
    "id": "layer-mypoints",
    "type": "symbol",
    "source": "mypoints",
    "layout": {
        "icon-image": "marker-15",
        "text-field": "{name}",
        "text-anchor": "top"
    }
});

This works as expected and the points are added to the map and the label is rendered under each point.

To make the map less cluttered I would like to hide the labels when the map is zoomed out past a certain zoom level (and vice versa show the labels when the map is zoomed in). I always want to show the point icons no matter what the zoom level is.

I have no idea how to do this. Any ideas of how to achieve this would be greatly appreciated!

2

2 Answers

17
votes

After doing some more thinking/reading/testing I think I have found a solution to the problem.

I now add one layer that only shows the icons, and also add a second layer that only contains the labels. In this second layer I set the 'minzoom' property to the zoom level where I want the labels to appear when the user zooms in the map.

map.addSource("mypoints", {
    type: "geojson",
    data: "mypoints.geojson",
});


// Layer with icons that should always be visible
map.addLayer({
    "id": "layer-mypoints",
    "type": "symbol",
    "source": "mypoints",
    "layout": {
        "icon-image": "monument-15",
        "icon-allow-overlap": true
    }
});            

// Layer with just labels that are only visible from a certain zoom level
map.addLayer({
    "id": "layer-mypoints-label",
    "type": "symbol",
    "source": "mypoints",
    "minzoom": 12, // Set zoom level to whatever suits your needs
    "layout": {
        "text-field": "{name}",
        "text-anchor": "top",
        "text-offset": [0,0.5]
    }
});

This seems to work really well for my needs.

16
votes

If you don't want multiple layers, there's an alternative work-around that I use.

Given your original code, you can use the text-size property with zoom-dependent stops:

map.addSource("mypoints", {
    type: "geojson",
    data: "mypoints.geojson",
});

map.addLayer({
    "id": "layer-mypoints",
    "type": "symbol",
    "source": "mypoints",
    "layout": {
        "icon-image": "marker-15",
        "text-field": "{name}",
        "text-anchor": "top",
        "text-size": {
            "stops": [
                [0, 0],
                [3, 0],
                [4, 10]
            ]
        }
    }
});

The value is interpolated between stops, so between zoom 0 and 3 the text-size is interpolated between 0 and 0, resulting in... 0 (i.e. absent). From zoom 3 it gives the text the effect of enlarging until zoom 4 (I like the effect, but that's personal). From zoom 4 text-size will just be constant at 10. If you don't want the "embiggening" effect between zoom 3 and 4, you can also specify a fractional zoom: just change 4 to 3.1.