1
|
/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
|
2
|
* full list of contributors). Published under the 2-clause BSD license.
|
3
|
* See license.txt in the OpenLayers distribution or repository for the
|
4
|
* full text of the license. */
|
5
|
|
6
|
|
7
|
/**
|
8
|
* @requires OpenLayers/Util.js
|
9
|
* @requires OpenLayers/BaseTypes.js
|
10
|
* @requires OpenLayers/BaseTypes/Element.js
|
11
|
* @requires OpenLayers/Layer/Grid.js
|
12
|
* @requires OpenLayers/Tile/Image.js
|
13
|
*/
|
14
|
|
15
|
/**
|
16
|
* Class: OpenLayers.TileManager
|
17
|
* Provides queueing of image requests and caching of image elements.
|
18
|
*
|
19
|
* Queueing avoids unnecessary image requests while changing zoom levels
|
20
|
* quickly, and helps improve dragging performance on mobile devices that show
|
21
|
* a lag in dragging when loading of new images starts. <zoomDelay> and
|
22
|
* <moveDelay> are the configuration options to control this behavior.
|
23
|
*
|
24
|
* Caching avoids setting the src on image elements for images that have already
|
25
|
* been used. Several maps can share a TileManager instance, in which case each
|
26
|
* map gets its own tile queue, but all maps share the same tile cache.
|
27
|
*/
|
28
|
OpenLayers.TileManager = OpenLayers.Class({
|
29
|
|
30
|
/**
|
31
|
* APIProperty: cacheSize
|
32
|
* {Number} Number of image elements to keep referenced in this instance's
|
33
|
* cache for fast reuse. Default is 256.
|
34
|
*/
|
35
|
cacheSize: 256,
|
36
|
|
37
|
/**
|
38
|
* APIProperty: tilesPerFrame
|
39
|
* {Number} Number of queued tiles to load per frame (see <frameDelay>).
|
40
|
* Default is 2.
|
41
|
*/
|
42
|
tilesPerFrame: 2,
|
43
|
|
44
|
/**
|
45
|
* APIProperty: frameDelay
|
46
|
* {Number} Delay between tile loading frames (see <tilesPerFrame>) in
|
47
|
* milliseconds. Default is 16.
|
48
|
*/
|
49
|
frameDelay: 16,
|
50
|
|
51
|
/**
|
52
|
* APIProperty: moveDelay
|
53
|
* {Number} Delay in milliseconds after a map's move event before loading
|
54
|
* tiles. Default is 100.
|
55
|
*/
|
56
|
moveDelay: 100,
|
57
|
|
58
|
/**
|
59
|
* APIProperty: zoomDelay
|
60
|
* {Number} Delay in milliseconds after a map's zoomend event before loading
|
61
|
* tiles. Default is 200.
|
62
|
*/
|
63
|
zoomDelay: 200,
|
64
|
|
65
|
/**
|
66
|
* Property: maps
|
67
|
* {Array(<OpenLayers.Map>)} The maps to manage tiles on.
|
68
|
*/
|
69
|
maps: null,
|
70
|
|
71
|
/**
|
72
|
* Property: tileQueueId
|
73
|
* {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id.
|
74
|
*/
|
75
|
tileQueueId: null,
|
76
|
|
77
|
/**
|
78
|
* Property: tileQueue
|
79
|
* {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by
|
80
|
* map id.
|
81
|
*/
|
82
|
tileQueue: null,
|
83
|
|
84
|
/**
|
85
|
* Property: tileCache
|
86
|
* {Object} Cached image elements, keyed by URL.
|
87
|
*/
|
88
|
tileCache: null,
|
89
|
|
90
|
/**
|
91
|
* Property: tileCacheIndex
|
92
|
* {Array(String)} URLs of cached tiles. First entry is the least recently
|
93
|
* used.
|
94
|
*/
|
95
|
tileCacheIndex: null,
|
96
|
|
97
|
/**
|
98
|
* Constructor: OpenLayers.TileManager
|
99
|
* Constructor for a new <OpenLayers.TileManager> instance.
|
100
|
*
|
101
|
* Parameters:
|
102
|
* options - {Object} Configuration for this instance.
|
103
|
*/
|
104
|
initialize: function(options) {
|
105
|
OpenLayers.Util.extend(this, options);
|
106
|
this.maps = [];
|
107
|
this.tileQueueId = {};
|
108
|
this.tileQueue = {};
|
109
|
this.tileCache = {};
|
110
|
this.tileCacheIndex = [];
|
111
|
},
|
112
|
|
113
|
/**
|
114
|
* Method: addMap
|
115
|
* Binds this instance to a map
|
116
|
*
|
117
|
* Parameters:
|
118
|
* map - {<OpenLayers.Map>}
|
119
|
*/
|
120
|
addMap: function(map) {
|
121
|
if (this._destroyed || !OpenLayers.Layer.Grid) {
|
122
|
return;
|
123
|
}
|
124
|
this.maps.push(map);
|
125
|
this.tileQueue[map.id] = [];
|
126
|
for (var i=0, ii=map.layers.length; i<ii; ++i) {
|
127
|
this.addLayer({layer: map.layers[i]});
|
128
|
}
|
129
|
map.events.on({
|
130
|
move: this.move,
|
131
|
zoomend: this.zoomEnd,
|
132
|
changelayer: this.changeLayer,
|
133
|
addlayer: this.addLayer,
|
134
|
preremovelayer: this.removeLayer,
|
135
|
scope: this
|
136
|
});
|
137
|
},
|
138
|
|
139
|
/**
|
140
|
* Method: removeMap
|
141
|
* Unbinds this instance from a map
|
142
|
*
|
143
|
* Parameters:
|
144
|
* map - {<OpenLayers.Map>}
|
145
|
*/
|
146
|
removeMap: function(map) {
|
147
|
if (this._destroyed || !OpenLayers.Layer.Grid) {
|
148
|
return;
|
149
|
}
|
150
|
window.clearTimeout(this.tileQueueId[map.id]);
|
151
|
if (map.layers) {
|
152
|
for (var i=0, ii=map.layers.length; i<ii; ++i) {
|
153
|
this.removeLayer({layer: map.layers[i]});
|
154
|
}
|
155
|
}
|
156
|
if (map.events) {
|
157
|
map.events.un({
|
158
|
move: this.move,
|
159
|
zoomend: this.zoomEnd,
|
160
|
changelayer: this.changeLayer,
|
161
|
addlayer: this.addLayer,
|
162
|
preremovelayer: this.removeLayer,
|
163
|
scope: this
|
164
|
});
|
165
|
}
|
166
|
delete this.tileQueue[map.id];
|
167
|
delete this.tileQueueId[map.id];
|
168
|
OpenLayers.Util.removeItem(this.maps, map);
|
169
|
},
|
170
|
|
171
|
/**
|
172
|
* Method: move
|
173
|
* Handles the map's move event
|
174
|
*
|
175
|
* Parameters:
|
176
|
* evt - {Object} Listener argument
|
177
|
*/
|
178
|
move: function(evt) {
|
179
|
this.updateTimeout(evt.object, this.moveDelay, true);
|
180
|
},
|
181
|
|
182
|
/**
|
183
|
* Method: zoomEnd
|
184
|
* Handles the map's zoomEnd event
|
185
|
*
|
186
|
* Parameters:
|
187
|
* evt - {Object} Listener argument
|
188
|
*/
|
189
|
zoomEnd: function(evt) {
|
190
|
this.updateTimeout(evt.object, this.zoomDelay);
|
191
|
},
|
192
|
|
193
|
/**
|
194
|
* Method: changeLayer
|
195
|
* Handles the map's changeLayer event
|
196
|
*
|
197
|
* Parameters:
|
198
|
* evt - {Object} Listener argument
|
199
|
*/
|
200
|
changeLayer: function(evt) {
|
201
|
if (evt.property === 'visibility' || evt.property === 'params') {
|
202
|
this.updateTimeout(evt.object, 0);
|
203
|
}
|
204
|
},
|
205
|
|
206
|
/**
|
207
|
* Method: addLayer
|
208
|
* Handles the map's addlayer event
|
209
|
*
|
210
|
* Parameters:
|
211
|
* evt - {Object} The listener argument
|
212
|
*/
|
213
|
addLayer: function(evt) {
|
214
|
var layer = evt.layer;
|
215
|
if (layer instanceof OpenLayers.Layer.Grid) {
|
216
|
layer.events.on({
|
217
|
addtile: this.addTile,
|
218
|
retile: this.clearTileQueue,
|
219
|
scope: this
|
220
|
});
|
221
|
var i, j, tile;
|
222
|
for (i=layer.grid.length-1; i>=0; --i) {
|
223
|
for (j=layer.grid[i].length-1; j>=0; --j) {
|
224
|
tile = layer.grid[i][j];
|
225
|
this.addTile({tile: tile});
|
226
|
if (tile.url && !tile.imgDiv) {
|
227
|
this.manageTileCache({object: tile});
|
228
|
}
|
229
|
}
|
230
|
}
|
231
|
}
|
232
|
},
|
233
|
|
234
|
/**
|
235
|
* Method: removeLayer
|
236
|
* Handles the map's preremovelayer event
|
237
|
*
|
238
|
* Parameters:
|
239
|
* evt - {Object} The listener argument
|
240
|
*/
|
241
|
removeLayer: function(evt) {
|
242
|
var layer = evt.layer;
|
243
|
if (layer instanceof OpenLayers.Layer.Grid) {
|
244
|
this.clearTileQueue({object: layer});
|
245
|
if (layer.events) {
|
246
|
layer.events.un({
|
247
|
addtile: this.addTile,
|
248
|
retile: this.clearTileQueue,
|
249
|
scope: this
|
250
|
});
|
251
|
}
|
252
|
if (layer.grid) {
|
253
|
var i, j, tile;
|
254
|
for (i=layer.grid.length-1; i>=0; --i) {
|
255
|
for (j=layer.grid[i].length-1; j>=0; --j) {
|
256
|
tile = layer.grid[i][j];
|
257
|
this.unloadTile({object: tile});
|
258
|
}
|
259
|
}
|
260
|
}
|
261
|
}
|
262
|
},
|
263
|
|
264
|
/**
|
265
|
* Method: updateTimeout
|
266
|
* Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop,
|
267
|
* and schedules more queue processing after <frameDelay> if there are still
|
268
|
* tiles in the queue.
|
269
|
*
|
270
|
* Parameters:
|
271
|
* map - {<OpenLayers.Map>} The map to update the timeout for
|
272
|
* delay - {Number} The delay to apply
|
273
|
* nice - {Boolean} If true, the timeout function will only be created if
|
274
|
* the tilequeue is not empty. This is used by the move handler to
|
275
|
* avoid impacts on dragging performance. For other events, the tile
|
276
|
* queue may not be populated yet, so we need to set the timer
|
277
|
* regardless of the queue size.
|
278
|
*/
|
279
|
updateTimeout: function(map, delay, nice) {
|
280
|
window.clearTimeout(this.tileQueueId[map.id]);
|
281
|
var tileQueue = this.tileQueue[map.id];
|
282
|
if (!nice || tileQueue.length) {
|
283
|
this.tileQueueId[map.id] = window.setTimeout(
|
284
|
OpenLayers.Function.bind(function() {
|
285
|
this.drawTilesFromQueue(map);
|
286
|
if (tileQueue.length) {
|
287
|
this.updateTimeout(map, this.frameDelay);
|
288
|
}
|
289
|
}, this), delay
|
290
|
);
|
291
|
}
|
292
|
},
|
293
|
|
294
|
/**
|
295
|
* Method: addTile
|
296
|
* Listener for the layer's addtile event
|
297
|
*
|
298
|
* Parameters:
|
299
|
* evt - {Object} The listener argument
|
300
|
*/
|
301
|
addTile: function(evt) {
|
302
|
if (evt.tile instanceof OpenLayers.Tile.Image) {
|
303
|
evt.tile.events.on({
|
304
|
beforedraw: this.queueTileDraw,
|
305
|
beforeload: this.manageTileCache,
|
306
|
loadend: this.addToCache,
|
307
|
unload: this.unloadTile,
|
308
|
scope: this
|
309
|
});
|
310
|
} else {
|
311
|
// Layer has the wrong tile type, so don't handle it any longer
|
312
|
this.removeLayer({layer: evt.tile.layer});
|
313
|
}
|
314
|
},
|
315
|
|
316
|
/**
|
317
|
* Method: unloadTile
|
318
|
* Listener for the tile's unload event
|
319
|
*
|
320
|
* Parameters:
|
321
|
* evt - {Object} The listener argument
|
322
|
*/
|
323
|
unloadTile: function(evt) {
|
324
|
var tile = evt.object;
|
325
|
tile.events.un({
|
326
|
beforedraw: this.queueTileDraw,
|
327
|
beforeload: this.manageTileCache,
|
328
|
loadend: this.addToCache,
|
329
|
unload: this.unloadTile,
|
330
|
scope: this
|
331
|
});
|
332
|
OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile);
|
333
|
},
|
334
|
|
335
|
/**
|
336
|
* Method: queueTileDraw
|
337
|
* Adds a tile to the queue that will draw it.
|
338
|
*
|
339
|
* Parameters:
|
340
|
* evt - {Object} Listener argument of the tile's beforedraw event
|
341
|
*/
|
342
|
queueTileDraw: function(evt) {
|
343
|
var tile = evt.object;
|
344
|
var queued = false;
|
345
|
var layer = tile.layer;
|
346
|
var url = layer.getURL(tile.bounds);
|
347
|
var img = this.tileCache[url];
|
348
|
if (img && img.className !== 'olTileImage') {
|
349
|
// cached image no longer valid, e.g. because we're olTileReplacing
|
350
|
delete this.tileCache[url];
|
351
|
OpenLayers.Util.removeItem(this.tileCacheIndex, url);
|
352
|
img = null;
|
353
|
}
|
354
|
// queue only if image with same url not cached already
|
355
|
if (layer.url && (layer.async || !img)) {
|
356
|
// add to queue only if not in queue already
|
357
|
var tileQueue = this.tileQueue[layer.map.id];
|
358
|
if (!~OpenLayers.Util.indexOf(tileQueue, tile)) {
|
359
|
tileQueue.push(tile);
|
360
|
}
|
361
|
queued = true;
|
362
|
}
|
363
|
return !queued;
|
364
|
},
|
365
|
|
366
|
/**
|
367
|
* Method: drawTilesFromQueue
|
368
|
* Draws tiles from the tileQueue, and unqueues the tiles
|
369
|
*/
|
370
|
drawTilesFromQueue: function(map) {
|
371
|
var tileQueue = this.tileQueue[map.id];
|
372
|
var limit = this.tilesPerFrame;
|
373
|
var animating = map.zoomTween && map.zoomTween.playing;
|
374
|
while (!animating && tileQueue.length && limit) {
|
375
|
tileQueue.shift().draw(true);
|
376
|
--limit;
|
377
|
}
|
378
|
},
|
379
|
|
380
|
/**
|
381
|
* Method: manageTileCache
|
382
|
* Adds, updates, removes and fetches cache entries.
|
383
|
*
|
384
|
* Parameters:
|
385
|
* evt - {Object} Listener argument of the tile's beforeload event
|
386
|
*/
|
387
|
manageTileCache: function(evt) {
|
388
|
var tile = evt.object;
|
389
|
var img = this.tileCache[tile.url];
|
390
|
if (img) {
|
391
|
// if image is on its layer's backbuffer, remove it from backbuffer
|
392
|
if (img.parentNode &&
|
393
|
OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer')) {
|
394
|
img.parentNode.removeChild(img);
|
395
|
img.id = null;
|
396
|
}
|
397
|
// only use image from cache if it is not on a layer already
|
398
|
if (!img.parentNode) {
|
399
|
img.style.visibility = 'hidden';
|
400
|
img.style.opacity = 0;
|
401
|
tile.setImage(img);
|
402
|
// LRU - move tile to the end of the array to mark it as the most
|
403
|
// recently used
|
404
|
OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url);
|
405
|
this.tileCacheIndex.push(tile.url);
|
406
|
}
|
407
|
}
|
408
|
},
|
409
|
|
410
|
/**
|
411
|
* Method: addToCache
|
412
|
*
|
413
|
* Parameters:
|
414
|
* evt - {Object} Listener argument for the tile's loadend event
|
415
|
*/
|
416
|
addToCache: function(evt) {
|
417
|
var tile = evt.object;
|
418
|
if (!this.tileCache[tile.url]) {
|
419
|
if (!OpenLayers.Element.hasClass(tile.imgDiv, 'olImageLoadError')) {
|
420
|
if (this.tileCacheIndex.length >= this.cacheSize) {
|
421
|
delete this.tileCache[this.tileCacheIndex[0]];
|
422
|
this.tileCacheIndex.shift();
|
423
|
}
|
424
|
this.tileCache[tile.url] = tile.imgDiv;
|
425
|
this.tileCacheIndex.push(tile.url);
|
426
|
}
|
427
|
}
|
428
|
},
|
429
|
|
430
|
/**
|
431
|
* Method: clearTileQueue
|
432
|
* Clears the tile queue from tiles of a specific layer
|
433
|
*
|
434
|
* Parameters:
|
435
|
* evt - {Object} Listener argument of the layer's retile event
|
436
|
*/
|
437
|
clearTileQueue: function(evt) {
|
438
|
var layer = evt.object;
|
439
|
var tileQueue = this.tileQueue[layer.map.id];
|
440
|
for (var i=tileQueue.length-1; i>=0; --i) {
|
441
|
if (tileQueue[i].layer === layer) {
|
442
|
tileQueue.splice(i, 1);
|
443
|
}
|
444
|
}
|
445
|
},
|
446
|
|
447
|
/**
|
448
|
* Method: destroy
|
449
|
*/
|
450
|
destroy: function() {
|
451
|
for (var i=this.maps.length-1; i>=0; --i) {
|
452
|
this.removeMap(this.maps[i]);
|
453
|
}
|
454
|
this.maps = null;
|
455
|
this.tileQueue = null;
|
456
|
this.tileQueueId = null;
|
457
|
this.tileCache = null;
|
458
|
this.tileCacheIndex = null;
|
459
|
this._destroyed = true;
|
460
|
}
|
461
|
|
462
|
});
|