diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/TileManager.js')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/TileManager.js | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/TileManager.js b/misc/openlayers/lib/OpenLayers/TileManager.js new file mode 100644 index 0000000..5ae1666 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/TileManager.js @@ -0,0 +1,462 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Util.js + * @requires OpenLayers/BaseTypes.js + * @requires OpenLayers/BaseTypes/Element.js + * @requires OpenLayers/Layer/Grid.js + * @requires OpenLayers/Tile/Image.js + */ + +/** + * Class: OpenLayers.TileManager + * Provides queueing of image requests and caching of image elements. + * + * Queueing avoids unnecessary image requests while changing zoom levels + * quickly, and helps improve dragging performance on mobile devices that show + * a lag in dragging when loading of new images starts. <zoomDelay> and + * <moveDelay> are the configuration options to control this behavior. + * + * Caching avoids setting the src on image elements for images that have already + * been used. Several maps can share a TileManager instance, in which case each + * map gets its own tile queue, but all maps share the same tile cache. + */ +OpenLayers.TileManager = OpenLayers.Class({ + + /** + * APIProperty: cacheSize + * {Number} Number of image elements to keep referenced in this instance's + * cache for fast reuse. Default is 256. + */ + cacheSize: 256, + + /** + * APIProperty: tilesPerFrame + * {Number} Number of queued tiles to load per frame (see <frameDelay>). + * Default is 2. + */ + tilesPerFrame: 2, + + /** + * APIProperty: frameDelay + * {Number} Delay between tile loading frames (see <tilesPerFrame>) in + * milliseconds. Default is 16. + */ + frameDelay: 16, + + /** + * APIProperty: moveDelay + * {Number} Delay in milliseconds after a map's move event before loading + * tiles. Default is 100. + */ + moveDelay: 100, + + /** + * APIProperty: zoomDelay + * {Number} Delay in milliseconds after a map's zoomend event before loading + * tiles. Default is 200. + */ + zoomDelay: 200, + + /** + * Property: maps + * {Array(<OpenLayers.Map>)} The maps to manage tiles on. + */ + maps: null, + + /** + * Property: tileQueueId + * {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id. + */ + tileQueueId: null, + + /** + * Property: tileQueue + * {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by + * map id. + */ + tileQueue: null, + + /** + * Property: tileCache + * {Object} Cached image elements, keyed by URL. + */ + tileCache: null, + + /** + * Property: tileCacheIndex + * {Array(String)} URLs of cached tiles. First entry is the least recently + * used. + */ + tileCacheIndex: null, + + /** + * Constructor: OpenLayers.TileManager + * Constructor for a new <OpenLayers.TileManager> instance. + * + * Parameters: + * options - {Object} Configuration for this instance. + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + this.maps = []; + this.tileQueueId = {}; + this.tileQueue = {}; + this.tileCache = {}; + this.tileCacheIndex = []; + }, + + /** + * Method: addMap + * Binds this instance to a map + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + addMap: function(map) { + if (this._destroyed || !OpenLayers.Layer.Grid) { + return; + } + this.maps.push(map); + this.tileQueue[map.id] = []; + for (var i=0, ii=map.layers.length; i<ii; ++i) { + this.addLayer({layer: map.layers[i]}); + } + map.events.on({ + move: this.move, + zoomend: this.zoomEnd, + changelayer: this.changeLayer, + addlayer: this.addLayer, + preremovelayer: this.removeLayer, + scope: this + }); + }, + + /** + * Method: removeMap + * Unbinds this instance from a map + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + removeMap: function(map) { + if (this._destroyed || !OpenLayers.Layer.Grid) { + return; + } + window.clearTimeout(this.tileQueueId[map.id]); + if (map.layers) { + for (var i=0, ii=map.layers.length; i<ii; ++i) { + this.removeLayer({layer: map.layers[i]}); + } + } + if (map.events) { + map.events.un({ + move: this.move, + zoomend: this.zoomEnd, + changelayer: this.changeLayer, + addlayer: this.addLayer, + preremovelayer: this.removeLayer, + scope: this + }); + } + delete this.tileQueue[map.id]; + delete this.tileQueueId[map.id]; + OpenLayers.Util.removeItem(this.maps, map); + }, + + /** + * Method: move + * Handles the map's move event + * + * Parameters: + * evt - {Object} Listener argument + */ + move: function(evt) { + this.updateTimeout(evt.object, this.moveDelay, true); + }, + + /** + * Method: zoomEnd + * Handles the map's zoomEnd event + * + * Parameters: + * evt - {Object} Listener argument + */ + zoomEnd: function(evt) { + this.updateTimeout(evt.object, this.zoomDelay); + }, + + /** + * Method: changeLayer + * Handles the map's changeLayer event + * + * Parameters: + * evt - {Object} Listener argument + */ + changeLayer: function(evt) { + if (evt.property === 'visibility' || evt.property === 'params') { + this.updateTimeout(evt.object, 0); + } + }, + + /** + * Method: addLayer + * Handles the map's addlayer event + * + * Parameters: + * evt - {Object} The listener argument + */ + addLayer: function(evt) { + var layer = evt.layer; + if (layer instanceof OpenLayers.Layer.Grid) { + layer.events.on({ + addtile: this.addTile, + retile: this.clearTileQueue, + scope: this + }); + var i, j, tile; + for (i=layer.grid.length-1; i>=0; --i) { + for (j=layer.grid[i].length-1; j>=0; --j) { + tile = layer.grid[i][j]; + this.addTile({tile: tile}); + if (tile.url && !tile.imgDiv) { + this.manageTileCache({object: tile}); + } + } + } + } + }, + + /** + * Method: removeLayer + * Handles the map's preremovelayer event + * + * Parameters: + * evt - {Object} The listener argument + */ + removeLayer: function(evt) { + var layer = evt.layer; + if (layer instanceof OpenLayers.Layer.Grid) { + this.clearTileQueue({object: layer}); + if (layer.events) { + layer.events.un({ + addtile: this.addTile, + retile: this.clearTileQueue, + scope: this + }); + } + if (layer.grid) { + var i, j, tile; + for (i=layer.grid.length-1; i>=0; --i) { + for (j=layer.grid[i].length-1; j>=0; --j) { + tile = layer.grid[i][j]; + this.unloadTile({object: tile}); + } + } + } + } + }, + + /** + * Method: updateTimeout + * Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop, + * and schedules more queue processing after <frameDelay> if there are still + * tiles in the queue. + * + * Parameters: + * map - {<OpenLayers.Map>} The map to update the timeout for + * delay - {Number} The delay to apply + * nice - {Boolean} If true, the timeout function will only be created if + * the tilequeue is not empty. This is used by the move handler to + * avoid impacts on dragging performance. For other events, the tile + * queue may not be populated yet, so we need to set the timer + * regardless of the queue size. + */ + updateTimeout: function(map, delay, nice) { + window.clearTimeout(this.tileQueueId[map.id]); + var tileQueue = this.tileQueue[map.id]; + if (!nice || tileQueue.length) { + this.tileQueueId[map.id] = window.setTimeout( + OpenLayers.Function.bind(function() { + this.drawTilesFromQueue(map); + if (tileQueue.length) { + this.updateTimeout(map, this.frameDelay); + } + }, this), delay + ); + } + }, + + /** + * Method: addTile + * Listener for the layer's addtile event + * + * Parameters: + * evt - {Object} The listener argument + */ + addTile: function(evt) { + if (evt.tile instanceof OpenLayers.Tile.Image) { + evt.tile.events.on({ + beforedraw: this.queueTileDraw, + beforeload: this.manageTileCache, + loadend: this.addToCache, + unload: this.unloadTile, + scope: this + }); + } else { + // Layer has the wrong tile type, so don't handle it any longer + this.removeLayer({layer: evt.tile.layer}); + } + }, + + /** + * Method: unloadTile + * Listener for the tile's unload event + * + * Parameters: + * evt - {Object} The listener argument + */ + unloadTile: function(evt) { + var tile = evt.object; + tile.events.un({ + beforedraw: this.queueTileDraw, + beforeload: this.manageTileCache, + loadend: this.addToCache, + unload: this.unloadTile, + scope: this + }); + OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile); + }, + + /** + * Method: queueTileDraw + * Adds a tile to the queue that will draw it. + * + * Parameters: + * evt - {Object} Listener argument of the tile's beforedraw event + */ + queueTileDraw: function(evt) { + var tile = evt.object; + var queued = false; + var layer = tile.layer; + var url = layer.getURL(tile.bounds); + var img = this.tileCache[url]; + if (img && img.className !== 'olTileImage') { + // cached image no longer valid, e.g. because we're olTileReplacing + delete this.tileCache[url]; + OpenLayers.Util.removeItem(this.tileCacheIndex, url); + img = null; + } + // queue only if image with same url not cached already + if (layer.url && (layer.async || !img)) { + // add to queue only if not in queue already + var tileQueue = this.tileQueue[layer.map.id]; + if (!~OpenLayers.Util.indexOf(tileQueue, tile)) { + tileQueue.push(tile); + } + queued = true; + } + return !queued; + }, + + /** + * Method: drawTilesFromQueue + * Draws tiles from the tileQueue, and unqueues the tiles + */ + drawTilesFromQueue: function(map) { + var tileQueue = this.tileQueue[map.id]; + var limit = this.tilesPerFrame; + var animating = map.zoomTween && map.zoomTween.playing; + while (!animating && tileQueue.length && limit) { + tileQueue.shift().draw(true); + --limit; + } + }, + + /** + * Method: manageTileCache + * Adds, updates, removes and fetches cache entries. + * + * Parameters: + * evt - {Object} Listener argument of the tile's beforeload event + */ + manageTileCache: function(evt) { + var tile = evt.object; + var img = this.tileCache[tile.url]; + if (img) { + // if image is on its layer's backbuffer, remove it from backbuffer + if (img.parentNode && + OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer')) { + img.parentNode.removeChild(img); + img.id = null; + } + // only use image from cache if it is not on a layer already + if (!img.parentNode) { + img.style.visibility = 'hidden'; + img.style.opacity = 0; + tile.setImage(img); + // LRU - move tile to the end of the array to mark it as the most + // recently used + OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url); + this.tileCacheIndex.push(tile.url); + } + } + }, + + /** + * Method: addToCache + * + * Parameters: + * evt - {Object} Listener argument for the tile's loadend event + */ + addToCache: function(evt) { + var tile = evt.object; + if (!this.tileCache[tile.url]) { + if (!OpenLayers.Element.hasClass(tile.imgDiv, 'olImageLoadError')) { + if (this.tileCacheIndex.length >= this.cacheSize) { + delete this.tileCache[this.tileCacheIndex[0]]; + this.tileCacheIndex.shift(); + } + this.tileCache[tile.url] = tile.imgDiv; + this.tileCacheIndex.push(tile.url); + } + } + }, + + /** + * Method: clearTileQueue + * Clears the tile queue from tiles of a specific layer + * + * Parameters: + * evt - {Object} Listener argument of the layer's retile event + */ + clearTileQueue: function(evt) { + var layer = evt.object; + var tileQueue = this.tileQueue[layer.map.id]; + for (var i=tileQueue.length-1; i>=0; --i) { + if (tileQueue[i].layer === layer) { + tileQueue.splice(i, 1); + } + } + }, + + /** + * Method: destroy + */ + destroy: function() { + for (var i=this.maps.length-1; i>=0; --i) { + this.removeMap(this.maps[i]); + } + this.maps = null; + this.tileQueue = null; + this.tileQueueId = null; + this.tileCache = null; + this.tileCacheIndex = null; + this._destroyed = true; + } + +}); |