Mapbox-gl-js: style.load does not fire when switching from one raster layer to another

Created on 9 Nov 2018  路  11Comments  路  Source: mapbox/mapbox-gl-js

When a map has two or more raster layers, and you switch from one raster layer to another, style.load does not fire. By comparison, if you switch to one of them from a non-raster layer, it does.

Example:

https://jsbin.com/dozaqaduku/edit?html,output

mapbox-gl-js version: 0.51.0

browser: Chrome

Steps to Trigger Behavior

  1. Load page (defaults to 'Basic' style)
  2. Switch to 'Streets' - style is switched to, and popup is created showing style.load has fired.
  3. Switch to 'raster1' - style is switched to, and popup is created showing style.load has fired.
  4. Switch to 'raster2' - style is switched to, but popup is not created.
  5. Switch to 'Streets' - style is switched to, and popup is created showing style.load has fired.
  6. Switch to 'raster2' - style is switched to, and popup is created showing style.load has fired, compared to step 4 where it did not.

Link to Demonstration

https://jsbin.com/dozaqaduku/edit?html,output

Expected Behavior

In step 4, the popup should be created.

Actual Behavior

In step 4, the popup is not created.

Code of example

Minimised testcase, based on minimal changes from examples: Change a map's style and Add a raster tile source.

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title>Change a map's style</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.css' rel='stylesheet' />
    <style>
        body { margin:0; padding:0; }
        #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
</head>
<body>

<style>
    #menu {
        position: absolute;
        background: #fff;
        padding: 10px;
        font-family: 'Open Sans', sans-serif;
    }
</style>

<div id='map'></div>
<div id='menu'>
    <input id='basic' type='radio' name='rtoggle' value='basic' checked='checked'>
    <label for='basic'>basic</label>
    <input id='streets' type='radio' name='rtoggle' value='streets'>
    <label for='streets'>streets</label>
    <input id='bright' type='radio' name='rtoggle' value='bright'>
    <label for='bright'>bright</label>
    <input id='light' type='radio' name='rtoggle' value='light'>
    <label for='light'>light</label>
    <input id='dark' type='radio' name='rtoggle' value='dark'>
    <label for='dark'>dark</label>
    <input id='satellite' type='radio' name='rtoggle' value='satellite'>
    <label for='satellite'>satellite</label>
    <input id='raster1' type='radio' name='rtoggle' value='raster1'>
    <label for='raster1'>raster1</label>
    <input id='raster2' type='radio' name='rtoggle' value='raster2'>
    <label for='raster2'>raster2</label>
</div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiY2FtdW5pZ2VvZyIsImEiOiJlOTVlZWNjYTM5NjJiNTQyY2NiNTQ3YjAyNGU5MjIyMyJ9.f4__2uuNivyAvtvYgfFhLQ';
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/basic-v9',
    zoom: 13,
    center: [4.899, 52.372]
});

var layerList = document.getElementById('menu');
var inputs = layerList.getElementsByTagName('input');

function switchLayer(layer) {
    var layerId = layer.target.id;
    if (layerId == 'raster1') {

        var style = {
            "version": 8,
            "sources": {
                "hot": {
                    "type": "raster",
                    "tiles": [
                        "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png ",
                        "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
                        "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
                    ],
                    "tileSize": 256,
                    "attribution": "Humanitarian"
                }
            },
            "layers": [{
                "id": "simple-tiles-hot",
                "type": "raster",
                "source": "hot",
            }]
        }

        map.setStyle(style);

    } else if (layerId == 'raster2') {

        var style = {
            "version": 8,
            "sources": {
                "mapnik": {
                    "type": "raster",
                    "tiles": [
                        "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
                        "https://b.tile.openstreetmap.org/{z}/{x}/{y}.png",
                        "https://c.tile.openstreetmap.org/{z}/{x}/{y}.png"
                    ],
                    "tileSize": 256,
                    "attribution": "OpenStreetMap"
                }
            },
            "layers": [{
                "id": "simple-tiles-mapnik",
                "type": "raster",
                "source": "mapnik",
            }]
        }

        map.setStyle(style);

    } else {
        map.setStyle('mapbox://styles/mapbox/' + layerId + '-v9');
    }
}

for (var i = 0; i < inputs.length; i++) {
    inputs[i].onclick = switchLayer;
}

map.on ('style.load', function () {
    alert('style.load triggered');
});

</script>

</body>
</html>
bug

Most helpful comment

@mvl22 You are correct, I was experiencing the same issue, however there is a workaround.

What you can do is trigger a styledata event "once" before setting the style. That way the event will on fire a single time.

