Configure maps with TileManger instances (and not the other way around)

This resolves issues with destroying caches on window unload, and makes
cache sharing among maps less confusing to configure.
This commit is contained in:
ahocevar
2012-11-30 10:14:08 -06:00
parent 9609bb1449
commit 781f2ac73d
5 changed files with 146 additions and 121 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf"; var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
// initialize map when page ready // initialize map when page ready
var map, tileManager; var map;
var gg = new OpenLayers.Projection("EPSG:4326"); var gg = new OpenLayers.Projection("EPSG:4326");
var sm = new OpenLayers.Projection("EPSG:900913"); var sm = new OpenLayers.Projection("EPSG:900913");
@@ -42,6 +42,7 @@ var init = function (onSelectFeatureFunction) {
theme: null, theme: null,
projection: sm, projection: sm,
numZoomLevels: 18, numZoomLevels: 18,
tileManager: new OpenLayers.TileManager(),
controls: [ controls: [
new OpenLayers.Control.Attribution(), new OpenLayers.Control.Attribution(),
new OpenLayers.Control.TouchNavigation({ new OpenLayers.Control.TouchNavigation({
@@ -85,7 +86,6 @@ var init = function (onSelectFeatureFunction) {
center: new OpenLayers.LonLat(0, 0), center: new OpenLayers.LonLat(0, 0),
zoom: 1 zoom: 1
}); });
tileManager = new OpenLayers.TileManager({map: map});
var style = { var style = {
fillOpacity: 0.1, fillOpacity: 0.1,
+2 -2
View File
@@ -1,4 +1,4 @@
var map, tileManager; var map;
(function() { (function() {
// Set document language for css content // Set document language for css content
@@ -99,6 +99,7 @@ var map, tileManager;
maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34], maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34],
maxResolution: 156543.0339, maxResolution: 156543.0339,
numZoomLevels: 20, numZoomLevels: 20,
tileManager: new OpenLayers.TileManager(),
controls: [ controls: [
new OpenLayers.Control.Navigation({ new OpenLayers.Control.Navigation({
mouseWheelOptions: { mouseWheelOptions: {
@@ -124,7 +125,6 @@ var map, tileManager;
} }
} }
}); });
tileManager = new OpenLayers.TileManager({map: map});
layerPanel.activateControl(mapButton); layerPanel.activateControl(mapButton);
layerPanel.activateControl(labelButton); layerPanel.activateControl(labelButton);
+9
View File
@@ -580,6 +580,10 @@ OpenLayers.Map = OpenLayers.Class({
{includeXY: true} {includeXY: true}
); );
if (this.tileManager) {
this.tileManager.addMap(this);
}
// the layerContainerDiv is the one that holds all the layers // the layerContainerDiv is the one that holds all the layers
id = this.id + "_OpenLayers_Container"; id = this.id + "_OpenLayers_Container";
this.layerContainerDiv = OpenLayers.Util.createDiv(id); this.layerContainerDiv = OpenLayers.Util.createDiv(id);
@@ -779,6 +783,11 @@ OpenLayers.Map = OpenLayers.Class({
} }
this.viewPortDiv = null; this.viewPortDiv = null;
if (this.tileManager) {
this.tileManager.removeMap(this);
this.tileManager = null;
}
if(this.eventListeners) { if(this.eventListeners) {
this.events.un(this.eventListeners); this.events.un(this.eventListeners);
this.eventListeners = null; this.eventListeners = null;
+104 -84
View File
@@ -21,26 +21,17 @@
* <moveDelay> are the configuration options to control this behavior. * <moveDelay> are the configuration options to control this behavior.
* *
* Caching avoids setting the src on image elements for images that have already * Caching avoids setting the src on image elements for images that have already
* been used. A TileManager instance can have a private cache (when configured * been used. Several maps can share a TileManager instance, in which case each
* with a <cacheSize>), or share a cache with other instances, in which case the * map gets its own tile queue, but all maps share the same tile cache.
* cache size can be controlled by adjusting <OpenLayers.TileManager.cacheSize>.
*/ */
OpenLayers.TileManager = OpenLayers.Class({ OpenLayers.TileManager = OpenLayers.Class({
/**
* APIProperty: map
* {<OpenLayers.Map>} The map to manage tiles on.
*/
map: null,
/** /**
* APIProperty: cacheSize * APIProperty: cacheSize
* {Number} Number of image elements to keep referenced in this instance's * {Number} Number of image elements to keep referenced in this instance's
* private cache for fast reuse. If not set, this instance will use the * cache for fast reuse. Default is 512.
* shared cache. To configure the shared cache size, set
* <OpenLayers.TileManager.cacheSize>.
*/ */
cacheSize: null, cacheSize: 512,
/** /**
* APIProperty: moveDelay * APIProperty: moveDelay
@@ -56,32 +47,37 @@ OpenLayers.TileManager = OpenLayers.Class({
*/ */
zoomDelay: 200, zoomDelay: 200,
/**
* Property: maps
* {Array(<OpenLayers.Map>)} The maps to manage tiles on.
*/
maps: null,
/** /**
* Property: tileQueueId * Property: tileQueueId
* {Number} The id of the <drawTilesFromQueue> animation. * {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id.
*/ */
tileQueueId: null, tileQueueId: null,
/** /**
* Property: tileQueue * Property: tileQueue
* {Array(<OpenLayers.Tile>)} Tiles queued for drawing. * {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by
* map id.
*/ */
tileQueue: null, tileQueue: null,
/** /**
* Property: tileCache * Property: tileCache
* {Object} Cached image elements, keyed by URL. This is shared among all * {Object} Cached image elements, keyed by URL.
* TileManager instances, unless <cacheSize> is set on the instance.
*/ */
tileCache: {}, tileCache: null,
/** /**
* Property: tileCacheIndex * Property: tileCacheIndex
* {Array<String>} URLs of cached tiles; first entry is least recently * {Array<String>} URLs of cached tiles. First entry in each array is the
* used. This is shared among all TileManager instances, unless * least recently used.
* <cacheSize> is set on the instance.
*/ */
tileCacheIndex: [], tileCacheIndex: null,
/** /**
* Constructor: OpenLayers.TileManager * Constructor: OpenLayers.TileManager
@@ -89,46 +85,84 @@ OpenLayers.TileManager = OpenLayers.Class({
* *
* Parameters: * Parameters:
* options - {Object} Configuration for this instance. * options - {Object} Configuration for this instance.
*
* Required options:
* map - {<OpenLayers.Map>} The map to manage tiles on.
*/ */
initialize: function(options) { initialize: function(options) {
OpenLayers.Util.extend(this, options); OpenLayers.Util.extend(this, options);
this.tileQueue = []; this.maps = [];
if (this.cacheSize == null) { this.tileQueueId = {};
this.cacheSize = OpenLayers.TileManager.cacheSize; this.tileQueue = {};
} else { this.tileCache = {};
this.tileCache = {}; this.tileCacheIndex = [];
this.tileCacheIndex = []; },
/**
* Method: addMap
* Binds this instance to a map
*
* Parameters:
* map - {<OpenLayers.Map>}
*/
addMap: function(map) {
if (this._destroyed) {
return;
} }
var map = this.map; this.maps.push(map);
this.tileQueue[map.id] = [];
for (var i=0, ii=map.layers.length; i<ii; ++i) { for (var i=0, ii=map.layers.length; i<ii; ++i) {
this.addLayer({layer: map.layers[i]}); this.addLayer({layer: map.layers[i]});
} }
this.map.events.on({ map.events.on({
move: this.move, move: this.move,
zoomend: this.zoomEnd, zoomend: this.zoomEnd,
addlayer: this.addLayer, addlayer: this.addLayer,
removelayer: this.removeLayer, preremovelayer: this.removeLayer,
scope: this scope: this
}); });
}, },
removeMap: function(map) {
if (this._destroyed) {
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,
addlayer: this.addLayer,
preremovelayer: this.removeLayer,
scope: this
});
}
delete this.tileQueue[map.id];
delete this.tileQueueId[map.id];
},
/** /**
* Method: move * Method: move
* Handles the map's move event * Handles the map's move event
*
* Parameters:
* evt - {Object} Listener argument
*/ */
move: function() { move: function(evt) {
this.updateTimeout(this.moveDelay); this.updateTimeout(evt.object, this.moveDelay);
}, },
/** /**
* Method: zoomEnd * Method: zoomEnd
* Handles the map's zoomEnd event * Handles the map's zoomEnd event
*
* Parameters:
* evt - {Object} Listener argument
*/ */
zoomEnd: function() { zoomEnd: function(evt) {
this.updateTimeout(this.zoomDelay); this.updateTimeout(evt.object, this.zoomDelay);
}, },
/** /**
@@ -160,8 +194,8 @@ OpenLayers.TileManager = OpenLayers.Class({
}, },
/** /**
* Method: addLayer * Method: removeLayer
* Handles the map's removelayer event * Handles the map's preremovelayer event
* *
* Parameters: * Parameters:
* evt - {Object} The listener argument * evt - {Object} The listener argument
@@ -197,14 +231,16 @@ OpenLayers.TileManager = OpenLayers.Class({
* Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop. * Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop.
* *
* Parameters: * Parameters:
* map - {<OpenLayers.Map>} The map to update the timeout for
* delay - {Number} The delay to apply * delay - {Number} The delay to apply
*/ */
updateTimeout: function(delay) { updateTimeout: function(map, delay) {
window.clearTimeout(this.tileQueueId); window.clearTimeout(this.tileQueueId[map.id]);
if (this.tileQueue.length) { if (this.tileQueue[map.id].length) {
this.tileQueueId = window.setTimeout( this.tileQueueId[map.id] = window.setTimeout(
OpenLayers.Function.bind(this.drawTilesFromQueue, this), OpenLayers.Function.bind(function() {
delay this.drawTilesFromQueue(map);
}, this), delay
); );
} }
}, },
@@ -234,7 +270,8 @@ OpenLayers.TileManager = OpenLayers.Class({
* evt - {Object} The listener argument * evt - {Object} The listener argument
*/ */
unloadTile: function(evt) { unloadTile: function(evt) {
evt.object.events.un({ var tile = evt.object;
tile.events.un({
beforedraw: this.queueTileDraw, beforedraw: this.queueTileDraw,
loadstart: this.manageTileCache, loadstart: this.manageTileCache,
reload: this.manageTileCache, reload: this.manageTileCache,
@@ -242,7 +279,7 @@ OpenLayers.TileManager = OpenLayers.Class({
unload: this.unloadTile, unload: this.unloadTile,
scope: this scope: this
}); });
OpenLayers.Util.removeItem(this.tileQueue, evt.object); OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile);
}, },
/** /**
@@ -260,8 +297,9 @@ OpenLayers.TileManager = OpenLayers.Class({
if (layer.url && (layer.async || if (layer.url && (layer.async ||
!this.tileCache[layer.getURL(tile.bounds)])) { !this.tileCache[layer.getURL(tile.bounds)])) {
// add to queue only if not in queue already // add to queue only if not in queue already
if (!~OpenLayers.Util.indexOf(this.tileQueue, tile)) { var tileQueue = this.tileQueue[layer.map.id];
this.tileQueue.push(tile); if (!~OpenLayers.Util.indexOf(tileQueue, tile)) {
tileQueue.push(tile);
} }
queued = true; queued = true;
} }
@@ -272,9 +310,10 @@ OpenLayers.TileManager = OpenLayers.Class({
* Method: drawTilesFromQueue * Method: drawTilesFromQueue
* Draws tiles from the tileQueue, and unqueues the tiles * Draws tiles from the tileQueue, and unqueues the tiles
*/ */
drawTilesFromQueue: function() { drawTilesFromQueue: function(map) {
while (this.tileQueue.length) { var tileQueue = this.tileQueue[map.id];
this.tileQueue.shift().draw(true); while (tileQueue.length) {
tileQueue.shift().draw(true);
} }
}, },
@@ -288,7 +327,7 @@ OpenLayers.TileManager = OpenLayers.Class({
manageTileCache: function(evt) { manageTileCache: function(evt) {
var tile = evt.object; var tile = evt.object;
var img = this.tileCache[tile.url]; var img = this.tileCache[tile.url];
// only use images from the cache that are not on a layer already // only use image from cache if it is not on a layer already
if (img && (!img.parentNode || if (img && (!img.parentNode ||
OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer'))) { OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer'))) {
tile.imgDiv = img; tile.imgDiv = img;
@@ -331,43 +370,24 @@ OpenLayers.TileManager = OpenLayers.Class({
*/ */
clearTileQueue: function(evt) { clearTileQueue: function(evt) {
var layer = evt.object; var layer = evt.object;
for (var i=this.tileQueue.length-1; i>=0; --i) { var tileQueue = this.tileQueue[layer.map.id];
if (this.tileQueue[i].layer === layer) { for (var i=tileQueue.length-1; i>=0; --i) {
this.tileQueue.splice(i, 1); if (tileQueue.layer === layer) {
tileQueue.splice(i, 1);
} }
} }
}, },
destroy: function() { destroy: function() {
window.clearTimeout(this.tileQueueId); for (var i=this.maps.length-1; i>=0; --i) {
var map = this.map; this.removeMap(this.maps[i]);
if (map.layers) {
for (var i=0, ii=map.layers.length; i<ii; ++i) {
this.removeLayer({layer: map.layers[i]});
}
} }
if (map.events) { this.maps = null;
map.events.un({
move: this.move,
zoomend: this.zoomEnd,
addlayer: this.addLayer,
removelayer: this.removeLayer,
scope: this
});
}
this.map = null;
this.tileQueue = null; this.tileQueue = null;
if (this.tileCache !== OpenLayers.TileManager.prototype.tileCache) { this.tileQueueId = null;
this.tileCache = null; this.tileCache = null;
this.tileCacheIndex = null; this.tileCacheIndex = null;
} this._destroyed = true;
} }
}); });
/**
* APIProperty: OpenLayers.TileManager.cacheSize
* {Number} Number of image elements to keep referenced in the shared cache
* for fast reuse. Default is 512.
*/
OpenLayers.TileManager.cacheSize = 512;
+28 -32
View File
@@ -6,48 +6,48 @@
function test_initialize(t) { function test_initialize(t) {
t.plan(4); t.plan(4);
var map = new OpenLayers.Map('map'); var tileManager = new OpenLayers.TileManager();
var map = new OpenLayers.Map('map', {
tileManager: tileManager
});
var layer = new OpenLayers.Layer.WMS('WMS1', '../img/blank.gif'); var layer = new OpenLayers.Layer.WMS('WMS1', '../img/blank.gif');
map.addLayer(layer); map.addLayer(layer);
var tileManager = new OpenLayers.TileManager({map: map});
map.setCenter([16, 48], 9); map.setCenter([16, 48], 9);
t.ok(tileManager.tileQueue.length, "Tiles queued from layer"); t.ok(tileManager.tileQueue[map.id].length, "Tiles queued from layer");
map.removeLayer(layer); map.removeLayer(layer);
t.eq(tileManager.tileQueue.length, 0, "Tiles unqueued when layer is removed"); t.eq(tileManager.tileQueue[map.id].length, 0, "Tiles unqueued when layer is removed");
map.addLayer(new OpenLayers.Layer.WMS('WMS2', '../img/blank.gif')); map.addLayer(new OpenLayers.Layer.WMS('WMS2', '../img/blank.gif'));
map.zoomIn(); map.zoomIn();
t.ok(tileManager.tileQueue.length, "Tiles queued from added layer"); t.ok(tileManager.tileQueue[map.id].length, "Tiles queued from added layer");
map.destroy(); map.destroy();
t.eq(tileManager.tileQueue.length, 0, "Tiles unqueued when map is destroyed"); t.eq(tileManager.tileQueue[map.id], undefined, "Tile queue removed when map was destroyed");
tileManager.destroy();
} }
function test_destroy(t) { function test_destroy(t) {
t.plan(3); t.plan(3);
var map = new OpenLayers.Map('map'); var tileManager = new OpenLayers.TileManager();
var map = new OpenLayers.Map('map', {tileManager: tileManager});
var layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif'); var layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
map.addLayer(layer); map.addLayer(layer);
map.setCenter([16, 48], 9); map.setCenter([16, 48], 9);
var numTileListeners = (layer.grid[0][0].events.listeners.reload || []).length; var numTileListeners = layer.grid[0][0].events.listeners.reload.length;
var numLayerListeners = (layer.events.listeners.retile || []).length; var numLayerListeners = layer.events.listeners.retile.length;
var numMapListeners = (map.events.listeners.removelayer || []).length; var numMapListeners = map.events.listeners.preremovelayer.length;
var tileManager = new OpenLayers.TileManager({map: map});
tileManager.destroy(); tileManager.destroy();
t.eq(layer.grid[0][0].events.listeners.reload.length, numTileListeners, "no listener on tile after destroy"); t.eq(layer.grid[0][0].events.listeners.reload.length, numTileListeners - 1, "no listener on tile after destroy");
t.eq(layer.events.listeners.retile.length, numLayerListeners, "no listeners on layer after destroy"); t.eq(layer.events.listeners.retile.length, numLayerListeners - 1, "no listeners on layer after destroy");
t.eq(map.events.listeners.removelayer.length, numMapListeners, "no listeners on map after destroy"); t.eq(map.events.listeners.preremovelayer.length, numMapListeners - 1, "no listeners on map after destroy");
map.destroy(); map.destroy();
} }
function test_manageTileCache(t) { function test_manageTileCache(t) {
t.plan(9); t.plan(9);
var map = new OpenLayers.Map('map');
var tileManager = new OpenLayers.TileManager({ var tileManager = new OpenLayers.TileManager({
map: map,
cacheSize: 12 cacheSize: 12
}); });
var map = new OpenLayers.Map('map', {tileManager: tileManager});
layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif'); layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
map.addLayer(layer); map.addLayer(layer);
map.setCenter([16, 48], 9); map.setCenter([16, 48], 9);
@@ -79,19 +79,17 @@
function test_queueTileDraw(t) { function test_queueTileDraw(t) {
t.plan(3); t.plan(3);
var map = new OpenLayers.Map('map'); var tileManager = new OpenLayers.TileManager();
var tileManager = new OpenLayers.TileManager({ var map = new OpenLayers.Map('map', {tileManager: tileManager});
map: map
});
layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif'); layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
map.addLayer(layer); map.addLayer(layer);
map.setCenter([0, 0], 3); map.setCenter([0, 0], 3);
var queued = tileManager.tileQueue.length; var queued = tileManager.tileQueue[map.id].length;
t.ok(tileManager.tileQueue.length, "Tiles queued for drawing"); t.ok(tileManager.tileQueue[map.id].length, "Tiles queued for drawing");
map.zoomIn(); map.zoomIn();
t.eq(tileManager.tileQueue.length, queued, "Tile queue has same length after immediate zoom change"); t.eq(tileManager.tileQueue[map.id].length, queued, "Tile queue has same length after immediate zoom change");
t.delay_call(1, function() { t.delay_call(1, function() {
t.eq(tileManager.tileQueue.length, 0, "Tiles from queue processed"); t.eq(tileManager.tileQueue[map.id].length, 0, "Tiles from queue processed");
map.destroy(); map.destroy();
}); });
} }
@@ -100,10 +98,8 @@
t.plan(3); t.plan(3);
var map = new OpenLayers.Map('map'); var tileManager = new OpenLayers.TileManager();
var tileManager = new OpenLayers.TileManager({ var map = new OpenLayers.Map('map', {tileManager: tileManager});
map: map
});
layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif'); layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
layer.destroy = function() {}; //we're going to do funky things with the grid layer.destroy = function() {}; //we're going to do funky things with the grid
layer.applyBackBuffer = function() {}; // backbuffering isn't under test here layer.applyBackBuffer = function() {}; // backbuffering isn't under test here
@@ -111,11 +107,11 @@
map.setCenter([-10, 0], 5); map.setCenter([-10, 0], 5);
map.moveTo([5, 0]); map.moveTo([5, 0]);
t.ok(tileManager.tileQueue.length, "tile loading deferred after moveTo"); t.ok(tileManager.tileQueue[map.id].length, "tile loading deferred after moveTo");
map.moveTo([0, 0]); map.moveTo([0, 0]);
t.ok(tileManager.tileQueue.length, "deferred again after another moveTo"); t.ok(tileManager.tileQueue[map.id].length, "deferred again after another moveTo");
t.delay_call(1, function() { t.delay_call(1, function() {
t.eq(tileManager.tileQueue.length, 0, "tiles loaded after moveDelay"); t.eq(tileManager.tileQueue[map.id].length, 0, "tiles loaded after moveDelay");
}); });
} }
</script> </script>