diff --git a/lib/OpenLayers/Layer/Bing.js b/lib/OpenLayers/Layer/Bing.js index 638891dadb..06af556cf2 100644 --- a/lib/OpenLayers/Layer/Bing.js +++ b/lib/OpenLayers/Layer/Bing.js @@ -201,13 +201,13 @@ OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { }, /** - * Method: drawTileFromQueue - * Draws the first tile from the tileQueue, and unqueues that tile + * Method: drawTilesFromQueue + * Draws tiles from the tileQueue, and unqueues the tiles */ - drawTileFromQueue: function() { + drawTilesFromQueue: function() { // don't start working on the queue before we have a url from initLayer if (this.url) { - OpenLayers.Layer.XYZ.prototype.drawTileFromQueue.apply(this, arguments); + OpenLayers.Layer.XYZ.prototype.drawTilesFromQueue.apply(this, arguments); } }, diff --git a/lib/OpenLayers/Layer/Grid.js b/lib/OpenLayers/Layer/Grid.js index 8791236cb6..28ae7734d5 100644 --- a/lib/OpenLayers/Layer/Grid.js +++ b/lib/OpenLayers/Layer/Grid.js @@ -144,7 +144,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { /** * Property: tileQueueId - * {Number} The id of the animation. + * {Number} The id of the animation. */ tileQueueId: null, @@ -240,6 +240,26 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * should not be zero. */ className: null, + + /** + * Property: tileCache + * {Object} Cached image elements, keyed by URL. + */ + tileCache: null, + + /** + * Property: tileCacheIndex + * {Array} URLs of cached tiles; first entry is least recently + * used. + */ + tileCacheIndex: null, + + /** + * APIProperty: tileCacheSize + * {Number} Number of image elements to keep referenced for fast reuse. + * Default is 128 per layer. + */ + tileCacheSize: 128, /** * Register a listener for a particular event with the following syntax: @@ -309,6 +329,8 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { arguments); this.grid = []; this.tileQueue = []; + this.tileCache = {}; + this.tileCacheIndex = []; this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this); if (this.removeBackBufferDelay === null) { @@ -370,6 +392,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { this.grid = null; this.tileSize = null; + this.tileCache = null; OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); }, @@ -585,37 +608,79 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { */ queueTileDraw: function(evt) { var tile = evt.object; - if (!~OpenLayers.Util.indexOf(this.tileQueue, tile)) { - // queue only if not in queue already - this.tileQueue.push(tile); + var queued = false; + if (this.async || !this.url || + !this.tileCache[this.getURL(tile.bounds)]) { + // queue only if not in tileCache already + if (!~OpenLayers.Util.indexOf(this.tileQueue, tile)) { + // add to queue only if not in queue already + this.tileQueue.push(tile); + } + queued = true; + if (!this.tileQueueId) { + this.tileQueueId = OpenLayers.Animation.start( + OpenLayers.Function.bind(this.drawTilesFromQueue, this), + null, this.div + ); + } } - if (!this.tileQueueId) { - this.tileQueueId = OpenLayers.Animation.start( - OpenLayers.Function.bind(this.drawTileFromQueue, this), - null, this.div - ); - } - return false; + return !queued; }, /** - * Method: drawTileFromQueue - * Draws the first tile from the tileQueue, and unqueues that tile + * Method: drawTilesFromQueue + * Draws tiles from the tileQueue, and unqueues the tiles */ - drawTileFromQueue: function() { - if (this.tileQueue.length === 0) { - this.clearTileQueue(); - } else { + drawTilesFromQueue: function() { + var numUrls = OpenLayers.Util.isArray(this.url) ? this.url.length : 1; + //TODO instead of using 2 * urls, we could keep track of the hosts used + // by all grid layers, and use a number that just saturates the number + // of parallel requests the browser can send + while (this.numLoadingTiles < 2 * numUrls) { + if (this.tileQueue.length === 0) { + this.clearTileQueue(); + break; + } this.tileQueue.shift().draw(true); } }, + /** + * Method: manageTileCache + * Adds, updates, removes and fetches cache entries. + * + * Parameters: + * evt - {Object} Listener argument of the tile's loadstart event + */ + manageTileCache: function(evt) { + var tile = evt.object; + if (this.tileCache[tile.url]) { + tile.imgDiv = this.tileCache[tile.url]; + OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url); + this.tileCacheIndex.push(tile.url); + tile.positionTile(); + this.div.appendChild(tile.imgDiv); + } else { + tile.events.register('loadend', this, function loadend() { + tile.events.unregister('loadend', this, loadend); + if (!this.tileCache[tile.url]) { + if (this.tileCacheIndex.length >= this.tileCacheSize) { + delete this.tileCache[this.tileCacheIndex[0]]; + this.tileCacheIndex.shift(); + } + this.tileCache[tile.url] = tile.imgDiv; + this.tileCacheIndex.push(tile.url); + } + }); + } + }, + /** * Method: clearTileQueue * Clears the animation queue */ clearTileQueue: function() { - OpenLayers.Animation.stop(this.tileQueueId); + window.clearInterval(this.tileQueueId); this.tileQueueId = null; this.tileQueue = []; }, @@ -1102,7 +1167,11 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { var tile = new this.tileClass( this, position, bounds, null, this.tileSize, this.tileOptions ); - tile.events.register("beforedraw", this, this.queueTileDraw); + tile.events.on({ + beforedraw: this.queueTileDraw, + loadstart: this.manageTileCache, + scope: this + }); return tile; }, diff --git a/tests/Layer/Grid.html b/tests/Layer/Grid.html index 6e7d4181bf..ac1db09e89 100644 --- a/tests/Layer/Grid.html +++ b/tests/Layer/Grid.html @@ -100,6 +100,39 @@ map.destroy(); } + function test_manageTileCache(t) { + t.plan(9); + + var map = new OpenLayers.Map('map'); + layer = new OpenLayers.Layer.WMS(name, "../../img/blank.gif", params, { + tileCacheSize: 12 + }); + map.addLayer(layer); + map.setCenter([16, 48], 9); + + var firstInCache, sharedTile; + t.delay_call(2, function() { + t.eq(layer.tileCacheIndex.length, 12, "tiles cached"); + t.ok(~OpenLayers.Util.indexOf(layer.tileCacheIndex, layer.grid[1][2].url), "tile found in cache"); + t.ok(layer.tileCache[layer.grid[1][2].url] === layer.grid[1][2].imgDiv, "correct object cached"); + firstInCache = layer.tileCache[layer.tileCacheIndex[0]]; + sharedTile = layer.tileCache[layer.tileCacheIndex[11]]; + map.setCenter([17, 47]); + }); + t.delay_call(4, function() { + t.eq(layer.tileCacheIndex.length, 12, "tiles cached"); + t.ok(layer.tileCache[layer.grid[1][2].url] === layer.grid[1][2].imgDiv, "correct object cached"); + t.ok(!(firstInCache.getAttribute("src") in layer.tileCache), "old tile discarded"); + t.ok(sharedTile.getAttribute("src") in layer.tileCache, "shared tile still in cache"); + firstInCache = layer.tileCache[layer.tileCacheIndex[0]]; + map.setCenter([16, 48]); + }); + t.delay_call(6, function() { + t.ok(!(firstInCache.getAttribute("src") in layer.tileCache), "old tile discarded"); + t.ok(sharedTile.getAttribute("src") in layer.tileCache, "shared tile still in cache"); + }); + } + function test_queueTileDraw(t) { t.plan(3); OpenLayers.Layer.Grid.prototype.queueTileDraw = origQueueTileDraw;