Project

General

Profile

Download (16.4 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/Tile.js
9
 * @requires OpenLayers/Animation.js
10
 * @requires OpenLayers/Util.js
11
 */
12

    
13
/**
14
 * Class: OpenLayers.Tile.Image
15
 * Instances of OpenLayers.Tile.Image are used to manage the image tiles
16
 * used by various layers.  Create a new image tile with the
17
 * <OpenLayers.Tile.Image> constructor.
18
 *
19
 * Inherits from:
20
 *  - <OpenLayers.Tile>
21
 */
22
OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
23

    
24
    /**
25
     * APIProperty: events
26
     * {<OpenLayers.Events>} An events object that handles all 
27
     *     events on the tile.
28
     *
29
     * Register a listener for a particular event with the following syntax:
30
     * (code)
31
     * tile.events.register(type, obj, listener);
32
     * (end)
33
     *
34
     * Supported event types (in addition to the <OpenLayers.Tile> events):
35
     * beforeload - Triggered before an image is prepared for loading, when the
36
     *     url for the image is known already. Listeners may call <setImage> on
37
     *     the tile instance. If they do so, that image will be used and no new
38
     *     one will be created.
39
     */
40

    
41
    /** 
42
     * APIProperty: url
43
     * {String} The URL of the image being requested. No default. Filled in by
44
     * layer.getURL() function. May be modified by loadstart listeners.
45
     */
46
    url: null,
47
    
48
    /** 
49
     * Property: imgDiv
50
     * {HTMLImageElement} The image for this tile.
51
     */
52
    imgDiv: null,
53
    
54
    /**
55
     * Property: frame
56
     * {DOMElement} The image element is appended to the frame.  Any gutter on
57
     * the image will be hidden behind the frame. If no gutter is set,
58
     * this will be null.
59
     */ 
60
    frame: null, 
61

    
62
    /** 
63
     * Property: imageReloadAttempts
64
     * {Integer} Attempts to load the image.
65
     */
66
    imageReloadAttempts: null,
67
    
68
    /**
69
     * Property: layerAlphaHack
70
     * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
71
     */
72
    layerAlphaHack: null,
73
    
74
    /**
75
     * Property: asyncRequestId
76
     * {Integer} ID of an request to see if request is still valid. This is a
77
     * number which increments by 1 for each asynchronous request.
78
     */
79
    asyncRequestId: null,
80
    
81
    /**
82
     * APIProperty: maxGetUrlLength
83
     * {Number} If set, requests that would result in GET urls with more
84
     * characters than the number provided will be made using form-encoded
85
     * HTTP POST. It is good practice to avoid urls that are longer than 2048
86
     * characters.
87
     *
88
     * Caution:
89
     * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
90
     * Opera versions do not fully support this option. On all browsers,
91
     * transition effects are not supported if POST requests are used.
92
     */
93
    maxGetUrlLength: null,
94

    
95
    /**
96
     * Property: canvasContext
97
     * {CanvasRenderingContext2D} A canvas context associated with
98
     * the tile image.
99
     */
100
    canvasContext: null,
101
    
102
    /**
103
     * APIProperty: crossOriginKeyword
104
     * The value of the crossorigin keyword to use when loading images. This is
105
     * only relevant when using <getCanvasContext> for tiles from remote
106
     * origins and should be set to either 'anonymous' or 'use-credentials'
107
     * for servers that send Access-Control-Allow-Origin headers with their
108
     * tiles.
109
     */
110
    crossOriginKeyword: null,
111

    
112
    /** TBD 3.0 - reorder the parameters to the init function to remove 
113
     *             URL. the getUrl() function on the layer gets called on 
114
     *             each draw(), so no need to specify it here.
115
     */
116

    
117
    /** 
118
     * Constructor: OpenLayers.Tile.Image
119
     * Constructor for a new <OpenLayers.Tile.Image> instance.
120
     * 
121
     * Parameters:
122
     * layer - {<OpenLayers.Layer>} layer that the tile will go in.
123
     * position - {<OpenLayers.Pixel>}
124
     * bounds - {<OpenLayers.Bounds>}
125
     * url - {<String>} Deprecated. Remove me in 3.0.
126
     * size - {<OpenLayers.Size>}
127
     * options - {Object}
128
     */   
129
    initialize: function(layer, position, bounds, url, size, options) {
130
        OpenLayers.Tile.prototype.initialize.apply(this, arguments);
131

    
132
        this.url = url; //deprecated remove me
133
        
134
        this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
135

    
136
        if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
137
            // only create frame if it's needed
138
            this.frame = document.createElement("div");
139
            this.frame.style.position = "absolute";
140
            this.frame.style.overflow = "hidden";
141
        }
142
        if (this.maxGetUrlLength != null) {
143
            OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
144
        }
145
    },
