PageRenderTime 60ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/WebContent/js/lib/OpenLayers/TileManager.js

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