Merge pull request #5795 from thomasmoelhave/wmts-tile-transitions

Render older loaded tiles while waiting for new tiles
This commit is contained in:
Andreas Hocevar
2016-09-02 16:53:55 +02:00
committed by GitHub
5 changed files with 146 additions and 24 deletions

View File

@@ -139,8 +139,8 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(
for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (y = tileRange.minY; y <= tileRange.maxY; ++y) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
tile = tileSource.getTile(z, x, y, pixelRatio, projection); tile = tileSource.getTile(z, x, y, pixelRatio, projection);
if (!drawableTile(tile) && tile.interimTile) { if (!drawableTile(tile)) {
tile = tile.interimTile; tile = tile.getInterimTile();
} }
if (drawableTile(tile)) { if (drawableTile(tile)) {
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;

View File

@@ -252,8 +252,8 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerS
drawable = tileState == ol.Tile.State.LOADED || drawable = tileState == ol.Tile.State.LOADED ||
tileState == ol.Tile.State.EMPTY || tileState == ol.Tile.State.EMPTY ||
tileState == ol.Tile.State.ERROR && !useInterimTilesOnError; tileState == ol.Tile.State.ERROR && !useInterimTilesOnError;
if (!drawable && tile.interimTile) { if (!drawable) {
tile = tile.interimTile; tile = tile.getInterimTile();
} }
tileState = tile.getState(); tileState = tile.getState();
if (tileState == ol.Tile.State.LOADED) { if (tileState == ol.Tile.State.LOADED) {

View File

@@ -296,32 +296,22 @@ ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, pr
tile = this.createTile_(z, x, y, pixelRatio, projection, key); tile = this.createTile_(z, x, y, pixelRatio, projection, key);
this.tileCache.set(tileCoordKey, tile); this.tileCache.set(tileCoordKey, tile);
} else { } else {
tile = /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); tile = this.tileCache.get(tileCoordKey);
if (tile.key != key) { if (tile.key != key) {
// The source's params changed. If the tile has an interim tile and if we // The source's params changed. If the tile has an interim tile and if we
// can use it then we use it. Otherwise we create a new tile. In both // can use it then we use it. Otherwise we create a new tile. In both
// cases we attempt to assign an interim tile to the new tile. // cases we attempt to assign an interim tile to the new tile.
var /** @type {ol.Tile} */ interimTile = tile; var interimTile = tile;
if (tile.interimTile && tile.interimTile.key == key) {
ol.DEBUG && console.assert(tile.interimTile.getState() == ol.Tile.State.LOADED);
ol.DEBUG && console.assert(tile.interimTile.interimTile === null);
tile = tile.interimTile;
if (interimTile.getState() == ol.Tile.State.LOADED) {
tile.interimTile = interimTile;
}
} else {
tile = this.createTile_(z, x, y, pixelRatio, projection, key); tile = this.createTile_(z, x, y, pixelRatio, projection, key);
if (interimTile.getState() == ol.Tile.State.LOADED) {
tile.interimTile = interimTile; //make the new tile the head of the list,
} else if (interimTile.interimTile && if (interimTile.getState() == ol.Tile.State.IDLE) {
interimTile.interimTile.getState() == ol.Tile.State.LOADED) { //the old tile hasn't begun loading yet, and is now outdated, so we can simply discard it
tile.interimTile = interimTile.interimTile; tile.interimTile = interimTile.interimTile;
interimTile.interimTile = null; } else {
} tile.interimTile = interimTile;
}
if (tile.interimTile) {
tile.interimTile.interimTile = null;
} }
tile.refreshInterimChain();
this.tileCache.replace(tileCoordKey, tile); this.tileCache.replace(tileCoordKey, tile);
} }
} }

View File

@@ -72,6 +72,67 @@ ol.Tile.prototype.getKey = function() {
return this.key + '/' + this.tileCoord; return this.key + '/' + this.tileCoord;
}; };
/**
* Get the interim tile most suitable for rendering using the chain of interim
* tiles. This corresponds to the most recent tile that has been loaded, if no
* such tile exists, the original tile is returned.
* @return {!ol.Tile} Best tile for rendering.
*/
ol.Tile.prototype.getInterimTile = function() {
if (!this.interimTile) {
//empty chain
return this;
}
var tile = this.interimTile;
// find the first loaded tile and return it. Since the chain is sorted in
// decreasing order of creation time, there is no need to search the remainder
// of the list (all those tiles correspond to older requests and will be
// cleaned up by refreshInterimChain)
do {
if (tile.getState() == ol.Tile.State.LOADED) {
return tile;
}
tile = tile.interimTile;
} while (tile);
// we can not find a better tile
return this;
};
/**
* Goes through the chain of interim tiles and discards sections of the chain
* that are no longer relevant.
*/
ol.Tile.prototype.refreshInterimChain = function() {
if (!this.interimTile) {
return;
}
var tile = this.interimTile;
var prev = this;
do {
if (tile.getState() == ol.Tile.State.LOADED) {
//we have a loaded tile, we can discard the rest of the list
//we would could abort any LOADING tile request
//older than this tile (i.e. any LOADING tile following this entry in the chain)
tile.interimTile = null;
break;
} else if (tile.getState() == ol.Tile.State.LOADING) {
//keep this LOADING tile any loaded tiles later in the chain are
//older than this tile, so we're still interested in the request
prev = tile;
} else if (tile.getState() == ol.Tile.State.IDLE) {
//the head of the list is the most current tile, we don't need
//to start any other requests for this chain
prev.interimTile = tile.interimTile;
} else {
prev = tile;
}
tile = prev.interimTile;
} while (tile);
};
/** /**
* Get the tile coordinate for this tile. * Get the tile coordinate for this tile.

71
test/spec/ol/tile.test.js Normal file
View File

@@ -0,0 +1,71 @@
goog.provide('ol.test.Tile');
goog.require('ol.Tile');
describe('ol.Tile', function() {
describe('interimChain', function() {
var head, renderTile;
beforeEach(function() {
var tileCoord = [0, 0, 0];
head = new ol.ImageTile(tileCoord, ol.Tile.State.IDLE);
ol.getUid(head);
var addToChain = function(tile, state) {
var next = new ol.ImageTile(tileCoord, state);
ol.getUid(next);
tile.interimTile = next;
return next;
};
var tail = addToChain(head,ol.Tile.State.IDLE); //discard, deprecated by head
tail = addToChain(tail,ol.Tile.State.LOADING); //keep, request already going
tail = addToChain(tail,ol.Tile.State.IDLE); //discard, deprecated by head
tail = addToChain(tail,ol.Tile.State.LOADED); //keep, use for rendering
renderTile = tail; //store this tile for later tests
tail = addToChain(tail,ol.Tile.State.IDLE); //rest of list outdated by tile above
tail = addToChain(tail,ol.Tile.State.LOADED);
tail = addToChain(tail,ol.Tile.State.LOADING);
tail = addToChain(tail,ol.Tile.State.LOADED);
});
it('shrinks tile chain correctly', function(done) {
var chainLength = function(tile) {
var c = 0;
while (tile) {
++c;
tile = tile.interimTile;
}
return c;
};
expect(chainLength(head)).to.be(9);
head.refreshInterimChain();
expect(chainLength(head)).to.be(3);
done();
});
it('gives the right tile to render', function(done) {
expect(head.getInterimTile()).to.be(renderTile);
head.refreshInterimChain();
expect(head.getInterimTile()).to.be(renderTile);
done();
});
it('discards everything after the render tile', function(done) {
head.refreshInterimChain();
expect(renderTile.interimTile).to.be(null);
done();
});
it('preserves order of tiles', function(done) {
head.refreshInterimChain();
while (head.interimTile !== null) {
//use property of ol.getUid returning increasing id's.
expect(ol.getUid(head) < ol.getUid(head.interimTile));
head = head.interimTile;
}
done();
});
});
});