Project

General

Profile

Download (13.8 KB) Statistics
| Branch: | Tag: | Revision:
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
});
(31-31/35)