We have some layers that make use of a ol.source.XYZ
source. For the loading strategy we use ol.loadingstrategy.tile(new ol.tilegrid.createXYZ({}))
. We need to ensure that all tiles have been completely loaded in the map view before proceeding with other operations.
We have come across multiple articles regarding this and haven't found a 100% solution yet that will give us the solution we need. The logic returns true even when it's not the case. We've tried to make use of the tileloadstart, tileloadend, tileloaderror events as shown on the example page but this doesn't seem to always return the expected result.
The GIS Stack Exchange article here seemed promising because we could use the code listed below in conjunction with tileloadstart/tileloadend events but there are a number of the function calls that are only available in ol-debug.js and not the ol.js source code. Because of this the code pasted below does not work with ol.js. This code is just a copy from the referenced GIS Stack Exchange article.
function calculateNumberOfTiles(tileSource) {
var tg = (tileSource.getTileGrid()) ? tileSource.getTileGrid(): ol.tilegrid.getForProjection(map.getView().getProjection()),
z = tg.getZForResolution(map.getView().getResolution()),
tileRange = tg.getTileRangeForExtentAndZ(map.getView().calculateExtent(map.getSize()), z),
xTiles = tileRange['maxX'] - tileRange['minX'] + 1,
yTiles = tileRange['maxY'] - tileRange['minY'] + 1;
return xTiles * yTiles;
}
I have two questions, can anyone please provide any additional thoughts in what we may be missing? Thanks for your help.
Loading events
You are correct in assuming that each tileloadstart
event on the source should be followed by a tileloadend
or tileloaderror
for the corresponding tile. That can be used, as in the linked official example, to keep track of the number of loading tiles.
When the sum of emitted tileloadend
and tileloaderror
events equal the number of tileloadstart
events, no loading is in progress. If this is not the case, you should try to make a reproducible example, as it would probably be a bug in the library.
It is however important to understand what these events mean. The tileloadend
event does not mean that the tile is visible on the map, it means that the tile has finished loading and is usable for rendering. The actual rendering of the tile will be done after the event handler is invoked. So any tile loading logic requiring information about when all tiles are loaded and rendered (such when taking screenshots/creating prints) will have to wait until the next postrender
event.
You mention 5-10 seconds between a tileloadend
and the tile actually appearing on the map, which is too long for it to be rendering related (unless you do some really freaky rendering callbacks).
ol-debug.js vs ol.js
Like many JS libraries, OpenLayers code is optimized and minimized in the build process to create smaller and more efficient builds. Any type or function that is not part of the API will be minified or removed. Only the methods available in ol.js, and documented on openlayers.org, should be used as any minified methods may change each build.
ol-debug.js is a non-optimized version of the library, intended for use when debugging or exploring.
This is my approach. It uses an undocumented API, but it works in non-debug openlayers 4.2.0.
//"Dirty" tiles can be in one of two states: Either they are being downloaded,
//or the map is holding off downloading their replacement, and they are "wanted."
//We can tell when the map is ready when there are no tiles in either of these
//states, and rendering is done.
var numInFlightTiles = 0;
map.getLayers().forEach(function (layer) {
var source = layer.getSource();
if (source instanceof ol.source.TileImage) {
source.on('tileloadstart', function () {++numInFlightTiles})
source.on('tileloadend', function () {--numInFlightTiles})
}
})
map.on('postrender', function (evt) {
if (!evt.frameState)
return;
var numHeldTiles = 0;
var wanted = evt.frameState.wantedTiles;
for (var layer in wanted)
if (wanted.hasOwnProperty(layer))
numHeldTiles += Object.keys(wanted[layer]).length;
var ready = numInFlightTiles === 0 && numHeldTiles === 0;
if (map.get('ready') !== ready)
map.set('ready', ready);
});
map.set('ready', false);
function whenMapIsReady(callback) {
if (map.get('ready'))
callback();
else
map.once('change:ready', whenMapIsReady.bind(null, callback));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With