Mapbox GL JS: Style is not done loading
Asked Answered
H

10

11

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?

Hypocorism answered 6/6, 2017 at 15:49 Comment(1)
Possible duplicate of Style is not done loading: Mapbox GL JSImportune
P
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;
Postulate answered 26/1, 2019 at 6:8 Comment(2)
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.Stonehenge
@Stonehenge is correct, this event gets fired multiple times. Moreover, it never actually gets fired after the style is finished loading. See my answer for better solution.Ampulla
T
7

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.

Trinitytrinket answered 3/7, 2018 at 0:43 Comment(2)
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: github.com/mapbox/mapbox-gl-js/issues/7579Watanabe
This works well when using styledata instead of style.load. Thanks!Banded
A
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...

}
Appendicular answered 15/11, 2017 at 17:9 Comment(3)
yes I done the same thing, waiting that Mapbox team fix that bug.Hypocorism
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.)Homs
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()Audsley
B
1

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

map.on('style.load', function() {
    addLayer();
});
Birdbath answered 18/11, 2017 at 4:5 Comment(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: github.com/mapbox/mapbox-gl-js/issues/7579Watanabe
J
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

Johnsonjohnsonese answered 9/8, 2019 at 18:52 Comment(0)
A
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;
  }
}
Ampulla answered 4/9, 2019 at 16:27 Comment(2)
For some styles, it works, isStyleLoaded returns true on final styledata event, for some styles doesn't (1.12.0).Pants
Relevant issue: github.com/mapbox/mapbox-gl-js/issues/8691Pants
Z
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();
        }

      }
    },
Zither answered 25/6, 2021 at 14:14 Comment(0)
S
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);
 });
Scarlettscarp answered 30/1, 2021 at 20:20 Comment(0)
I
0

You should wait for the Mapbox map style to finish loading before adding markers or layers to the map. You can do this by listening for the 'style.load' event on your map instance. Here's an example of how to do it:

// Inside the initializeMap function
map = new mappls.Map('map', {
  center: { lat, lng },
  zoom,
});

map.on('style.load', function () {
  // Map style has finished loading, you can now add markers or layers
  loadMyLayers();
});

here i took example of map my India

Ideo answered 25/9, 2023 at 19:6 Comment(0)
S
-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();
});
Subotica answered 27/1, 2021 at 4:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.