map.once('styledata', () => {
  // do what you need to do here
}

map.setStyle('http://someurl');

This should hopefully work for you.

All 11 comments

@mvl22 Thank you for the detailed report. The style.load is a private event. Have you looked into using the styledata event to be notified when switching between the two raster styles? It turns out that since the two raster styles are defined in-line, as opposed to loaded from a URL, there is no style.load when switching from one to the next.

@asheemmamoowala Hey thanks for the suggestion. I had the same issue as @mvl22, but couldn't actually identify what the problem was. I'm not sure where i got the tip to use the style.load event (probably from GitHub) since I remember struggling to find a solution since a lot of the available Mapbox events were not included in the documentation a few months back. This has obviously been fixed as I can now see styledata listed. Swapping over to styledata seems to work correctly! Cheers.

Great to hear that styledata works for you!

Have you looked into using the styledata event to be notified when switching between the two raster styles?

I did, but that fires multiple times, making it non-usable. Is that the intended behaviour?

https://jsbin.com/tigoqujama/edit?html,output
(exactly the same as my previous reproduce case, but with the event changed.)

As far as I can see, styledata is fired three times for a vector style, and twice for a raster style.

Visually inspecting the the event using console.log (e), if passed in, I can't immediately see any difference between the three firings. I was hoping this might provide some means for application code to disambiguate them, reducing this down to a single call.

I see that others seem to have run into a similar problem: https://github.com/mapbox/mapbox-gl-js/issues/6707

@mvl22 You are correct, I was experiencing the same issue, however there is a workaround.

What you can do is trigger a styledata event "once" before setting the style. That way the event will on fire a single time.

map.once('styledata', () => {
  // do what you need to do here
}

map.setStyle('http://someurl');

This should hopefully work for you.

@jacknkandy @asheemmamoowala I have one more question here, will styledata on fire even if I set a style the same as map's previous style? According to the documents:

Fired when the map's style loads or changes. See MapDataEvent for more information. - from https://docs.mapbox.com/mapbox-gl-js/api/#map.event:styledata

If not, which event is recommended for this situation (I am not sure whether the style will be the same as before or not)?

// already did `map.setStyle(previousProps);` on map initialization work

...

map.setStyle(newProps);

map.once('styledata', () => {
  // will the event on fire
  // if newProps === previousProps?
}

Hi @hijiangtao. I have just tested out your question and it does appear that the styledata event still fires even when the style is the same as what is previously set.

@jacknkandy Thanks for replying. I found .setStyle had a second parameter with contains diff property, and it's default to false, which means .setStyle will always force update style by default. But when we set it to true, setStyle will compare previous style and current setting style, to decide whether to change the styles.

setStyle(style, options?)
... If a style already is set and options.diff is true, this compares the style against the map's current state and performs only the changes necessary to make the map style match the desired state. from https://docs.mapbox.com/mapbox-gl-js/api/#map#setstyle

So it could fire sometimes. :-)

@hijiangtao Ah I didn't realise that property was available - that is quite useful. Cheers

@mvl22 You are correct, I was experiencing the same issue, however there is a workaround.

What you can do is trigger a styledata event "once" before setting the style. That way the event will on fire a single time.

map.once('styledata', () => {
  // do what you need to do here
}

map.setStyle('http://someurl');

This should hopefully work for you.

Yes.

 map.on("styledata", loadTiles);    // will fire multiple 3 times, whenever style changed.

  map.once('styledata', loadTiles);   //The listener will be called first time the event fires after the listener is registered.

It works!!!!!!

The answer in 2019 is:

map.once('styledata', loadTiles); //The listener will be called first time the event fires after the listener is registered.

      map.once('styledata', loadTiles);   //The listener will be called first time the event fires after the listener is registered.


     map.on("styledata", loadTiles);    // will fire multiple 3 times, whenever style changed.  
                                // event.stopPropagation(); does not work.


     map.on("load", loadTiles);       // only fire 1 time. but when you change base map, use below

  map.setStyle('mapbox://styles/mapbox/' + layerId, {diff: false});

    //  on load event will not fire, which I need it fire to re-load geojson layer. 


       map.on("styledata"    // works fine, but it fire 3 same event at same time, cause load 3 times geojson layer, cause other error when you load 3 times geojson layer at same time.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

bgentry picture bgentry  路  3Comments

aaronlidman picture aaronlidman  路  3Comments

BernhardRode picture BernhardRode  路  3Comments

aendrew picture aendrew  路  3Comments

Scarysize picture Scarysize  路  3Comments