diff --git a/src/ol/imagetile.js b/src/ol/imagetile.js index 883bfe071b..3022a3e7f6 100644 --- a/src/ol/imagetile.js +++ b/src/ol/imagetile.js @@ -152,3 +152,66 @@ ol.ImageTile.prototype.unlistenImage_ = function() { this.imageListenerKeys_.forEach(ol.events.unlistenByKey); this.imageListenerKeys_ = null; }; + +/** + * 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.ImageTile.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 remaineder 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 starting and discards + * sections of the chain that are no longer relevant. + * @return {void} + * @private + */ +ol.ImageTile.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); +}; diff --git a/src/ol/renderer/canvas/tilelayer.js b/src/ol/renderer/canvas/tilelayer.js index 2f94b6920e..a512ee8592 100644 --- a/src/ol/renderer/canvas/tilelayer.js +++ b/src/ol/renderer/canvas/tilelayer.js @@ -139,8 +139,8 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = function( for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { tile = tileSource.getTile(z, x, y, pixelRatio, projection); - if (!drawableTile(tile) && tile.interimTile) { - tile = tile.interimTile; + if (!drawableTile(tile)) { + tile = tile.getInterimTile(); } if (drawableTile(tile)) { tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; diff --git a/src/ol/renderer/webgl/tilelayer.js b/src/ol/renderer/webgl/tilelayer.js index 2581d2e2df..0a4f084a1a 100644 --- a/src/ol/renderer/webgl/tilelayer.js +++ b/src/ol/renderer/webgl/tilelayer.js @@ -252,8 +252,8 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerS drawable = tileState == ol.Tile.State.LOADED || tileState == ol.Tile.State.EMPTY || tileState == ol.Tile.State.ERROR && !useInterimTilesOnError; - if (!drawable && tile.interimTile) { - tile = tile.interimTile; + if (!drawable) { + tile = tile.getInterimTile(); } tileState = tile.getState(); if (tileState == ol.Tile.State.LOADED) { diff --git a/src/ol/source/tileimage.js b/src/ol/source/tileimage.js index dba039aa93..cd84284492 100644 --- a/src/ol/source/tileimage.js +++ b/src/ol/source/tileimage.js @@ -302,26 +302,16 @@ ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, pr // 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. var /** @type {ol.Tile} */ 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; - } + tile = this.createTile_(z, x, y, pixelRatio, projection, key); + + //make the new tile the head of the list, + if (interimTile.getState() == ol.Tile.State.IDLE) { + //the old tile hasn't begun loading yet, and is now outdated, so we can simply discard it + tile.interimTile = interimTile.interimTile; } else { - tile = this.createTile_(z, x, y, pixelRatio, projection, key); - if (interimTile.getState() == ol.Tile.State.LOADED) { - tile.interimTile = interimTile; - } else if (interimTile.interimTile && - interimTile.interimTile.getState() == ol.Tile.State.LOADED) { - tile.interimTile = interimTile.interimTile; - interimTile.interimTile = null; - } - } - if (tile.interimTile) { - tile.interimTile.interimTile = null; + tile.interimTile = interimTile; } + tile.refreshInterimChain(); this.tileCache.replace(tileCoordKey, tile); } } diff --git a/src/ol/tile.js b/src/ol/tile.js index 55e34f0842..9da13fa24e 100644 --- a/src/ol/tile.js +++ b/src/ol/tile.js @@ -72,6 +72,17 @@ ol.Tile.prototype.getKey = function() { return this.key + '/' + this.tileCoord; }; +/** + * Get the interim tile if it exists, otherwise returns the tile itself. + * @return {!ol.Tile} The interim tile, or the tile itself + * + */ +ol.Tile.prototype.getInterimTile = function() { + if (this.interimTile) { + return this.interimTile; + } + return this; +}; /** * Get the tile coordinate for this tile. diff --git a/test/spec/ol/imagetile.test.js b/test/spec/ol/imagetile.test.js index 3986c7c4ff..82de601437 100644 --- a/test/spec/ol/imagetile.test.js +++ b/test/spec/ol/imagetile.test.js @@ -60,7 +60,73 @@ describe('ol.ImageTile', function() { tile.load(); }); + }); + describe('interimChain', function() { + var head, renderTile; + beforeEach(function() { + var tileCoord = [0, 0, 0]; + var src = 'spec/ol/data/osm-0-0-0.png'; + var tileLoadFunction = ol.source.Image.defaultImageLoadFunction; + head = new ol.ImageTile(tileCoord, ol.Tile.State.IDLE, src, null, tileLoadFunction); + ol.getUid(head); + + var addToChain = function(tile, state) { + var next = new ol.ImageTile(tileCoord, state, src, null, tileLoadFunction); + 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(); + }); }); });