Ensuring all tiles are loaded in Open Layers 3 XYZ source
Asked Answered
W

3

9

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?
Wellesley answered 11/10, 2015 at 3:25 Comment(9)
Could you describe in what way the results differ from what you expect? Are you trying to deduce if all tiles in a given tilegrid is loaded, or if any loading is still in progress? Also, what version are you using?Silsby
Trying to determine if there is anything left to be loaded for the current extent and zoom of the map. We need to make sure there are no pending tiles to be loaded. Based on another example we found in a SO question we thought we could keep a counter with the tileloadstart event and increment the counter each time that fired and decrement the counter each time tileloadend or tileloaderror fired. When the counter gets to 0 assume done. In some cases our counter never hit 0 and thats where it differ s from expectation. It seemd to do some things in bulk.Wellesley
We just recently updated to v3.9 of OL just before 3.10 was availableWellesley
So your issue is that some tiles seem to start loading but not doesn't appear to finish (according to the events of the source)? Have you identified what happens to those (unfinished) tiles? Are they still loading or are they finished?Silsby
Somewhat, the tileloadend event does fire like its indicating its finished but in the map the tile is not yet there. Our counter gets down to 0 becuase the event fired even though the tile hasn't completely loaded. Eventually the tile shows up in the map but the tileloadend event seem to fire too early, possibly?Wellesley
By eventually, are we talking milliseconds or seconds later (while not pausing with any debugger break points)?Silsby
Seconds, like 5 to 10, maybe more. This happens even for a production build that is minified and has no debugger statements. Is the approach of having a counter to keep track of tileloadstart and tileloadend a right assumption? As mentioned in the question for this post we also thought to try calculating number of tiles but the functions aren't available except in ol-debug.js.Wellesley
I posted a general answer. Any more detailed answers would probably require more detailed information about your application. Preferably a running example.Silsby
getTileRangeForExtentAndZ is only available in ol.debug You can still calc the number of tiles using: var extent = map.getView().calculateExtent(map.getSize()); var tileExtentBottomLeft = tg.getTileCoordForCoordAndZ([extent[0], extent[1]], z); var tileExtentTopRight = tg.getTileCoordForCoordAndZ([extent[2], extent[3]], z); var xTiles = tileExtentTopRight[1] - tileExtentBottomLeft[1] + 1; var yTiles = tileExtentTopRight[2] - tileExtentBottomLeft[2] + 1; return xTiles * yTiles;Egin
S
9

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.

Silsby answered 11/10, 2015 at 19:52 Comment(1)
Thanks you've provided some great feedback. I'll mark this as the answer as it answers the question I posted. I'll see if I can put together a reproducible jsfiddle or plunk and get back with you. Thanks.Wellesley
C
8

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));
}
Cancroid answered 12/7, 2017 at 9:56 Comment(1)
Works for openlayers 5.2 👍Oriane
D
0

This answer is outdated and does not work from openlayers 6 or higher.

As documented here: https://github.com/openlayers/openlayers/releases/tag/v6.0.0

A solution to this is to put the postrender on a layer like this

const mapLayer = map.getLayers().getArray()[0];

mapLayer.on('postrender', (evt) => {});

This triggers the postrender after the first layer is rendered

Duaneduarchy answered 30/11, 2020 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.