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
|
* @api
|
||||||
*/
|
*/
|
||||||
load() {
|
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
|
* 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.
|
* @return {string} Key.
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ import LRUCache from './structs/LRUCache.js';
|
|||||||
import {fromKey, getKey} from './tilecoord.js';
|
import {fromKey, getKey} from './tilecoord.js';
|
||||||
|
|
||||||
class TileCache extends LRUCache {
|
class TileCache extends LRUCache {
|
||||||
|
clear() {
|
||||||
|
while (this.getCount() > 0) {
|
||||||
|
this.pop().release();
|
||||||
|
}
|
||||||
|
super.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!Object<string, boolean>} usedTiles Used tiles.
|
* @param {!Object<string, boolean>} usedTiles Used tiles.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ class TileQueue extends PriorityQueue {
|
|||||||
state === TileState.ERROR ||
|
state === TileState.ERROR ||
|
||||||
state === TileState.EMPTY
|
state === TileState.EMPTY
|
||||||
) {
|
) {
|
||||||
|
if (state !== TileState.ERROR) {
|
||||||
tile.removeEventListener(EventType.CHANGE, this.boundHandleTileChange_);
|
tile.removeEventListener(EventType.CHANGE, this.boundHandleTileChange_);
|
||||||
|
}
|
||||||
const tileKey = tile.getKey();
|
const tileKey = tile.getKey();
|
||||||
if (tileKey in this.tilesLoadingKeys_) {
|
if (tileKey in this.tilesLoadingKeys_) {
|
||||||
delete this.tilesLoadingKeys_[tileKey];
|
delete this.tilesLoadingKeys_[tileKey];
|
||||||
|
|||||||
@@ -129,10 +129,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const tileSource = tileLayer.getSource();
|
const tileSource = tileLayer.getSource();
|
||||||
let tile = tileSource.getTile(z, x, y, pixelRatio, projection);
|
let tile = tileSource.getTile(z, x, y, pixelRatio, projection);
|
||||||
if (tile.getState() == TileState.ERROR) {
|
if (tile.getState() == TileState.ERROR) {
|
||||||
if (!tileLayer.getUseInterimTilesOnError()) {
|
if (tileLayer.getUseInterimTilesOnError() && tileLayer.getPreload() > 0) {
|
||||||
// When useInterimTilesOnError is false, we consider the error tile as loaded.
|
|
||||||
tile.setState(TileState.LOADED);
|
|
||||||
} else if (tileLayer.getPreload() > 0) {
|
|
||||||
// Preloaded tiles for lower resolutions might have finished loading.
|
// Preloaded tiles for lower resolutions might have finished loading.
|
||||||
this.newTiles_ = true;
|
this.newTiles_ = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export default {
|
|||||||
TILELOADEND: 'tileloadend',
|
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
|
* @event module:ol/source/Tile.TileSourceEvent#tileloaderror
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import ImageTile from '../../../../src/ol/ImageTile.js';
|
import ImageTile from '../../../../src/ol/ImageTile.js';
|
||||||
import Tile from '../../../../src/ol/Tile.js';
|
import Tile from '../../../../src/ol/Tile.js';
|
||||||
|
import TileCache from '../../../../src/ol/TileCache.js';
|
||||||
import TileQueue from '../../../../src/ol/TileQueue.js';
|
import TileQueue from '../../../../src/ol/TileQueue.js';
|
||||||
import TileState from '../../../../src/ol/TileState.js';
|
import TileState from '../../../../src/ol/TileState.js';
|
||||||
import {DROP} from '../../../../src/ol/structs/PriorityQueue.js';
|
import {DROP} from '../../../../src/ol/structs/PriorityQueue.js';
|
||||||
@@ -149,7 +150,7 @@ describe('ol.TileQueue', function () {
|
|||||||
expect(tile.hasListener('change')).to.be(false);
|
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 tq = new TileQueue(noop, noop);
|
||||||
const tile = createImageTile(noop);
|
const tile = createImageTile(noop);
|
||||||
|
|
||||||
@@ -159,8 +160,48 @@ describe('ol.TileQueue', function () {
|
|||||||
expect(tile.getState()).to.eql(1); // LOADING
|
expect(tile.getState()).to.eql(1); // LOADING
|
||||||
|
|
||||||
tile.setState(TileState.ERROR);
|
tile.setState(TileState.ERROR);
|
||||||
|
expect(tq.getTilesLoading()).to.eql(0);
|
||||||
|
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(tq.getTilesLoading()).to.eql(0);
|
||||||
expect(tile.hasListener('change')).to.be(false);
|
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