9

I have a map wher we can classically switch from one style to another, streets to satellite for example.

I want to be informed that the style is loaded to then add a layer.

According to the doc, I tried to wait that the style being loaded to add a layer based on a GEOJson dataset.

That works perfectly when the page is loaded which fires map.on('load') but I get an error when I just change the style, so when adding layer from map.on('styledataloading'), and I even get memory problems in Firefox.

My code is:

mapboxgl.accessToken = 'pk.token';
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v10',
    center: [5,45.5],
    zoom: 7
});

map.on('load', function () {

    loadRegionMask();
});

map.on('styledataloading', function (styledata) {

    if (map.isStyleLoaded()) {
        loadRegionMask();
    }
});

$('#typeMap').on('click', function switchLayer(layer) {
    var layerId = layer.target.control.id;

    switch (layerId) {
        case 'streets':
            map.setStyle('mapbox://styles/mapbox/' + layerId + '-v10');
        break;

        case 'satellite':
            map.setStyle('mapbox://styles/mapbox/satellite-streets-v9');
        break;
    }
});

function loadJSON(callback) {   

  var xobj = new XMLHttpRequest();
      xobj.overrideMimeType("application/json");

  xobj.open('GET', 'regions.json', true);

  xobj.onreadystatechange = function () {
        if (xobj.readyState == 4 && xobj.status == "200") {
          callback(xobj.responseText);
        }
  };
  xobj.send(null);  
}

function loadRegionMask() {

  loadJSON(function(response) {

    var geoPoints_JSON = JSON.parse(response);

    map.addSource("region-boundaries", {
      'type': 'geojson',
      'data': geoPoints_JSON,
    });

    map.addLayer({
        'id': 'region-fill',
        'type': 'fill',
        'source': "region-boundaries",
        'layout': {},
        'paint': {
            'fill-color': '#C4633F',
            'fill-opacity': 0.5
        },
        "filter": ["==", "$type", "Polygon"]
    });
  });
}

And the error is:

Uncaught Error: Style is not done loading
    at t._checkLoaded (mapbox-gl.js:308)
    at t.addSource (mapbox-gl.js:308)
    at e.addSource (mapbox-gl.js:390)
    at map.js:92 (map.addSource("region-boundaries",...)
    at XMLHttpRequest.xobj.onreadystatechange (map.js:63)

Why do I get this error whereas I call loadRegionMask() after testing that the style is loaded?

fralbo
  • 2,534
  • 4
  • 41
  • 73
  • Possible duplicate of [Style is not done loading: Mapbox GL JS](https://stackoverflow.com/questions/40557070/style-is-not-done-loading-mapbox-gl-js) – Eldan Goldenberg Oct 06 '17 at 23:53

9 Answers9

8

1. Listen styledata event to solve your problem

You may need to listen styledata event in your project, since this is the only standard event mentioned in mapbox-gl-js documents, see https://docs.mapbox.com/mapbox-gl-js/api/#map.event:styledata.

You can use it in this way:

map.on('styledata', function() {
    addLayer();
});

2. Reasons why you shouldn't use other methods mentioned above

  1. setTimeout may work but is not a recommend way to solve the problem, and you would got unexpected result if your render work is heavy;
  2. style.load is a private event in mapbox, as discussed in issue https://github.com/mapbox/mapbox-gl-js/issues/7579, so we shouldn't listen to it apparently;
  3. .isStyleLoaded() works but can't be called all the time until style is full loaded, you need a listener rather than a judgement method;
hijiangtao
  • 843
  • 1
  • 7
  • 12
  • 3
    I'm finding with `mapbox-gl 1.1.1` that this doesn't work without modification: the `styledata` event is fired multiple times, so I get errors from trying to re-add a layer that already exists. I needed also to create a boolean flag that tracks whether the layers have already been added: it's set to `false` whenever style changes, and to `true` when `addLayer()` is called. It's very hackish, but seems necessary. – thund Jul 25 '19 at 22:52
  • @thund is correct, this event gets fired multiple times. Moreover, it never actually gets fired *after* the style is finished loading. See [my answer](https://stackoverflow.com/a/57792470/165673) for better solution. – Yarin Sep 04 '19 at 16:28
6

Ok, this mapbox issue sucks, but I have a solution

myMap.on('styledata', () => {
  const waiting = () => {
    if (!myMap.isStyleLoaded()) {
      setTimeout(waiting, 200);
    } else {
      loadMyLayers();
    }
  };
  waiting();
});

I mix both solutions.

Nathan Redblur
  • 632
  • 6
  • 12
  • Note that style.load is apparently *not* part of the public API, nor does it seem to fire when the style is defined in-line as opposed to from a URL: https://github.com/mapbox/mapbox-gl-js/issues/7579 – fooquency Nov 13 '18 at 17:13
  • 1
    This works well when using `styledata` instead of `style.load`. Thanks! – tempranova Mar 01 '19 at 19:31
3

I was facing a similar issue and ended up with this solution:

I created a small function that would check if the style was done loading:

// Check if the Mapbox-GL style is loaded.
function checkIfMapboxStyleIsLoaded() {
  if (map.isStyleLoaded()) {
    return true; // When it is safe to manipulate layers
  } else {
    return false; // When it is not safe to manipulate layers
  }
}

Then whenever I swap or otherwise modify layers in the app I use the function like this:

function swapLayer() {
  var check = checkIfMapboxStyleIsLoaded();
  if (!check) {
    // It's not safe to manipulate layers yet, so wait 200ms and then check again
    setTimeout(function() {
      swapLayer();
    }, 200);
    return;
  }

  // Whew, now it's safe to manipulate layers!
  the rest of the swapLayer logic goes here...

}
Bwyss
  • 1,736
  • 3
  • 25
  • 48
  • 1
    yes I done the same thing, waiting that Mapbox team fix that bug. – fralbo Nov 18 '17 at 18:37
  • 1
    I'm also using this hack, but starting with MapBox v0.42.1 it's not enough to check `isStyleLoaded`. I'm currently doing `typeof map.getSource('foo') === undefined`. (FWIW, I think the other answer is right that it's better to arrange that your code doesn't run at all until after the map and style have finished loading.) – Nelson Nov 21 '17 at 01:30
  • That first code block is entirely unnecessary. If you ever see return true followed by a return false, just return the condition. Then that leaves only the return of the mapbox isStyleLoaded() call. You should just have var = map.isStyleLoaded() – four43 Jun 23 '21 at 11:12
1

Use the style.load event. It will trigger once each time a new style loads.

map.on('style.load', function() {
    addLayer();
});
Luis Estevez
  • 1,387
  • 12
  • 19
  • 1
    Note that style.load is apparently *not* part of the public API, nor does it seem to fire when the style is defined in-line as opposed to from a URL: https://github.com/mapbox/mapbox-gl-js/issues/7579 – fooquency Nov 13 '18 at 17:13
1

My working example:

when I change style map.setStyle()

I get error Uncaught Error: Style is not done loading

This solved my problem

Do not use map.on("load", loadTiles);

instead use

map.on('styledata', function() { addLayer(); });

when you change style, map.setStyle(), you must wait for setStyle() finished, then to add other layers. so far map.setStyle('xxx', callback) Does not allowed. To wait until callback, work around is use map.on("styledata" map.on("load" not work, if you change map.setStyle(). you will get error: Uncaught Error: Style is not done loading

hoogw
  • 4,982
  • 1
  • 37
  • 33
1

The current style event structure is broken (at least as of Mapbox GL v1.3.0). If you check map.isStyleLoaded() in the styledata event handler, it always resolves to false:

map.on('styledata', function (e) {
  if (map.isStyleLoaded()){
    // This never happens...
  }
}

My solution is to create a new event called "style_finally_loaded" that gets fired only once, and only when the style has actually loaded:

var checking_style_status = false;
map.on('styledata', function (e) {
  if (checking_style_status){
    // If already checking style status, bail out
    // (important because styledata event may fire multiple times)
    return;
  } else {
    checking_style_status = true;
    check_style_status();
  }
});
function check_style_status() {
  if (map.isStyleLoaded()) {
    checking_style_status = false;
    map._container.trigger('map_style_finally_loaded');
  } else {
    // If not yet loaded, repeat check after delay:
    setTimeout(function() {check_style_status();}, 200);
    return;
  }
}
Yarin
  • 173,523
  • 149
  • 402
  • 512
  • 1
    For some styles, it works, `isStyleLoaded` returns true on final `styledata` event, for some styles doesn't (1.12.0). – kolen Jan 19 '21 at 21:56
  • Relevant issue: https://github.com/mapbox/mapbox-gl-js/issues/8691 – kolen Jan 19 '21 at 22:04
1

I had the same problem, when adding real estate markers to the map. For the first time addding the markers I wait till the map turns idle. After it was added once I save this in realEstateWasInitialLoaded and just add it afterwards without any waiting. But make sure to reset realEstateWasInitialLoaded to false when changing the base map or something similar.

checkIfRealEstateLayerCanBeAddedAndAdd() {
      /* The map must exist and real estates must be ready */
      if (this.map && this.realEstates) {
        this.map.once('idle', () => {
          if (!this.realEstateWasInitialLoaded) {
            this.addRealEstatesLayer();
            this.realEstateWasInitialLoaded = true
          }
        })
        if(this.realEstateWasInitialLoaded) {
          this.addRealEstatesLayer();
        }

      }
    },
Beeblebrox
  • 36
  • 4
0

I ended up with : map.once("idle", ()=>{ ... some function here}); In case you have a bunch of stuff you want to do , i would do something like this => add them to an array which looks like [{func: function, param: params}], then you have another function which does this:

executeActions(actions) {
  actions.forEach((action) => {
  action.func(action.params);
});

And at the end you have

this.map.once("idle", () => {
    this.executeActions(actionsArray);
 });
vvn050
  • 194
  • 2
  • 4
-1

I have created simple solution. Give 1 second for mapbox to load the style after you set the style and you can draw the layer

map.setStyle(styleUrl);     
setTimeout(function(){
   reDrawMapSourceAndLayer(); /// your function layer
}, 1000);

when you use map.on('styledataloading') it will trigger couple of time when you changes the style

map.on('styledataloading', () => {
  const waiting = () => {
    if (!myMap.isStyleLoaded()) {
      setTimeout(waiting, 200);
    } else {
      loadMyLayers();
    }
  };
  waiting();
});