146
    
147
    /** 
148
     * APIMethod: destroy
149
     * nullify references to prevent circular references and memory leaks
150
     */
151
    destroy: function() {
152
        if (this.imgDiv)  {
153
            this.clear();
154
            this.imgDiv = null;
155
            this.frame = null;
156
        }
157
        // don't handle async requests any more
158
        this.asyncRequestId = null;
159
        OpenLayers.Tile.prototype.destroy.apply(this, arguments);
160
    },
161
    
162
    /**
163
     * Method: draw
164
     * Check that a tile should be drawn, and draw it.
165
     * 
166
     * Returns:
167
     * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
168
     *     false.
169
     */
170
    draw: function() {
171
        var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
172
        if (shouldDraw) {
173
            // The layer's reproject option is deprecated.
174
            if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
175
                // getBoundsFromBaseLayer is defined in deprecated.js.
176
                this.bounds = this.getBoundsFromBaseLayer(this.position);
177
            }
178
            if (this.isLoading) {
179
                //if we're already loading, send 'reload' instead of 'loadstart'.
180
                this._loadEvent = "reload";
181
            } else {
182
                this.isLoading = true;
183
                this._loadEvent = "loadstart";
184
            }
185
            this.renderTile();
186
            this.positionTile();
187
        } else if (shouldDraw === false) {
188
            this.unload();
189
        }
190
        return shouldDraw;
191
    },
192
    
193
    /**
194
     * Method: renderTile
195
     * Internal function to actually initialize the image tile,
196
     *     position it correctly, and set its url.
197
     */
198
    renderTile: function() {
199
        if (this.layer.async) {
200
            // Asynchronous image requests call the asynchronous getURL method
201
            // on the layer to fetch an image that covers 'this.bounds'.
202
            var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
203
            this.layer.getURLasync(this.bounds, function(url) {
204
                if (id == this.asyncRequestId) {
205
                    this.url = url;
206
                    this.initImage();
207
                }
208
            }, this);
209
        } else {
210
            // synchronous image requests get the url immediately.
211
            this.url = this.layer.getURL(this.bounds);
212
            this.initImage();
213
        }
214
    },
215

    
216
    /**
217
     * Method: positionTile
218
     * Using the properties currenty set on the layer, position the tile correctly.
219
     * This method is used both by the async and non-async versions of the Tile.Image
220
     * code.
221
     */
222
    positionTile: function() {
223
        var style = this.getTile().style,
224
            size = this.frame ? this.size :
225
                this.layer.getImageSize(this.bounds),
226
            ratio = 1;
227
        if (this.layer instanceof OpenLayers.Layer.Grid) {
228
            ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
229
        }
230
        style.left = this.position.x + "px";
231
        style.top = this.position.y + "px";
232
        style.width = Math.round(ratio * size.w) + "px";
233
        style.height = Math.round(ratio * size.h) + "px";
234
    },
235

    
236
    /** 
237
     * Method: clear
238
     * Remove the tile from the DOM, clear it of any image related data so that
239
     * it can be reused in a new location.
240
     */
241
    clear: function() {
242
        OpenLayers.Tile.prototype.clear.apply(this, arguments);
243
        var img = this.imgDiv;
244
        if (img) {
245
            var tile = this.getTile();
246
            if (tile.parentNode === this.layer.div) {
247
                this.layer.div.removeChild(tile);
248
            }
249
            this.setImgSrc();
250
            if (this.layerAlphaHack === true) {
251
                img.style.filter = "";
252
            }
253
            OpenLayers.Element.removeClass(img, "olImageLoadError");
254
        }
255
        this.canvasContext = null;
256
    },
