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
|
|