Grid.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. /* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the Clear BSD license.
  3. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Layer/HTTPRequest.js
  7. * @requires OpenLayers/Console.js
  8. */
  9. /**
  10. * Class: OpenLayers.Layer.Grid
  11. * Base class for layers that use a lattice of tiles. Create a new grid
  12. * layer with the <OpenLayers.Layer.Grid> constructor.
  13. *
  14. * Inherits from:
  15. * - <OpenLayers.Layer.HTTPRequest>
  16. */
  17. OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
  18. /**
  19. * APIProperty: tileSize
  20. * {<OpenLayers.Size>}
  21. */
  22. tileSize: null,
  23. /**
  24. * Property: tileOriginCorner
  25. * {String} If the <tileOrigin> property is not provided, the tile origin
  26. * will be derived from the layer's <maxExtent>. The corner of the
  27. * <maxExtent> used is determined by this property. Acceptable values
  28. * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
  29. * (bottom right). Default is "bl".
  30. */
  31. tileOriginCorner: "bl",
  32. /**
  33. * APIProperty: tileOrigin
  34. * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
  35. * If provided, requests for tiles at all resolutions will be aligned
  36. * with this location (no tiles shall overlap this location). If
  37. * not provided, the grid of tiles will be aligned with the layer's
  38. * <maxExtent>. Default is ``null``.
  39. */
  40. tileOrigin: null,
  41. /** APIProperty: tileOptions
  42. * {Object} optional configuration options for <OpenLayers.Tile> instances
  43. * created by this Layer, if supported by the tile class.
  44. */
  45. tileOptions: null,
  46. /**
  47. * Property: grid
  48. * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is
  49. * an array of tiles.
  50. */
  51. grid: null,
  52. /**
  53. * APIProperty: singleTile
  54. * {Boolean} Moves the layer into single-tile mode, meaning that one tile
  55. * will be loaded. The tile's size will be determined by the 'ratio'
  56. * property. When the tile is dragged such that it does not cover the
  57. * entire viewport, it is reloaded.
  58. */
  59. singleTile: false,
  60. /** APIProperty: ratio
  61. * {Float} Used only when in single-tile mode, this specifies the
  62. * ratio of the size of the single tile to the size of the map.
  63. */
  64. ratio: 1.5,
  65. /**
  66. * APIProperty: buffer
  67. * {Integer} Used only when in gridded mode, this specifies the number of
  68. * extra rows and colums of tiles on each side which will
  69. * surround the minimum grid tiles to cover the map.
  70. * For very slow loading layers, a larger value may increase
  71. * performance somewhat when dragging, but will increase bandwidth
  72. * use significantly.
  73. */
  74. buffer: 0,
  75. /**
  76. * APIProperty: numLoadingTiles
  77. * {Integer} How many tiles are still loading?
  78. */
  79. numLoadingTiles: 0,
  80. /**
  81. * APIProperty: tileLoadingDelay
  82. * {Integer} - Number of milliseconds before we shift and load
  83. * tiles. Default is 100.
  84. */
  85. tileLoadingDelay: 100,
  86. /**
  87. * Property: timerId
  88. * {Number} - The id of the tileLoadingDelay timer.
  89. */
  90. timerId: null,
  91. /**
  92. * Constructor: OpenLayers.Layer.Grid
  93. * Create a new grid layer
  94. *
  95. * Parameters:
  96. * name - {String}
  97. * url - {String}
  98. * params - {Object}
  99. * options - {Object} Hashtable of extra options to tag onto the layer
  100. */
  101. initialize: function(name, url, params, options) {
  102. OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
  103. arguments);
  104. //grid layers will trigger 'tileloaded' when each new tile is
  105. // loaded, as a means of progress update to listeners.
  106. // listeners can access 'numLoadingTiles' if they wish to keep track
  107. // of the loading progress
  108. //
  109. this.events.addEventType("tileloaded");
  110. this.grid = [];
  111. this._moveGriddedTiles = OpenLayers.Function.bind(
  112. this.moveGriddedTiles, this
  113. );
  114. },
  115. /**
  116. * Method: removeMap
  117. * Called when the layer is removed from the map.
  118. *
  119. * Parameters:
  120. * map - {<OpenLayers.Map>} The map.
  121. */
  122. removeMap: function(map) {
  123. if(this.timerId != null) {
  124. window.clearTimeout(this.timerId);
  125. this.timerId = null;
  126. }
  127. },
  128. /**
  129. * APIMethod: destroy
  130. * Deconstruct the layer and clear the grid.
  131. */
  132. destroy: function() {
  133. this.clearGrid();
  134. this.grid = null;
  135. this.tileSize = null;
  136. OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments);
  137. },
  138. /**
  139. * Method: clearGrid
  140. * Go through and remove all tiles from the grid, calling
  141. * destroy() on each of them to kill circular references
  142. */
  143. clearGrid:function() {
  144. if (this.grid) {
  145. for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
  146. var row = this.grid[iRow];
  147. for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
  148. var tile = row[iCol];
  149. this.removeTileMonitoringHooks(tile);
  150. tile.destroy();
  151. }
  152. }
  153. this.grid = [];
  154. }
  155. },
  156. /**
  157. * APIMethod: clone
  158. * Create a clone of this layer
  159. *
  160. * Parameters:
  161. * obj - {Object} Is this ever used?
  162. *
  163. * Returns:
  164. * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
  165. */
  166. clone: function (obj) {
  167. if (obj == null) {
  168. obj = new OpenLayers.Layer.Grid(this.name,
  169. this.url,
  170. this.params,
  171. this.getOptions());
  172. }
  173. //get all additions from superclasses
  174. obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
  175. // copy/set any non-init, non-simple values here
  176. if (this.tileSize != null) {
  177. obj.tileSize = this.tileSize.clone();
  178. }
  179. // we do not want to copy reference to grid, so we make a new array
  180. obj.grid = [];
  181. return obj;
  182. },
  183. /**
  184. * Method: moveTo
  185. * This function is called whenever the map is moved. All the moving
  186. * of actual 'tiles' is done by the map, but moveTo's role is to accept
  187. * a bounds and make sure the data that that bounds requires is pre-loaded.
  188. *
  189. * Parameters:
  190. * bounds - {<OpenLayers.Bounds>}
  191. * zoomChanged - {Boolean}
  192. * dragging - {Boolean}
  193. */
  194. moveTo:function(bounds, zoomChanged, dragging) {
  195. OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
  196. bounds = bounds || this.map.getExtent();
  197. if (bounds != null) {
  198. // if grid is empty or zoom has changed, we *must* re-tile
  199. var forceReTile = !this.grid.length || zoomChanged;
  200. // total bounds of the tiles
  201. var tilesBounds = this.getTilesBounds();
  202. if (this.singleTile) {
  203. // We want to redraw whenever even the slightest part of the
  204. // current bounds is not contained by our tile.
  205. // (thus, we do not specify partial -- its default is false)
  206. if ( forceReTile ||
  207. (!dragging && !tilesBounds.containsBounds(bounds))) {
  208. this.initSingleTile(bounds);
  209. }
  210. } else {
  211. // if the bounds have changed such that they are not even
  212. // *partially* contained by our tiles (IE user has
  213. // programmatically panned to the other side of the earth)
  214. // then we want to reTile (thus, partial true).
  215. //
  216. if (forceReTile || !tilesBounds.containsBounds(bounds, true)) {
  217. this.initGriddedTiles(bounds);
  218. } else {
  219. this.scheduleMoveGriddedTiles();
  220. }
  221. }
  222. }
  223. },
  224. /**
  225. * Method: moveByPx
  226. * Move the layer based on pixel vector.
  227. *
  228. * Parameters:
  229. * dx - {Number}
  230. * dy - {Number}
  231. */
  232. moveByPx: function(dx, dy) {
  233. if (!this.singleTile) {
  234. this.scheduleMoveGriddedTiles();
  235. }
  236. },
  237. /**
  238. * Method: scheduleMoveGriddedTiles
  239. * Schedule the move of tiles.
  240. */
  241. scheduleMoveGriddedTiles: function() {
  242. if (this.timerId != null) {
  243. window.clearTimeout(this.timerId);
  244. }
  245. this.timerId = window.setTimeout(
  246. this._moveGriddedTiles,
  247. this.tileLoadingDelay
  248. );
  249. },
  250. /**
  251. * APIMethod: setTileSize
  252. * Check if we are in singleTile mode and if so, set the size as a ratio
  253. * of the map size (as specified by the layer's 'ratio' property).
  254. *
  255. * Parameters:
  256. * size - {<OpenLayers.Size>}
  257. */
  258. setTileSize: function(size) {
  259. if (this.singleTile) {
  260. size = this.map.getSize();
  261. size.h = parseInt(size.h * this.ratio);
  262. size.w = parseInt(size.w * this.ratio);
  263. }
  264. OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
  265. },
  266. /**
  267. * Method: getGridBounds
  268. * Deprecated. This function will be removed in 3.0. Please use
  269. * getTilesBounds() instead.
  270. *
  271. * Returns:
  272. * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
  273. * currently loaded tiles (including those partially or not at all seen
  274. * onscreen)
  275. */
  276. getGridBounds: function() {
  277. var msg = "The getGridBounds() function is deprecated. It will be " +
  278. "removed in 3.0. Please use getTilesBounds() instead.";
  279. OpenLayers.Console.warn(msg);
  280. return this.getTilesBounds();
  281. },
  282. /**
  283. * APIMethod: getTilesBounds
  284. * Return the bounds of the tile grid.
  285. *
  286. * Returns:
  287. * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
  288. * currently loaded tiles (including those partially or not at all seen
  289. * onscreen).
  290. */
  291. getTilesBounds: function() {
  292. var bounds = null;
  293. if (this.grid.length) {
  294. var bottom = this.grid.length - 1;
  295. var bottomLeftTile = this.grid[bottom][0];
  296. var right = this.grid[0].length - 1;
  297. var topRightTile = this.grid[0][right];
  298. bounds = new OpenLayers.Bounds(bottomLeftTile.bounds.left,
  299. bottomLeftTile.bounds.bottom,
  300. topRightTile.bounds.right,
  301. topRightTile.bounds.top);
  302. }
  303. return bounds;
  304. },
  305. /**
  306. * Method: initSingleTile
  307. *
  308. * Parameters:
  309. * bounds - {<OpenLayers.Bounds>}
  310. */
  311. initSingleTile: function(bounds) {
  312. //determine new tile bounds
  313. var center = bounds.getCenterLonLat();
  314. var tileWidth = bounds.getWidth() * this.ratio;
  315. var tileHeight = bounds.getHeight() * this.ratio;
  316. var tileBounds =
  317. new OpenLayers.Bounds(center.lon - (tileWidth/2),
  318. center.lat - (tileHeight/2),
  319. center.lon + (tileWidth/2),
  320. center.lat + (tileHeight/2));
  321. var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
  322. var px = this.map.getLayerPxFromLonLat(ul);
  323. if (!this.grid.length) {
  324. this.grid[0] = [];
  325. }
  326. var tile = this.grid[0][0];
  327. if (!tile) {
  328. tile = this.addTile(tileBounds, px);
  329. this.addTileMonitoringHooks(tile);
  330. tile.draw();
  331. this.grid[0][0] = tile;
  332. } else {
  333. tile.moveTo(tileBounds, px);
  334. }
  335. //remove all but our single tile
  336. this.removeExcessTiles(1,1);
  337. },
  338. /**
  339. * Method: calculateGridLayout
  340. * Generate parameters for the grid layout.
  341. *
  342. * Parameters:
  343. * bounds - {<OpenLayers.Bound>}
  344. * origin - {<OpenLayers.LonLat>}
  345. * resolution - {Number}
  346. *
  347. * Returns:
  348. * Object containing properties tilelon, tilelat, tileoffsetlat,
  349. * tileoffsetlat, tileoffsetx, tileoffsety
  350. */
  351. calculateGridLayout: function(bounds, origin, resolution) {
  352. var tilelon = resolution * this.tileSize.w;
  353. var tilelat = resolution * this.tileSize.h;
  354. var offsetlon = bounds.left - origin.lon;
  355. var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
  356. var tilecolremain = offsetlon/tilelon - tilecol;
  357. var tileoffsetx = -tilecolremain * this.tileSize.w;
  358. var tileoffsetlon = origin.lon + tilecol * tilelon;
  359. var offsetlat = bounds.top - (origin.lat + tilelat);
  360. var tilerow = Math.ceil(offsetlat/tilelat) + this.buffer;
  361. var tilerowremain = tilerow - offsetlat/tilelat;
  362. var tileoffsety = -tilerowremain * this.tileSize.h;
  363. var tileoffsetlat = origin.lat + tilerow * tilelat;
  364. return {
  365. tilelon: tilelon, tilelat: tilelat,
  366. tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat,
  367. tileoffsetx: tileoffsetx, tileoffsety: tileoffsety
  368. };
  369. },
  370. /**
  371. * Method: getTileOrigin
  372. * Determine the origin for aligning the grid of tiles. If a <tileOrigin>
  373. * property is supplied, that will be returned. Otherwise, the origin
  374. * will be derived from the layer's <maxExtent> property. In this case,
  375. * the tile origin will be the corner of the <maxExtent> given by the
  376. * <tileOriginCorner> property.
  377. *
  378. * Returns:
  379. * {<OpenLayers.LonLat>} The tile origin.
  380. */
  381. getTileOrigin: function() {
  382. var origin = this.tileOrigin;
  383. if (!origin) {
  384. var extent = this.getMaxExtent();
  385. var edges = ({
  386. "tl": ["left", "top"],
  387. "tr": ["right", "top"],
  388. "bl": ["left", "bottom"],
  389. "br": ["right", "bottom"]
  390. })[this.tileOriginCorner];
  391. origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
  392. }
  393. return origin;
  394. },
  395. /**
  396. * Method: initGriddedTiles
  397. *
  398. * Parameters:
  399. * bounds - {<OpenLayers.Bounds>}
  400. */
  401. initGriddedTiles:function(bounds) {
  402. // work out mininum number of rows and columns; this is the number of
  403. // tiles required to cover the viewport plus at least one for panning
  404. var viewSize = this.map.getSize();
  405. var minRows = Math.ceil(viewSize.h/this.tileSize.h) +
  406. Math.max(1, 2 * this.buffer);
  407. var minCols = Math.ceil(viewSize.w/this.tileSize.w) +
  408. Math.max(1, 2 * this.buffer);
  409. var origin = this.getTileOrigin();
  410. var resolution = this.map.getResolution();
  411. var tileLayout = this.calculateGridLayout(bounds, origin, resolution);
  412. var tileoffsetx = Math.round(tileLayout.tileoffsetx); // heaven help us
  413. var tileoffsety = Math.round(tileLayout.tileoffsety);
  414. var tileoffsetlon = tileLayout.tileoffsetlon;
  415. var tileoffsetlat = tileLayout.tileoffsetlat;
  416. var tilelon = tileLayout.tilelon;
  417. var tilelat = tileLayout.tilelat;
  418. this.origin = new OpenLayers.Pixel(tileoffsetx, tileoffsety);
  419. var startX = tileoffsetx;
  420. var startLon = tileoffsetlon;
  421. var rowidx = 0;
  422. var layerContainerDivLeft = parseInt(this.map.layerContainerDiv.style.left);
  423. var layerContainerDivTop = parseInt(this.map.layerContainerDiv.style.top);
  424. do {
  425. var row = this.grid[rowidx++];
  426. if (!row) {
  427. row = [];
  428. this.grid.push(row);
  429. }
  430. tileoffsetlon = startLon;
  431. tileoffsetx = startX;
  432. var colidx = 0;
  433. do {
  434. var tileBounds =
  435. new OpenLayers.Bounds(tileoffsetlon,
  436. tileoffsetlat,
  437. tileoffsetlon + tilelon,
  438. tileoffsetlat + tilelat);
  439. var x = tileoffsetx;
  440. x -= layerContainerDivLeft;
  441. var y = tileoffsety;
  442. y -= layerContainerDivTop;
  443. var px = new OpenLayers.Pixel(x, y);
  444. var tile = row[colidx++];
  445. if (!tile) {
  446. tile = this.addTile(tileBounds, px);
  447. this.addTileMonitoringHooks(tile);
  448. row.push(tile);
  449. } else {
  450. tile.moveTo(tileBounds, px, false);
  451. }
  452. tileoffsetlon += tilelon;
  453. tileoffsetx += this.tileSize.w;
  454. } while ((tileoffsetlon <= bounds.right + tilelon * this.buffer)
  455. || colidx < minCols);
  456. tileoffsetlat -= tilelat;
  457. tileoffsety += this.tileSize.h;
  458. } while((tileoffsetlat >= bounds.bottom - tilelat * this.buffer)
  459. || rowidx < minRows);
  460. //shave off exceess rows and colums
  461. this.removeExcessTiles(rowidx, colidx);
  462. //now actually draw the tiles
  463. this.spiralTileLoad();
  464. },
  465. /**
  466. * Method: getMaxExtent
  467. * Get this layer's maximum extent. (Implemented as a getter for
  468. * potential specific implementations in sub-classes.)
  469. *
  470. * Returns:
  471. * {OpenLayers.Bounds}
  472. */
  473. getMaxExtent: function() {
  474. return this.maxExtent;
  475. },
  476. /**
  477. * Method: spiralTileLoad
  478. * Starts at the top right corner of the grid and proceeds in a spiral
  479. * towards the center, adding tiles one at a time to the beginning of a
  480. * queue.
  481. *
  482. * Once all the grid's tiles have been added to the queue, we go back
  483. * and iterate through the queue (thus reversing the spiral order from
  484. * outside-in to inside-out), calling draw() on each tile.
  485. */
  486. spiralTileLoad: function() {
  487. var tileQueue = [];
  488. var directions = ["right", "down", "left", "up"];
  489. var iRow = 0;
  490. var iCell = -1;
  491. var direction = OpenLayers.Util.indexOf(directions, "right");
  492. var directionsTried = 0;
  493. while( directionsTried < directions.length) {
  494. var testRow = iRow;
  495. var testCell = iCell;
  496. switch (directions[direction]) {
  497. case "right":
  498. testCell++;
  499. break;
  500. case "down":
  501. testRow++;
  502. break;
  503. case "left":
  504. testCell--;
  505. break;
  506. case "up":
  507. testRow--;
  508. break;
  509. }
  510. // if the test grid coordinates are within the bounds of the
  511. // grid, get a reference to the tile.
  512. var tile = null;
  513. if ((testRow < this.grid.length) && (testRow >= 0) &&
  514. (testCell < this.grid[0].length) && (testCell >= 0)) {
  515. tile = this.grid[testRow][testCell];
  516. }
  517. if ((tile != null) && (!tile.queued)) {
  518. //add tile to beginning of queue, mark it as queued.
  519. tileQueue.unshift(tile);
  520. tile.queued = true;
  521. //restart the directions counter and take on the new coords
  522. directionsTried = 0;
  523. iRow = testRow;
  524. iCell = testCell;
  525. } else {
  526. //need to try to load a tile in a different direction
  527. direction = (direction + 1) % 4;
  528. directionsTried++;
  529. }
  530. }
  531. // now we go through and draw the tiles in forward order
  532. for(var i=0, len=tileQueue.length; i<len; i++) {
  533. var tile = tileQueue[i];
  534. tile.draw();
  535. //mark tile as unqueued for the next time (since tiles are reused)
  536. tile.queued = false;
  537. }
  538. },
  539. /**
  540. * APIMethod: addTile
  541. * Create a tile, initialize it, and add it to the layer div.
  542. *
  543. * Parameters
  544. * bounds - {<OpenLayers.Bounds>}
  545. * position - {<OpenLayers.Pixel>}
  546. *
  547. * Returns:
  548. * {<OpenLayers.Tile>} The added OpenLayers.Tile
  549. */
  550. addTile:function(bounds, position) {
  551. return new OpenLayers.Tile.Image(this, position, bounds, null,
  552. this.tileSize, this.tileOptions);
  553. },
  554. /**
  555. * Method: addTileMonitoringHooks
  556. * This function takes a tile as input and adds the appropriate hooks to
  557. * the tile so that the layer can keep track of the loading tiles.
  558. *
  559. * Parameters:
  560. * tile - {<OpenLayers.Tile>}
  561. */
  562. addTileMonitoringHooks: function(tile) {
  563. tile.onLoadStart = function() {
  564. //if that was first tile then trigger a 'loadstart' on the layer
  565. if (this.numLoadingTiles == 0) {
  566. this.events.triggerEvent("loadstart");
  567. }
  568. this.numLoadingTiles++;
  569. };
  570. tile.events.register("loadstart", this, tile.onLoadStart);
  571. tile.onLoadEnd = function() {
  572. this.numLoadingTiles--;
  573. this.events.triggerEvent("tileloaded");
  574. //if that was the last tile, then trigger a 'loadend' on the layer
  575. if (this.numLoadingTiles == 0) {
  576. this.events.triggerEvent("loadend");
  577. }
  578. };
  579. tile.events.register("loadend", this, tile.onLoadEnd);
  580. tile.events.register("unload", this, tile.onLoadEnd);
  581. },
  582. /**
  583. * Method: removeTileMonitoringHooks
  584. * This function takes a tile as input and removes the tile hooks
  585. * that were added in addTileMonitoringHooks()
  586. *
  587. * Parameters:
  588. * tile - {<OpenLayers.Tile>}
  589. */
  590. removeTileMonitoringHooks: function(tile) {
  591. tile.unload();
  592. tile.events.un({
  593. "loadstart": tile.onLoadStart,
  594. "loadend": tile.onLoadEnd,
  595. "unload": tile.onLoadEnd,
  596. scope: this
  597. });
  598. },
  599. /**
  600. * Method: moveGriddedTiles
  601. */
  602. moveGriddedTiles: function() {
  603. var shifted = true;
  604. var buffer = this.buffer || 1;
  605. var tlLayer = this.grid[0][0].position;
  606. var offsetX = parseInt(this.map.layerContainerDiv.style.left);
  607. var offsetY = parseInt(this.map.layerContainerDiv.style.top);
  608. var tlViewPort = tlLayer.add(offsetX, offsetY);
  609. if (tlViewPort.x > -this.tileSize.w * (buffer - 1)) {
  610. this.shiftColumn(true);
  611. } else if (tlViewPort.x < -this.tileSize.w * buffer) {
  612. this.shiftColumn(false);
  613. } else if (tlViewPort.y > -this.tileSize.h * (buffer - 1)) {
  614. this.shiftRow(true);
  615. } else if (tlViewPort.y < -this.tileSize.h * buffer) {
  616. this.shiftRow(false);
  617. } else {
  618. shifted = false;
  619. }
  620. if (shifted) {
  621. // we may have other row or columns to shift, schedule it
  622. // with a setTimeout, to give the user a chance to sneak
  623. // in moveTo's
  624. this.timerId = window.setTimeout(this._moveGriddedTiles, 0);
  625. }
  626. },
  627. /**
  628. * Method: shiftRow
  629. * Shifty grid work
  630. *
  631. * Parameters:
  632. * prepend - {Boolean} if true, prepend to beginning.
  633. * if false, then append to end
  634. */
  635. shiftRow:function(prepend) {
  636. var modelRowIndex = (prepend) ? 0 : (this.grid.length - 1);
  637. var grid = this.grid;
  638. var modelRow = grid[modelRowIndex];
  639. var resolution = this.map.getResolution();
  640. var deltaY = (prepend) ? -this.tileSize.h : this.tileSize.h;
  641. var deltaLat = resolution * -deltaY;
  642. var row = (prepend) ? grid.pop() : grid.shift();
  643. for (var i=0, len=modelRow.length; i<len; i++) {
  644. var modelTile = modelRow[i];
  645. var bounds = modelTile.bounds.clone();
  646. var position = modelTile.position.clone();
  647. bounds.bottom = bounds.bottom + deltaLat;
  648. bounds.top = bounds.top + deltaLat;
  649. position.y = position.y + deltaY;
  650. row[i].moveTo(bounds, position);
  651. }
  652. if (prepend) {
  653. grid.unshift(row);
  654. } else {
  655. grid.push(row);
  656. }
  657. },
  658. /**
  659. * Method: shiftColumn
  660. * Shift grid work in the other dimension
  661. *
  662. * Parameters:
  663. * prepend - {Boolean} if true, prepend to beginning.
  664. * if false, then append to end
  665. */
  666. shiftColumn: function(prepend) {
  667. var deltaX = (prepend) ? -this.tileSize.w : this.tileSize.w;
  668. var resolution = this.map.getResolution();
  669. var deltaLon = resolution * deltaX;
  670. for (var i=0, len=this.grid.length; i<len; i++) {
  671. var row = this.grid[i];
  672. var modelTileIndex = (prepend) ? 0 : (row.length - 1);
  673. var modelTile = row[modelTileIndex];
  674. var bounds = modelTile.bounds.clone();
  675. var position = modelTile.position.clone();
  676. bounds.left = bounds.left + deltaLon;
  677. bounds.right = bounds.right + deltaLon;
  678. position.x = position.x + deltaX;
  679. var tile = prepend ? this.grid[i].pop() : this.grid[i].shift();
  680. tile.moveTo(bounds, position);
  681. if (prepend) {
  682. row.unshift(tile);
  683. } else {
  684. row.push(tile);
  685. }
  686. }
  687. },
  688. /**
  689. * Method: removeExcessTiles
  690. * When the size of the map or the buffer changes, we may need to
  691. * remove some excess rows and columns.
  692. *
  693. * Parameters:
  694. * rows - {Integer} Maximum number of rows we want our grid to have.
  695. * columns - {Integer} Maximum number of columns we want our grid to have.
  696. */
  697. removeExcessTiles: function(rows, columns) {
  698. // remove extra rows
  699. while (this.grid.length > rows) {
  700. var row = this.grid.pop();
  701. for (var i=0, l=row.length; i<l; i++) {
  702. var tile = row[i];
  703. this.removeTileMonitoringHooks(tile);
  704. tile.destroy();
  705. }
  706. }
  707. // remove extra columns
  708. while (this.grid[0].length > columns) {
  709. for (var i=0, l=this.grid.length; i<l; i++) {
  710. var row = this.grid[i];
  711. var tile = row.pop();
  712. this.removeTileMonitoringHooks(tile);
  713. tile.destroy();
  714. }
  715. }
  716. },
  717. /**
  718. * Method: onMapResize
  719. * For singleTile layers, this will set a new tile size according to the
  720. * dimensions of the map pane.
  721. */
  722. onMapResize: function() {
  723. if (this.singleTile) {
  724. this.clearGrid();
  725. this.setTileSize();
  726. }
  727. },
  728. /**
  729. * APIMethod: getTileBounds
  730. * Returns The tile bounds for a layer given a pixel location.
  731. *
  732. * Parameters:
  733. * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
  734. *
  735. * Returns:
  736. * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
  737. */
  738. getTileBounds: function(viewPortPx) {
  739. var maxExtent = this.maxExtent;
  740. var resolution = this.getResolution();
  741. var tileMapWidth = resolution * this.tileSize.w;
  742. var tileMapHeight = resolution * this.tileSize.h;
  743. var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
  744. var tileLeft = maxExtent.left + (tileMapWidth *
  745. Math.floor((mapPoint.lon -
  746. maxExtent.left) /
  747. tileMapWidth));
  748. var tileBottom = maxExtent.bottom + (tileMapHeight *
  749. Math.floor((mapPoint.lat -
  750. maxExtent.bottom) /
  751. tileMapHeight));
  752. return new OpenLayers.Bounds(tileLeft, tileBottom,
  753. tileLeft + tileMapWidth,
  754. tileBottom + tileMapHeight);
  755. },
  756. CLASS_NAME: "OpenLayers.Layer.Grid"
  757. });