Merge pull request #13863 from ahocevar/tile-error-handling
Fix reloading tiles in case of an error with tile.load()
This commit is contained in:
@@ -112,7 +112,40 @@ class ImageTile extends Tile {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load not yet loaded URI.
|
||||
* Load the image or retry if loading previously failed.
|
||||
* Loading is taken care of by the tile queue, and calling this method is
|
||||
* only needed for preloading or for reloading in case of an error.
|
||||
*
|
||||
* To retry loading tiles on failed requests, use a custom `tileLoadFunction`
|
||||
* that checks for error status codes and reloads only when the status code is
|
||||
* 408, 429, 500, 502, 503 and 504, and only when not too many retries have been
|
||||
* made already:
|
||||
*
|
||||
* ```js
|
||||
* const retryCodes = [408, 429, 500, 502, 503, 504];
|
||||
* const retries = {};
|
||||
* source.setTileLoadFunction((tile, src) => {
|
||||
* const image = tile.getImage();
|
||||
* fetch(src)
|
||||
* .then((response) => {
|
||||
* if (retryCodes.includes(response.status)) {
|
||||
* retries[src] = (retries[src] || 0) + 1;
|
||||
* if (retries[src] <= 3) {
|
||||
* setTimeout(() => tile.load(), retries[src] * 1000);
|
||||
* }
|
||||
* return Promise.reject();
|
||||
* }
|
||||
* return response.blob();
|
||||
* })
|
||||
* .then((blob) => {
|
||||
* const imageUrl = URL.createObjectURL(blob);
|
||||
* image.src = imageUrl;
|
||||
* setTimeout(() => URL.revokeObjectURL(imageUrl), 5000);
|
||||
* })
|
||||
* .catch(() => tile.setState(3)); // error
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
load() {
|
||||
|
||||
@@ -142,7 +142,12 @@ class Tile extends EventTarget {
|
||||
/**
|
||||
* Called by the tile cache when the tile is removed from the cache due to expiry
|
||||
*/
|
||||
release() {}
|
||||
release() {
|
||||
if (this.state === TileState.ERROR) {
|
||||
// to remove the `change` listener on this tile in `ol/TileQueue#handleTileChange`
|
||||
this.setState(TileState.EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} Key.
|
||||
|
||||
@@ -5,6 +5,13 @@ import LRUCache from './structs/LRUCache.js';
|
||||
import {fromKey, getKey} from './tilecoord.js';
|
||||
|
||||
class TileCache extends LRUCache {
|
||||
clear() {
|
||||
while (this.getCount() > 0) {
|
||||
this.pop().release();
|
||||
}
|
||||
super.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object<string, boolean>} usedTiles Used tiles.
|
||||
*/
|
||||
|
||||
@@ -86,7 +86,9 @@ class TileQueue extends PriorityQueue {
|
||||
state === TileState.ERROR ||
|
||||
state === TileState.EMPTY
|
||||
) {
|
||||
tile.removeEventListener(EventType.CHANGE, this.boundHandleTileChange_);
|
||||
if (state !== TileState.ERROR) {
|
||||
tile.removeEventListener(EventType.CHANGE, this.boundHandleTileChange_);
|
||||
}
|
||||
const tileKey = tile.getKey();
|
||||
if (tileKey in this.tilesLoadingKeys_) {
|
||||
delete this.tilesLoadingKeys_[tileKey];
|
||||
|
||||
@@ -129,10 +129,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
const tileSource = tileLayer.getSource();
|
||||
let tile = tileSource.getTile(z, x, y, pixelRatio, projection);
|
||||
if (tile.getState() == TileState.ERROR) {
|
||||
if (!tileLayer.getUseInterimTilesOnError()) {
|
||||
// When useInterimTilesOnError is false, we consider the error tile as loaded.
|
||||
tile.setState(TileState.LOADED);
|
||||
} else if (tileLayer.getPreload() > 0) {
|
||||
if (tileLayer.getUseInterimTilesOnError() && tileLayer.getPreload() > 0) {
|
||||
// Preloaded tiles for lower resolutions might have finished loading.
|
||||
this.newTiles_ = true;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ export default {
|
||||
TILELOADEND: 'tileloadend',
|
||||
|
||||
/**
|
||||
* Triggered if tile loading results in an error.
|
||||
* Triggered if tile loading results in an error. Note that this is not the
|
||||
* right place to re-fetch tiles. See {@link module:ol/ImageTile~ImageTile#load}
|
||||
* for details.
|
||||
* @event module:ol/source/Tile.TileSourceEvent#tileloaderror
|
||||
* @api
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ImageTile from '../../../../src/ol/ImageTile.js';
|
||||
import Tile from '../../../../src/ol/Tile.js';
|
||||
import TileCache from '../../../../src/ol/TileCache.js';
|
||||
import TileQueue from '../../../../src/ol/TileQueue.js';
|
||||
import TileState from '../../../../src/ol/TileState.js';
|
||||
import {DROP} from '../../../../src/ol/structs/PriorityQueue.js';
|
||||
@@ -149,7 +150,7 @@ describe('ol.TileQueue', function () {
|
||||
expect(tile.hasListener('change')).to.be(false);
|
||||
});
|
||||
|
||||
it('error tiles', function () {
|
||||
it('error tiles - with retry', function (done) {
|
||||
const tq = new TileQueue(noop, noop);
|
||||
const tile = createImageTile(noop);
|
||||
|
||||
@@ -160,7 +161,47 @@ describe('ol.TileQueue', function () {
|
||||
|
||||
tile.setState(TileState.ERROR);
|
||||
expect(tq.getTilesLoading()).to.eql(0);
|
||||
expect(tile.hasListener('change')).to.be(false);
|
||||
expect(tile.hasListener('change')).to.be(true);
|
||||
|
||||
tile.setState(TileState.IDLE);
|
||||
setTimeout(() => tile.setState(TileState.LOADING), 100);
|
||||
setTimeout(() => tile.setState(TileState.LOADED), 200);
|
||||
setTimeout(() => {
|
||||
try {
|
||||
expect(tq.getTilesLoading()).to.eql(0);
|
||||
expect(tile.hasListener('change')).to.be(false);
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
it('error tiles - without retry', function (done) {
|
||||
const tq = new TileQueue(noop, noop);
|
||||
const tile = createImageTile(noop);
|
||||
const tileCache = new TileCache();
|
||||
tileCache.set(tile.getTileCoord().toString(), tile);
|
||||
|
||||
tq.enqueue([tile]);
|
||||
tq.loadMoreTiles(Infinity, Infinity);
|
||||
expect(tq.getTilesLoading()).to.eql(1);
|
||||
expect(tile.getState()).to.eql(1); // LOADING
|
||||
|
||||
tile.setState(TileState.ERROR);
|
||||
expect(tq.getTilesLoading()).to.eql(0);
|
||||
expect(tile.hasListener('change')).to.be(true);
|
||||
|
||||
setTimeout(() => tileCache.clear(), 100);
|
||||
setTimeout(() => {
|
||||
try {
|
||||
expect(tq.getTilesLoading()).to.eql(0);
|
||||
expect(tile.hasListener('change')).to.be(false);
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user