Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensuring all tiles are loaded in Open Layers 3 XYZ source

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.

  1. Why are the function calls available in ol-debug.js and not ol.js when they hang off of the prototype of tilegrid object?
  2. Any other suggestions how to tell all tiles are completely loaded in the map?
like image 403
Scott Avatar asked Oct 11 '15 03:10

Scott


2 Answers

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.

like image 169
Alvin Lindstam Avatar answered Nov 01 '22 18:11

Alvin Lindstam


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));
}
like image 23
Dan Avatar answered Nov 01 '22 18:11

Dan