257
    
258
    /**
259
     * Method: getImage
260
     * Returns or creates and returns the tile image.
261
     */
262
    getImage: function() {
263
        if (!this.imgDiv) {
264
            this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);
265

    
266
            var style = this.imgDiv.style;
267
            if (this.frame) {
268
                var left = 0, top = 0;
269
                if (this.layer.gutter) {
270
                    left = this.layer.gutter / this.layer.tileSize.w * 100;
271
                    top = this.layer.gutter / this.layer.tileSize.h * 100;
272
                }
273
                style.left = -left + "%";
274
                style.top = -top + "%";
275
                style.width = (2 * left + 100) + "%";
276
                style.height = (2 * top + 100) + "%";
277
            }
278
            style.visibility = "hidden";
279
            style.opacity = 0;
280
            if (this.layer.opacity < 1) {
281
                style.filter = 'alpha(opacity=' +
282
                               (this.layer.opacity * 100) +
283
                               ')';
284
            }
285
            style.position = "absolute";
286
            if (this.layerAlphaHack) {
287
                // move the image out of sight
288
                style.paddingTop = style.height;
289
                style.height = "0";
290
                style.width = "100%";
291
            }
292
            if (this.frame) {
293
                this.frame.appendChild(this.imgDiv);
294
            }
295
        }
296

    
297
        return this.imgDiv;
298
    },
299
    
300
    /**
301
     * APIMethod: setImage
302
     * Sets the image element for this tile. This method should only be called
303
     * from beforeload listeners.
304
     *
305
     * Parameters
306
     * img - {HTMLImageElement} The image to use for this tile.
307
     */
308
    setImage: function(img) {
309
        this.imgDiv = img;
310
    },
311

    
312
    /**
313
     * Method: initImage
314
     * Creates the content for the frame on the tile.
315
     */
316
    initImage: function() {
317
        if (!this.url && !this.imgDiv) {
318
            // fast path out - if there is no tile url and no previous image
319
            this.isLoading = false;
320
            return;
321
        }
322
        this.events.triggerEvent('beforeload');
323
        this.layer.div.appendChild(this.getTile());
324
        this.events.triggerEvent(this._loadEvent);
325
        var img = this.getImage();
326
        var src = img.getAttribute('src') || '';
327
        if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
328
            this._loadTimeout = window.setTimeout(
329
                OpenLayers.Function.bind(this.onImageLoad, this), 0
330
            );
331
        } else {
332
            this.stopLoading();
333
            if (this.crossOriginKeyword) {
334
                img.removeAttribute("crossorigin");
335
            }
336
            OpenLayers.Event.observe(img, "load",
337
                OpenLayers.Function.bind(this.onImageLoad, this)
338
            );
339
            OpenLayers.Event.observe(img, "error",
340
                OpenLayers.Function.bind(this.onImageError, this)
341
            );
342
            this.imageReloadAttempts = 0;
343
            this.setImgSrc(this.url);
344
        }
345
    },
346
    
347
    /**
348
     * Method: setImgSrc
349
     * Sets the source for the tile image
350
     *
351
     * Parameters:
352
     * url - {String} or undefined to hide the image
353
     */
354
    setImgSrc: function(url) {
355
        var img = this.imgDiv;
356
        if (url) {
357
            img.style.visibility = 'hidden';
358
            img.style.opacity = 0;
359
            // don't set crossOrigin if the url is a data URL
360
            if (this.crossOriginKeyword) {
361
                if (url.substr(0, 5) !== 'data:') {
362
                    img.setAttribute("crossorigin", this.crossOriginKeyword);
363
                } else {
364
                    img.removeAttribute("crossorigin");
365
                }
366
            }
367
            img.src = url;
368
        } else {
369
            // Remove reference to the image, and leave it to the browser's
370
            // caching and garbage collection.
371
            this.stopLoading();
372
            this.imgDiv = null;
373
            if (img.parentNode) {
374
                img.parentNode.removeChild(img);
375
            }
376
        }
377
    },
378
    
