Image.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  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. * @requires OpenLayers/Tile.js
  7. * @requires OpenLayers/Animation.js
  8. * @requires OpenLayers/Util.js
  9. */
  10. /**
  11. * Class: OpenLayers.Tile.Image
  12. * Instances of OpenLayers.Tile.Image are used to manage the image tiles
  13. * used by various layers. Create a new image tile with the
  14. * <OpenLayers.Tile.Image> constructor.
  15. *
  16. * Inherits from:
  17. * - <OpenLayers.Tile>
  18. */
  19. OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
  20. /**
  21. * APIProperty: events
  22. * {<OpenLayers.Events>} An events object that handles all
  23. * events on the tile.
  24. *
  25. * Register a listener for a particular event with the following syntax:
  26. * (code)
  27. * tile.events.register(type, obj, listener);
  28. * (end)
  29. *
  30. * Supported event types (in addition to the <OpenLayers.Tile> events):
  31. * beforeload - Triggered before an image is prepared for loading, when the
  32. * url for the image is known already. Listeners may call <setImage> on
  33. * the tile instance. If they do so, that image will be used and no new
  34. * one will be created.
  35. */
  36. /**
  37. * APIProperty: url
  38. * {String} The URL of the image being requested. No default. Filled in by
  39. * layer.getURL() function. May be modified by loadstart listeners.
  40. */
  41. url: null,
  42. /**
  43. * Property: imgDiv
  44. * {HTMLImageElement} The image for this tile.
  45. */
  46. imgDiv: null,
  47. /**
  48. * Property: frame
  49. * {DOMElement} The image element is appended to the frame. Any gutter on
  50. * the image will be hidden behind the frame. If no gutter is set,
  51. * this will be null.
  52. */
  53. frame: null,
  54. /**
  55. * Property: imageReloadAttempts
  56. * {Integer} Attempts to load the image.
  57. */
  58. imageReloadAttempts: null,
  59. /**
  60. * Property: layerAlphaHack
  61. * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
  62. */
  63. layerAlphaHack: null,
  64. /**
  65. * Property: asyncRequestId
  66. * {Integer} ID of an request to see if request is still valid. This is a
  67. * number which increments by 1 for each asynchronous request.
  68. */
  69. asyncRequestId: null,
  70. /**
  71. * APIProperty: maxGetUrlLength
  72. * {Number} If set, requests that would result in GET urls with more
  73. * characters than the number provided will be made using form-encoded
  74. * HTTP POST. It is good practice to avoid urls that are longer than 2048
  75. * characters.
  76. *
  77. * Caution:
  78. * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
  79. * Opera versions do not fully support this option. On all browsers,
  80. * transition effects are not supported if POST requests are used.
  81. */
  82. maxGetUrlLength: null,
  83. /**
  84. * Property: canvasContext
  85. * {CanvasRenderingContext2D} A canvas context associated with
  86. * the tile image.
  87. */
  88. canvasContext: null,
  89. /**
  90. * APIProperty: crossOriginKeyword
  91. * The value of the crossorigin keyword to use when loading images. This is
  92. * only relevant when using <getCanvasContext> for tiles from remote
  93. * origins and should be set to either 'anonymous' or 'use-credentials'
  94. * for servers that send Access-Control-Allow-Origin headers with their
  95. * tiles.
  96. */
  97. crossOriginKeyword: null,
  98. /** TBD 3.0 - reorder the parameters to the init function to remove
  99. * URL. the getUrl() function on the layer gets called on
  100. * each draw(), so no need to specify it here.
  101. */
  102. /**
  103. * Constructor: OpenLayers.Tile.Image
  104. * Constructor for a new <OpenLayers.Tile.Image> instance.
  105. *
  106. * Parameters:
  107. * layer - {<OpenLayers.Layer>} layer that the tile will go in.
  108. * position - {<OpenLayers.Pixel>}
  109. * bounds - {<OpenLayers.Bounds>}
  110. * url - {<String>} Deprecated. Remove me in 3.0.
  111. * size - {<OpenLayers.Size>}
  112. * options - {Object}
  113. */
  114. initialize: function(layer, position, bounds, url, size, options) {
  115. OpenLayers.Tile.prototype.initialize.apply(this, arguments);
  116. this.url = url; //deprecated remove me
  117. this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
  118. if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
  119. // only create frame if it's needed
  120. this.frame = document.createElement("div");
  121. this.frame.style.position = "absolute";
  122. this.frame.style.overflow = "hidden";
  123. }
  124. if (this.maxGetUrlLength != null) {
  125. OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
  126. }
  127. },
  128. /**
  129. * APIMethod: destroy
  130. * nullify references to prevent circular references and memory leaks
  131. */
  132. destroy: function() {
  133. if (this.imgDiv) {
  134. this.clear();
  135. this.imgDiv = null;
  136. this.frame = null;
  137. }
  138. // don't handle async requests any more
  139. this.asyncRequestId = null;
  140. OpenLayers.Tile.prototype.destroy.apply(this, arguments);
  141. },
  142. /**
  143. * Method: draw
  144. * Check that a tile should be drawn, and draw it.
  145. *
  146. * Returns:
  147. * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
  148. * false.
  149. */
  150. draw: function() {
  151. var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
  152. if (shouldDraw) {
  153. // The layer's reproject option is deprecated.
  154. if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
  155. // getBoundsFromBaseLayer is defined in deprecated.js.
  156. this.bounds = this.getBoundsFromBaseLayer(this.position);
  157. }
  158. if (this.isLoading) {
  159. //if we're already loading, send 'reload' instead of 'loadstart'.
  160. this._loadEvent = "reload";
  161. } else {
  162. this.isLoading = true;
  163. this._loadEvent = "loadstart";
  164. }
  165. this.renderTile();
  166. this.positionTile();
  167. } else if (shouldDraw === false) {
  168. this.unload();
  169. }
  170. return shouldDraw;
  171. },
  172. /**
  173. * Method: renderTile
  174. * Internal function to actually initialize the image tile,
  175. * position it correctly, and set its url.
  176. */
  177. renderTile: function() {
  178. if (this.layer.async) {
  179. // Asynchronous image requests call the asynchronous getURL method
  180. // on the layer to fetch an image that covers 'this.bounds'.
  181. var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
  182. this.layer.getURLasync(this.bounds, function(url) {
  183. if (id == this.asyncRequestId) {
  184. this.url = url;
  185. this.initImage();
  186. }
  187. }, this);
  188. } else {
  189. // synchronous image requests get the url immediately.
  190. this.url = this.layer.getURL(this.bounds);
  191. this.initImage();
  192. }
  193. },
  194. /**
  195. * Method: positionTile
  196. * Using the properties currenty set on the layer, position the tile correctly.
  197. * This method is used both by the async and non-async versions of the Tile.Image
  198. * code.
  199. */
  200. positionTile: function() {
  201. var style = this.getTile().style,
  202. size = this.frame ? this.size :
  203. this.layer.getImageSize(this.bounds),
  204. ratio = 1;
  205. if (this.layer instanceof OpenLayers.Layer.Grid) {
  206. ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
  207. }
  208. style.left = this.position.x + "px";
  209. style.top = this.position.y + "px";
  210. style.width = Math.round(ratio * size.w) + "px";
  211. style.height = Math.round(ratio * size.h) + "px";
  212. },
  213. /**
  214. * Method: clear
  215. * Remove the tile from the DOM, clear it of any image related data so that
  216. * it can be reused in a new location.
  217. */
  218. clear: function() {
  219. OpenLayers.Tile.prototype.clear.apply(this, arguments);
  220. var img = this.imgDiv;
  221. if (img) {
  222. var tile = this.getTile();
  223. if (tile.parentNode === this.layer.div) {
  224. this.layer.div.removeChild(tile);
  225. }
  226. this.setImgSrc();
  227. if (this.layerAlphaHack === true) {
  228. img.style.filter = "";
  229. }
  230. OpenLayers.Element.removeClass(img, "olImageLoadError");
  231. }
  232. this.canvasContext = null;
  233. },
  234. /**
  235. * Method: getImage
  236. * Returns or creates and returns the tile image.
  237. */
  238. getImage: function() {
  239. if (!this.imgDiv) {
  240. this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);
  241. var style = this.imgDiv.style;
  242. if (this.frame) {
  243. var left = 0, top = 0;
  244. if (this.layer.gutter) {
  245. left = this.layer.gutter / this.layer.tileSize.w * 100;
  246. top = this.layer.gutter / this.layer.tileSize.h * 100;
  247. }
  248. style.left = -left + "%";
  249. style.top = -top + "%";
  250. style.width = (2 * left + 100) + "%";
  251. style.height = (2 * top + 100) + "%";
  252. }
  253. style.visibility = "hidden";
  254. style.opacity = 0;
  255. if (this.layer.opacity < 1) {
  256. style.filter = 'alpha(opacity=' +
  257. (this.layer.opacity * 100) +
  258. ')';
  259. }
  260. style.position = "absolute";
  261. if (this.layerAlphaHack) {
  262. // move the image out of sight
  263. style.paddingTop = style.height;
  264. style.height = "0";
  265. style.width = "100%";
  266. }
  267. if (this.frame) {
  268. this.frame.appendChild(this.imgDiv);
  269. }
  270. }
  271. return this.imgDiv;
  272. },
  273. /**
  274. * APIMethod: setImage
  275. * Sets the image element for this tile. This method should only be called
  276. * from beforeload listeners.
  277. *
  278. * Parameters
  279. * img - {HTMLImageElement} The image to use for this tile.
  280. */
  281. setImage: function(img) {
  282. this.imgDiv = img;
  283. },
  284. /**
  285. * Method: initImage
  286. * Creates the content for the frame on the tile.
  287. */
  288. initImage: function() {
  289. if (!this.url && !this.imgDiv) {
  290. // fast path out - if there is no tile url and no previous image
  291. this.isLoading = false;
  292. return;
  293. }
  294. this.events.triggerEvent('beforeload');
  295. this.layer.div.appendChild(this.getTile());
  296. this.events.triggerEvent(this._loadEvent);
  297. var img = this.getImage();
  298. var src = img.getAttribute('src') || '';
  299. if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
  300. this._loadTimeout = window.setTimeout(
  301. OpenLayers.Function.bind(this.onImageLoad, this), 0
  302. );
  303. } else {
  304. this.stopLoading();
  305. if (this.crossOriginKeyword) {
  306. img.removeAttribute("crossorigin");
  307. }
  308. OpenLayers.Event.observe(img, "load",
  309. OpenLayers.Function.bind(this.onImageLoad, this)
  310. );
  311. OpenLayers.Event.observe(img, "error",
  312. OpenLayers.Function.bind(this.onImageError, this)
  313. );
  314. this.imageReloadAttempts = 0;
  315. this.setImgSrc(this.url);
  316. }
  317. },
  318. /**
  319. * Method: setImgSrc
  320. * Sets the source for the tile image
  321. *
  322. * Parameters:
  323. * url - {String} or undefined to hide the image
  324. */
  325. setImgSrc: function(url) {
  326. var img = this.imgDiv;
  327. if (url) {
  328. img.style.visibility = 'hidden';
  329. img.style.opacity = 0;
  330. // don't set crossOrigin if the url is a data URL
  331. if (this.crossOriginKeyword) {
  332. if (url.substr(0, 5) !== 'data:') {
  333. img.setAttribute("crossorigin", this.crossOriginKeyword);
  334. } else {
  335. img.removeAttribute("crossorigin");
  336. }
  337. }
  338. img.src = url;
  339. } else {
  340. // Remove reference to the image, and leave it to the browser's
  341. // caching and garbage collection.
  342. this.stopLoading();
  343. this.imgDiv = null;
  344. if (img.parentNode) {
  345. img.parentNode.removeChild(img);
  346. }
  347. }
  348. },
  349. /**
  350. * Method: getTile
  351. * Get the tile's markup.
  352. *
  353. * Returns:
  354. * {DOMElement} The tile's markup
  355. */
  356. getTile: function() {
  357. return this.frame ? this.frame : this.getImage();
  358. },
  359. /**
  360. * Method: createBackBuffer
  361. * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
  362. * of the tile's markup, because we want to avoid the reloading of the
  363. * image. So we clone the frame, and steal the image from the tile.
  364. *
  365. * Returns:
  366. * {DOMElement} The markup, or undefined if the tile has no image
  367. * or if it's currently loading.
  368. */
  369. createBackBuffer: function() {
  370. if (!this.imgDiv || this.isLoading) {
  371. return;
  372. }
  373. var backBuffer;
  374. if (this.frame) {
  375. backBuffer = this.frame.cloneNode(false);
  376. backBuffer.appendChild(this.imgDiv);
  377. } else {
  378. backBuffer = this.imgDiv;
  379. }
  380. this.imgDiv = null;
  381. return backBuffer;
  382. },
  383. /**
  384. * Method: onImageLoad
  385. * Handler for the image onload event
  386. */
  387. onImageLoad: function() {
  388. var img = this.imgDiv;
  389. this.stopLoading();
  390. img.style.visibility = 'inherit';
  391. img.style.opacity = this.layer.opacity;
  392. this.isLoading = false;
  393. this.canvasContext = null;
  394. this.events.triggerEvent("loadend");
  395. if (this.layerAlphaHack === true) {
  396. img.style.filter =
  397. "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
  398. img.src + "', sizingMethod='scale')";
  399. }
  400. },
  401. /**
  402. * Method: onImageError
  403. * Handler for the image onerror event
  404. */
  405. onImageError: function() {
  406. var img = this.imgDiv;
  407. if (img.src != null) {
  408. this.imageReloadAttempts++;
  409. if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
  410. this.setImgSrc(this.layer.getURL(this.bounds));
  411. } else {
  412. OpenLayers.Element.addClass(img, "olImageLoadError");
  413. this.events.triggerEvent("loaderror");
  414. this.onImageLoad();
  415. }
  416. }
  417. },
  418. /**
  419. * Method: stopLoading
  420. * Stops a loading sequence so <onImageLoad> won't be executed.
  421. */
  422. stopLoading: function() {
  423. OpenLayers.Event.stopObservingElement(this.imgDiv);
  424. window.clearTimeout(this._loadTimeout);
  425. delete this._loadTimeout;
  426. },
  427. /**
  428. * APIMethod: getCanvasContext
  429. * Returns a canvas context associated with the tile image (with
  430. * the image drawn on it).
  431. * Returns undefined if the browser does not support canvas, if
  432. * the tile has no image or if it's currently loading.
  433. *
  434. * The function returns a canvas context instance but the
  435. * underlying canvas is still available in the 'canvas' property:
  436. * (code)
  437. * var context = tile.getCanvasContext();
  438. * if (context) {
  439. * var data = context.canvas.toDataURL('image/jpeg');
  440. * }
  441. * (end)
  442. *
  443. * Returns:
  444. * {Boolean}
  445. */
  446. getCanvasContext: function() {
  447. if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
  448. if (!this.canvasContext) {
  449. var canvas = document.createElement("canvas");
  450. canvas.width = this.size.w;
  451. canvas.height = this.size.h;
  452. this.canvasContext = canvas.getContext("2d");
  453. this.canvasContext.drawImage(this.imgDiv, 0, 0);
  454. }
  455. return this.canvasContext;
  456. }
  457. },
  458. CLASS_NAME: "OpenLayers.Tile.Image"
  459. });
  460. /**
  461. * Constant: OpenLayers.Tile.Image.IMAGE
  462. * {HTMLImageElement} The image for a tile.
  463. */
  464. OpenLayers.Tile.Image.IMAGE = (function() {
  465. var img = new Image();
  466. img.className = "olTileImage";
  467. // avoid image gallery menu in IE6
  468. img.galleryImg = "no";
  469. return img;
  470. }());