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

View File

@@ -3,7 +3,7 @@
var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
// initialize map when page ready
var map, tileManager;
var map;
var gg = new OpenLayers.Projection("EPSG:4326");
var sm = new OpenLayers.Projection("EPSG:900913");
@@ -42,6 +42,7 @@ var init = function (onSelectFeatureFunction) {
theme: null,
projection: sm,
numZoomLevels: 18,
tileManager: new OpenLayers.TileManager(),
controls: [
new OpenLayers.Control.Attribution(),
new OpenLayers.Control.TouchNavigation({
@@ -85,7 +86,6 @@ var init = function (onSelectFeatureFunction) {
center: new OpenLayers.LonLat(0, 0),
zoom: 1
});
tileManager = new OpenLayers.TileManager({map: map});
var style = {
fillOpacity: 0.1,

View File

@@ -1,4 +1,4 @@
var map, tileManager;
var map;
(function() {
// Set document language for css content
@@ -99,6 +99,7 @@ var map, tileManager;
maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34],
maxResolution: 156543.0339,
numZoomLevels: 20,
tileManager: new OpenLayers.TileManager(),
controls: [
new OpenLayers.Control.Navigation({
mouseWheelOptions: {
@@ -124,7 +125,6 @@ var map, tileManager;
}
}
});
tileManager = new OpenLayers.TileManager({map: map});
layerPanel.activateControl(mapButton);
layerPanel.activateControl(labelButton);

View File

@@ -579,6 +579,10 @@ OpenLayers.Map = OpenLayers.Class({
this, this.viewPortDiv, null, this.fallThrough,
{includeXY: true}
);
if (this.tileManager) {
this.tileManager.addMap(this);
}
// the layerContainerDiv is the one that holds all the layers
id = this.id + "_OpenLayers_Container";
@@ -778,6 +782,11 @@ OpenLayers.Map = OpenLayers.Class({
this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
}
this.viewPortDiv = null;
if (this.tileManager) {
this.tileManager.removeMap(this);
this.tileManager = null;
}
if(this.eventListeners) {
this.events.un(this.eventListeners);

View File

@@ -21,26 +21,17 @@
* <moveDelay> are the configuration options to control this behavior.
*
* 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
* with a <cacheSize>), or share a cache with other instances, in which case the
* cache size can be controlled by adjusting <OpenLayers.TileManager.cacheSize>.
* 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: map
* {<OpenLayers.Map>} The map to manage tiles on.
*/
map: null,
/**
* APIProperty: cacheSize
* {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
* shared cache. To configure the shared cache size, set
* <OpenLayers.TileManager.cacheSize>.
* cache for fast reuse. Default is 512.
*/
cacheSize: null,
cacheSize: 512,
/**
* APIProperty: moveDelay
@@ -56,32 +47,37 @@ OpenLayers.TileManager = OpenLayers.Class({
*/
zoomDelay: 200,
/**
* Property: maps
* {Array(<OpenLayers.Map>)} The maps to manage tiles on.
*/
maps: null,
/**
* Property: tileQueueId
* {Number} The id of the <drawTilesFromQueue> animation.
* {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id.
*/
tileQueueId: null,
/**
* Property: tileQueue
* {Array(<OpenLayers.Tile>)} Tiles queued for drawing.
* {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by
* map id.
*/
tileQueue: null,
/**
* Property: tileCache
* {Object} Cached image elements, keyed by URL. This is shared among all
* TileManager instances, unless <cacheSize> is set on the instance.
* {Object} Cached image elements, keyed by URL.
*/
tileCache: {},
tileCache: null,
/**
* Property: tileCacheIndex
* {Array<String>} URLs of cached tiles; first entry is least recently
* used. This is shared among all TileManager instances, unless
* <cacheSize> is set on the instance.
* {Array<String>} URLs of cached tiles. First entry in each array is the
* least recently used.
*/
tileCacheIndex: [],
tileCacheIndex: null,
/**
* Constructor: OpenLayers.TileManager
@@ -89,46 +85,84 @@ OpenLayers.TileManager = OpenLayers.Class({
*
* Parameters:
* options - {Object} Configuration for this instance.
*
* Required options:
* map - {<OpenLayers.Map>} The map to manage tiles on.
*/
initialize: function(options) {
OpenLayers.Util.extend(this, options);
this.tileQueue = [];
if (this.cacheSize == null) {
this.cacheSize = OpenLayers.TileManager.cacheSize;
} else {
this.tileCache = {};
this.tileCacheIndex = [];
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) {
return;
}
var map = this.map;
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]});
}
this.map.events.on({
map.events.on({
move: this.move,
zoomend: this.zoomEnd,
addlayer: this.addLayer,
removelayer: this.removeLayer,
preremovelayer: this.removeLayer,
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
* Handles the map's move event
*
* Parameters:
* evt - {Object} Listener argument
*/
move: function() {
this.updateTimeout(this.moveDelay);
move: function(evt) {
this.updateTimeout(evt.object, this.moveDelay);
},
/**
* Method: zoomEnd
* Handles the map's zoomEnd event
*
* Parameters:
* evt - {Object} Listener argument
*/
zoomEnd: function() {
this.updateTimeout(this.zoomDelay);
zoomEnd: function(evt) {
this.updateTimeout(evt.object, this.zoomDelay);
},
/**
@@ -160,8 +194,8 @@ OpenLayers.TileManager = OpenLayers.Class({
},
/**
* Method: addLayer
* Handles the map's removelayer event
* Method: removeLayer
* Handles the map's preremovelayer event
*
* Parameters:
* evt - {Object} The listener argument
@@ -197,14 +231,16 @@ OpenLayers.TileManager = OpenLayers.Class({
* Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop.
*
* Parameters:
* map - {<OpenLayers.Map>} The map to update the timeout for
* delay - {Number} The delay to apply
*/
updateTimeout: function(delay) {
window.clearTimeout(this.tileQueueId);
if (this.tileQueue.length) {
this.tileQueueId = window.setTimeout(
OpenLayers.Function.bind(this.drawTilesFromQueue, this),
delay
updateTimeout: function(map, delay) {
window.clearTimeout(this.tileQueueId[map.id]);
if (this.tileQueue[map.id].length) {
this.tileQueueId[map.id] = window.setTimeout(
OpenLayers.Function.bind(function() {
this.drawTilesFromQueue(map);
}, this), delay
);
}
},
@@ -234,7 +270,8 @@ OpenLayers.TileManager = OpenLayers.Class({
* evt - {Object} The listener argument
*/
unloadTile: function(evt) {
evt.object.events.un({
var tile = evt.object;
tile.events.un({
beforedraw: this.queueTileDraw,
loadstart: this.manageTileCache,
reload: this.manageTileCache,
@@ -242,7 +279,7 @@ OpenLayers.TileManager = OpenLayers.Class({
unload: this.unloadTile,
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 ||
!this.tileCache[layer.getURL(tile.bounds)])) {
// add to queue only if not in queue already
if (!~OpenLayers.Util.indexOf(this.tileQueue, tile)) {
this.tileQueue.push(tile);
var tileQueue = this.tileQueue[layer.map.id];
if (!~OpenLayers.Util.indexOf(tileQueue, tile)) {
tileQueue.push(tile);
}
queued = true;
}
@@ -272,9 +310,10 @@ OpenLayers.TileManager = OpenLayers.Class({
* Method: drawTilesFromQueue
* Draws tiles from the tileQueue, and unqueues the tiles
*/
drawTilesFromQueue: function() {
while (this.tileQueue.length) {
this.tileQueue.shift().draw(true);
drawTilesFromQueue: function(map) {
var tileQueue = this.tileQueue[map.id];
while (tileQueue.length) {
tileQueue.shift().draw(true);
}
},
@@ -288,7 +327,7 @@ OpenLayers.TileManager = OpenLayers.Class({
manageTileCache: function(evt) {
var tile = evt.object;
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 ||
OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer'))) {
tile.imgDiv = img;
@@ -331,43 +370,24 @@ OpenLayers.TileManager = OpenLayers.Class({
*/
clearTileQueue: function(evt) {
var layer = evt.object;
for (var i=this.tileQueue.length-1; i>=0; --i) {
if (this.tileQueue[i].layer === layer) {
this.tileQueue.splice(i, 1);
var tileQueue = this.tileQueue[layer.map.id];
for (var i=tileQueue.length-1; i>=0; --i) {
if (tileQueue.layer === layer) {
tileQueue.splice(i, 1);
}
}
},
destroy: function() {
window.clearTimeout(this.tileQueueId);
var map = this.map;
if (map.layers) {
for (var i=0, ii=map.layers.length; i<ii; ++i) {
this.removeLayer({layer: map.layers[i]});
}
for (var i=this.maps.length-1; i>=0; --i) {
this.removeMap(this.maps[i]);
}
if (map.events) {
map.events.un({
move: this.move,
zoomend: this.zoomEnd,
addlayer: this.addLayer,
removelayer: this.removeLayer,
scope: this
});
}
this.map = null;
this.maps = null;
this.tileQueue = null;
if (this.tileCache !== OpenLayers.TileManager.prototype.tileCache) {
this.tileCache = null;
this.tileCacheIndex = null;
}
this.tileQueueId = null;
this.tileCache = 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;
});

View File

@@ -6,48 +6,48 @@
function test_initialize(t) {
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');
map.addLayer(layer);
var tileManager = new OpenLayers.TileManager({map: map});
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);
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.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();
t.eq(tileManager.tileQueue.length, 0, "Tiles unqueued when map is destroyed");
tileManager.destroy();
t.eq(tileManager.tileQueue[map.id], undefined, "Tile queue removed when map was destroyed");
}
function test_destroy(t) {
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');
map.addLayer(layer);
map.setCenter([16, 48], 9);
var numTileListeners = (layer.grid[0][0].events.listeners.reload || []).length;
var numLayerListeners = (layer.events.listeners.retile || []).length;
var numMapListeners = (map.events.listeners.removelayer || []).length;
var tileManager = new OpenLayers.TileManager({map: map});
var numTileListeners = layer.grid[0][0].events.listeners.reload.length;
var numLayerListeners = layer.events.listeners.retile.length;
var numMapListeners = map.events.listeners.preremovelayer.length;
tileManager.destroy();
t.eq(layer.grid[0][0].events.listeners.reload.length, numTileListeners, "no listener on tile after destroy");
t.eq(layer.events.listeners.retile.length, numLayerListeners, "no listeners on layer after destroy");
t.eq(map.events.listeners.removelayer.length, numMapListeners, "no listeners on map 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 - 1, "no listeners on layer after destroy");
t.eq(map.events.listeners.preremovelayer.length, numMapListeners - 1, "no listeners on map after destroy");
map.destroy();
}
function test_manageTileCache(t) {
t.plan(9);
var map = new OpenLayers.Map('map');
var tileManager = new OpenLayers.TileManager({
map: map,
cacheSize: 12
});
var map = new OpenLayers.Map('map', {tileManager: tileManager});
layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
map.addLayer(layer);
map.setCenter([16, 48], 9);
@@ -79,19 +79,17 @@
function test_queueTileDraw(t) {
t.plan(3);
var map = new OpenLayers.Map('map');
var tileManager = new OpenLayers.TileManager({
map: map
});
var tileManager = new OpenLayers.TileManager();
var map = new OpenLayers.Map('map', {tileManager: tileManager});
layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
map.addLayer(layer);
map.setCenter([0, 0], 3);
var queued = tileManager.tileQueue.length;
t.ok(tileManager.tileQueue.length, "Tiles queued for drawing");
var queued = tileManager.tileQueue[map.id].length;
t.ok(tileManager.tileQueue[map.id].length, "Tiles queued for drawing");
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.eq(tileManager.tileQueue.length, 0, "Tiles from queue processed");
t.eq(tileManager.tileQueue[map.id].length, 0, "Tiles from queue processed");
map.destroy();
});
}
@@ -100,10 +98,8 @@
t.plan(3);
var map = new OpenLayers.Map('map');
var tileManager = new OpenLayers.TileManager({
map: map
});
var tileManager = new OpenLayers.TileManager();
var map = new OpenLayers.Map('map', {tileManager: tileManager});
layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
layer.destroy = function() {}; //we're going to do funky things with the grid
layer.applyBackBuffer = function() {}; // backbuffering isn't under test here
@@ -111,11 +107,11 @@
map.setCenter([-10, 0], 5);
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]);
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.eq(tileManager.tileQueue.length, 0, "tiles loaded after moveDelay");
t.eq(tileManager.tileQueue[map.id].length, 0, "tiles loaded after moveDelay");
});
}
</script>