379
    /**
380
     * Method: getTile
381
     * Get the tile's markup.
382
     *
383
     * Returns:
384
     * {DOMElement} The tile's markup
385
     */
386
    getTile: function() {
387
        return this.frame ? this.frame : this.getImage();
388
    },
389

    
390
    /**
391
     * Method: createBackBuffer
392
     * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
393
     * of the tile's markup, because we want to avoid the reloading of the
394
     * image. So we clone the frame, and steal the image from the tile.
395
     *
396
     * Returns:
397
     * {DOMElement} The markup, or undefined if the tile has no image
398
     * or if it's currently loading.
399
     */
400
    createBackBuffer: function() {
401
        if (!this.imgDiv || this.isLoading) {
402
            return;
403
        }
404
        var backBuffer;
405
        if (this.frame) {
406
            backBuffer = this.frame.cloneNode(false);
407
            backBuffer.appendChild(this.imgDiv);
408
        } else {
409
            backBuffer = this.imgDiv;
410
        }
411
        this.imgDiv = null;
412
        return backBuffer;
413
    },
414

    
415
    /**
416
     * Method: onImageLoad
417
     * Handler for the image onload event
418
     */
419
    onImageLoad: function() {
420
        var img = this.imgDiv;
421
        this.stopLoading();
422
        img.style.visibility = 'inherit';
423
        img.style.opacity = this.layer.opacity;
424
        this.isLoading = false;
425
        this.canvasContext = null;
426
        this.events.triggerEvent("loadend");
427

    
428
        if (this.layerAlphaHack === true) {
429
            img.style.filter =
430
                "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
431
                img.src + "', sizingMethod='scale')";
432
        }
433
    },
434
    
435
    /**
436
     * Method: onImageError
437
     * Handler for the image onerror event
438
     */
439
    onImageError: function() {
440
        var img = this.imgDiv;
441
        if (img.src != null) {
442
            this.imageReloadAttempts++;
443
            if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
444
                this.setImgSrc(this.layer.getURL(this.bounds));
445
            } else {
446
                OpenLayers.Element.addClass(img, "olImageLoadError");
447
                this.events.triggerEvent("loaderror");
448
                this.onImageLoad();
449
            }
450
        }
451
    },
452
    
453
    /**
454
     * Method: stopLoading
455
     * Stops a loading sequence so <onImageLoad> won't be executed.
456
     */
457
    stopLoading: function() {
458
        OpenLayers.Event.stopObservingElement(this.imgDiv);
459
        window.clearTimeout(this._loadTimeout);
460
        delete this._loadTimeout;
461
    },
462

    
463
    /**
464
     * APIMethod: getCanvasContext
465
     * Returns a canvas context associated with the tile image (with
466
     * the image drawn on it).
467
     * Returns undefined if the browser does not support canvas, if
468
     * the tile has no image or if it's currently loading.
469
     *
470
     * The function returns a canvas context instance but the
471
     * underlying canvas is still available in the 'canvas' property:
472
     * (code)
473
     * var context = tile.getCanvasContext();
474
     * if (context) {
475
     *     var data = context.canvas.toDataURL('image/jpeg');
476
     * }
477
     * (end)
478
     *
479
     * Returns:
480
     * {Boolean}
481
     */
482
    getCanvasContext: function() {
483
        if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
484
            if (!this.canvasContext) {
485
                var canvas = document.createElement("canvas");
486
                canvas.width = this.size.w;
487
                canvas.height = this.size.h;
488
                this.canvasContext = canvas.getContext("2d");
489
                this.canvasContext.drawImage(this.imgDiv, 0, 0);
490
            }
491
            return this.canvasContext;
492
        }
493
    },
494

    
495
    CLASS_NAME: "OpenLayers.Tile.Image"
496

    
497
});
498

    
499
/** 
500
 * Constant: OpenLayers.Tile.Image.IMAGE
501
 * {HTMLImageElement} The image for a tile.
502
 */
503
OpenLayers.Tile.Image.IMAGE = (function() {
504
    var img = new Image();
505
    img.className = "olTileImage";
506
    // avoid image gallery menu in IE6
507
    img.galleryImg = "no";
508
    return img;
509
}());
510

    
(1-1/2)