There are events for when individual tiles load, but it's difficult to know when all the tiles for a source have finished loaded. Essentially, I'd like an API to know if tiles requests are in flight, or if the all the data I would expect to view for a given view is already loaded. Firing an event when the state transitions would also be helpful.
Knowing when a source is fully loaded is useful for smoothly transitioning between different overlay sources without a flash as tiles load.
As a quick sketch, I'd imaging something like:
map.on('source.loading', function (e) {
e.status; // one of 'fetching', 'idle', 'errored'
});
@jfirebaugh I think Map.load doesn't account for view changes requiring new tiles to be downloaded?
@scothis A related issue I encountered today (copying from a #sneak-peek slack conversation):
Iām trying to use
featuresInon load, and i think iām running into a race. I am waiting until i seesource.loadevents for the sources that i need features from, but even thoughsource.loadhas fired, sometimes some of the individual ā_tiles_ā arenāt loaded. I just tried a workaround where, if!source.loaded(), wait fortile.loadand check again, but the problem is that if thereās a missing tile,source.loaded()never becomes true. Iām thinking about hacking this in by setting anerroredflag here: https://github.com/mapbox/mapbox-gl-js/blob/master/js/source/vector_tile_source.js#L77
and then havingtile_pyramid.loadedcheck forloaded || erroredinstead of justloaded.
Just tried that change here: https://github.com/mapbox/mapbox-gl-js/compare/master...anandthakker:fix-source-loaded. Seems to resolve my particular issue; lemme know if you want a pr, though maybe you'd rather solve this more systematically.
^ Hmm, okay so my hack doesn't really resolve the problem after all: even after correcting pyramid.loaded() and then waiting for it, I end up with missing data in my featuresIn request. My guess is that loaded() comes back true when all the tiles that have been requested have come back, but there more yet that need to be requested for the current view.
I guess this is my :+1: for something like what @scothis proposed.
@jfirebaugh looking for something like the load event that stays up to date, and doesn't only fire once.
Got it, thanks. I think maybe we should make load fire whenever the view quiesces. This would be a breaking change and we'd need to switch most examples to map.once('load', ...).
Just wanted to +1 that I'm looking for something similar for when a geojson-source data url has loaded. Which I suppose would inherently trigger tile-loaded events. Just a slightly different case than a regular tile source.
I'm in favor of building this and calling the event something other than load.
Some strawman proposals:
quiescecoalescestabilizecomplete?
What about updating the function signatures of map#addSource to take accept an optional callback as the last argument?
map.addSource(id, source, function(){
// source loaded, do stuff...
});
Leaflet uses load for this, although I agree that it's a bit ambiguous. complete sounds fine.
+1. Related to https://github.com/mapbox/mapbox-gl-js/issues/1715#issuecomment-162101219 I would expect source.load to fire twice in this scenario and sometimes there's a long lag between the geojson layer finally added to the map:

I have successfully been able to detect when my features have loaded by watching the render event that the map fires when a single style is rendered and each time checking map.loaded(). I can then know it is ok to call featuresIn() and I will get my features.
map.on("render", function() {
if(map.loaded()) {
map.featuresIn(...);
}
});
Ideally, an event at the exact time all layers on the map are ready is better, rather than checking all the render events but this is getting me by. Looking forward to the update.
+1 @timjcook
+1 @timjcook that worked for use case as well, adding a loading bar while source.setData() is executing.
I'm thinking about resolving this issue with
Mapādata: a resource (tile, sprite, glyph) has been loaded OR changed with GeoJSONSource#setDataMapādataend: no future data events will be fired (unless map is interacted with)This parallels the structure of
Mapāmove: the map's viewport has been movedMapāmoveend: no future move events will be fired (unless map is interacted with)š @scothis?
@lucaswoj works for me
š
I like the symmetry there.
data and dataend (ref https://github.com/mapbox/mapbox-gl-js/issues/1715#issuecomment-209069733)tile.load in favor of datatile.stats in favor of datatile.add in favor of datatile.remove in favor of datasource.change in favor of dataref https://github.com/mapbox/mapbox-gl-js/issues/2232~~
š https://github.com/mapbox/mapbox-gl-js/issues/1715#issuecomment-249293404
@lucaswoj any movement on these events?
No movement on this PR. In almost every case, render is the best event to use. Closing this PR as stale.
My mistake -- this is the issue not the PR.
Is there a target release for this?
Below are explanations of the existing fire-ers and listeners of the events that are going to be refactored:
layer.addtodo
layer.removetodo
source.addtodo
source.changeGeoJSON source after a setData operation has completed http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/geojson_source.js#L122ImageSource after setCoordinates operation has completed http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/image_source.js#L109SourceCache to trigger a Map#reload and Map#update http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/source_cache.js#L53VideoSource after setCoordinates operation has completed http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/video_source.js#L138AttributionControl to trigger an update of the attributon string http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/ui/control/attribution.js#L58Map to trigger a Map#update http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/ui/map.js#L215source.loadGeoJSONSource after the initial data has been sent to the Worker http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/geojson_source.js#L91-L92ImageSource after the image data has been loaded http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/image_source.js#L56RasterTileSource after the TileJSON has been loaded http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/raster_tile_source.js#L20SourceCache to initialize some properties that might not have been available before this event was fired http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/source_cache.js#L33-L34VectorTileSource after the TileJSON has loaded http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/vector_tile_source.js#L26VideoSource after the video has loaded http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/video_source.js#L76Style to trigger source-layer validation http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/style/style.js#L79AttributionControl to trigger attribution string update http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/ui/control/attribution.js#L57Map to trigger a Map#update operation http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/ui/map.js#L214source.removetodo
style.changeImageSprite after both the sprite JSON and sprite image have loaded http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/style/image_sprite.js#L23 http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/style/image_sprite.js#L44ImageSprite to proxy a resized version of ImageSprite š¬ http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/style/image_sprite.js#L61Style after a Map#update operation has applied changes http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/style/style.js#L303-L304Style to trigger an update of SpriteAtlas after ImageSprite has new data http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/style/style.js#L721Map to trigger a Map#update operation http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/ui/map.js#L200-L201style.loadStyle to indicate that the style JSON has loaded http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/style/style.js#L70Map to trigger a jump to the style's specified coordinates and a Map#update operation http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/ui/map.js#L193tile.addSourceCache for each additional tile needed after the viewport has changed http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/source_cache.js#L410tile.loadSourceCache after a tile has been populated with data http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/source_cache.js#L169Tile after a tile has completed a Tile#redoPlacement operation http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/tile.js#L147Map to trigger a Map#update operation http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/ui/map.js#L216tile.removeSourceCache for each tile as it is no longer needed http://github.com/mapbox/mapbox-gl-js/blob/24f2135f058372bd7906e3c09148c92c8ffebef0/js/source/source_cache.js#L410Strawman proposal:
dataloading will be fired when any resource starts loading data will be be fired when any resource loads or changes interface DataEvent {
type: 'dataloading' | 'data';
target: Tile | Source | Style | SpriteImage;
dataType: (
'geoJSON' |
'tileJSON' |
'style' |
'sprite' |
'image' |
'video' |
'tile'
);
}
datastart will be fired when Map#loaded() transitions from true to false dataend will be fired after a data event while Map#loaded() is true (there will be no corresponding datastart event if the change doesn't require loading like GeoJSONSource#setData)š Very thorough.
'sourceJSON' |
'tileJSON' |
Is there a distinction between these two?
@jfirebaugh No, that was my mistake. Updated the spec above. Thank you š
EDIT: I meant to write styleJSON there
@lucaswoj nice! Couple questions:
dataend if a resource load yields an error.datatype= image|video|rasterTile|vectorTile?Are errors meant to be abstracted away in these semantics? If so, how will that work? I would assume we'd still want to get a
dataendif a resource load yields an error.
We will continue to fire error events to indicate that an error occurred.
Good catch on dataend. I updated the spec above to indicate that a dataend event may be fired after an error also.
Would it make sense to attach the source id when datatype= image|video|rasterTile|vectorTile?
A source property will be attached as the event propagates through Style https://github.com/mapbox/mapbox-gl-js/blob/2693ad5842724cda96c0e8c396aa6607bca5d0f4/js/style/style.js#L338
source.change with datasource.load with datastyle.change with data style.load with datatile.add with dataloadingtile.load with datatile.removewith datasource.add with datasource.remove with datalayer.add with datalayer.remove with datadata and dataloading eventsdataloading event for source datadataloading event for style datadataloading event for tile datasource.load with sourceload and document itstyle.load with styleload and document itsourcedata & sourcedataloading eventsstyledata & styledataloading eventstiledata & tiledataloading eventsdata & dataloading events aggregates of āļø and fired by Mapdatastart eventdataend event Any news on this: implement dataend event ?
Is the best method still to use on render as @timjcook mentions?
That depends on your use case @stackTom. If you want to listen for an event that fires when the map changes, Map#render is a good fit.
I have a dense mbtiles loaded on my map, and on zoom end, the source re-renders such that more points are rendered than at higher zoom levels. I need to execute a function when all of these points have been fully rendered at the lower zoom level.
+1, I would like to implement a loading spinner on my map when it is fetching tiles or waiting for geojson ajax calls to return. I would love it if there was a simple event that would fire when all layers are fully rendered.
is it possible to load different data layers dependent and a time of day month. ex. broadcast listener of system time
@LDF913 Due to the large volume of issues we get and the relatively small size of our team, we are unable to provide support here on GitHub. I recommend asking this question on Stack Overflow.
A reliable dataend event would be great, using render and map.loaded() occasionally doesn't work, on the final render event fire the map is still not loaded. Can't reliably reproduce though.
+1, I would like to implement a loading spinner on my map when it is fetching tiles or waiting for geojson ajax calls to return. I would love it if there was a simple event that would fire when all layers are fully rendered.
I was able to fairly reliably check if ajax-type data was loaded via:
map.on('data', function (data) {
if (data.dataType === 'source' && data.isSourceLoaded) {
console.log('data loaded', data)
// stop listening to map.on('data'), if applicable
}
})
The map.on('data') event is pretty noisy though.
@joehand: thanks for map.on('data') , it sounded great at first :) Sadly, it is so noisy that it misses many events, and as such my spinning wheel (same use case as @chriswhong ) often stays stuck, which makes for very poor user experience. Can't wait for this feature!
@jucor what I've done is to just start a timer on sourcedata event that checks map.loaded() every half a second and then removes the loading wheel. Not had a problem with this approach
@jdeboer-geoplan Nice workaround, thanks!
Following this thread as my app also needs this.
It's amazing that this issue is 2 years old. This feature is table stakes.
is this work in progress ?
Is this related to https://github.com/mapbox/mapbox-gl-js/issues/3964
I'd like to see it as on complete as in, no more pending source requests
i don't understand how for example get the event tile.remove. Was it removed, is there a documentation about all events?
i use this.map.on('data', event...
with event.tile.uses i check if it's loaded or unloaded. But there are much events missing of unloaded tiles. I compare to map.style.sourceCaches.xxxxx._tiles. This looks correct but i dont get for all unloaded tiles a event.
@jfirebaugh I think Map.load doesn't account for view changes requiring new tiles to be downloaded?
@scothis A related issue I encountered today (copying from a #sneak-peek slack conversation):
Iām trying to use
featuresInon load, and i think iām running into a race. I am waiting until i seesource.loadevents for the sources that i need features from, but even thoughsource.loadhas fired, sometimes some of the individual _tiles_ arenāt loaded. I just tried a workaround where, if!source.loaded(), wait fortile.loadand check again, but the problem is that if thereās a missing tile,source.loaded()never becomes true. Iām thinking about hacking this in by setting anerroredflag here: https://github.com/mapbox/mapbox-gl-js/blob/master/js/source/vector_tile_source.js#L77
and then havingtile_pyramid.loadedcheck forloaded || erroredinstead of justloaded.Just tried that change here: master...anandthakker:fix-source-loaded. Seems to resolve my particular issue; lemme know if you want a pr, though maybe you'd rather solve this more systematically.
Hi @anandthakker,
I am facing the same issue of "sourcedata" callback is not calling if one of the tiles in viewport fails to load, even the map.loaded() is not true.
I have high dependency on this, I set the state of feature on this basis(querySourceFeatures). I am stuck in this, if there is an alternative to this. Please suggest.
Like if we can abort/unload/remove that particular tile and make e.isSourceLoaded to true in callback
if (e.isSourceLoaded && e.sourceId === "src_chairs") {
//code to execute
}
Also, attaching screenshot

Please give me some suggestions on this.
I appreciate jdeboer-geoplan's suggestion; he or she could have been a little more explicit. I think a recursive timeout is the answer. This seems to work (most of the time) for me. I'm not sure if areTilesLoaded() helps or not. Also, isStyleLoaded could be added.
function checkOutThisLoad(){
if (map.loaded() == true && map.areTilesLoaded() == true) {
console.log("load is complete");
} else {
console.log("load is incomplete");
setTimeout(function() {
checkOutThisLoad();
},1000);
}
}
Edit : I went for the idle event which does exactly what I want
My use case : I want to use queryRenderedFeatures after adding a layer.
It's quite similar to the airport example in the documentation. This issue was avoided by displaying a message Drag the map to populate results but I can't affort to do that. I need the list to be updated when the layer loads
If I understand it correctly, dataend should be the event I need, right ?
Seeing how often the render event is fired I went for a hack by removing the listener after some time after adding the layer
// when a new layer is added
layers$.subscribe((layers) => {
this.map.on("render", checkRenderedFeatures);
setTimeout(() => this.map.off("render", checkRenderedFeatures), 2000);
});
This way checkRenderedFeatures is called ~30-50 times before the listener is removed
I don't like it one bit but hopefuly dataend will solve this issue for me. I'm fully aware that my "temporary hack" comment will still be in the codebase in 10 years :D
Yes, idle provided a solution for me too. I don't know how many years the idle event has been available but it should have been mentioned a long time ago for this issue. Like anyone else I guess I could have said something earlier.
Most helpful comment
It's amazing that this issue is 2 years old. This feature is table stakes.