Grid.js 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343
  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/Layer/HTTPRequest.js
  7. * @requires OpenLayers/Tile/Image.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. * APIProperty: tileClass
  48. * {<OpenLayers.Tile>} The tile class to use for this layer.
  49. * Defaults is OpenLayers.Tile.Image.
  50. */
  51. tileClass: OpenLayers.Tile.Image,
  52. /**
  53. * Property: grid
  54. * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is
  55. * an array of tiles.
  56. */
  57. grid: null,
  58. /**
  59. * APIProperty: singleTile
  60. * {Boolean} Moves the layer into single-tile mode, meaning that one tile
  61. * will be loaded. The tile's size will be determined by the 'ratio'
  62. * property. When the tile is dragged such that it does not cover the
  63. * entire viewport, it is reloaded.
  64. */
  65. singleTile: false,
  66. /** APIProperty: ratio
  67. * {Float} Used only when in single-tile mode, this specifies the
  68. * ratio of the size of the single tile to the size of the map.
  69. * Default value is 1.5.
  70. */
  71. ratio: 1.5,
  72. /**
  73. * APIProperty: buffer
  74. * {Integer} Used only when in gridded mode, this specifies the number of
  75. * extra rows and colums of tiles on each side which will
  76. * surround the minimum grid tiles to cover the map.
  77. * For very slow loading layers, a larger value may increase
  78. * performance somewhat when dragging, but will increase bandwidth
  79. * use significantly.
  80. */
  81. buffer: 0,
  82. /**
  83. * APIProperty: transitionEffect
  84. * {String} The transition effect to use when the map is zoomed.
  85. * Two posible values:
  86. *
  87. * "resize" - Existing tiles are resized on zoom to provide a visual
  88. * effect of the zoom having taken place immediately. As the
  89. * new tiles become available, they are drawn on top of the
  90. * resized tiles (this is the default setting).
  91. * "map-resize" - Existing tiles are resized on zoom and placed below the
  92. * base layer. New tiles for the base layer will cover existing tiles.
  93. * This setting is recommended when having an overlay duplicated during
  94. * the transition is undesirable (e.g. street labels or big transparent
  95. * fills).
  96. * null - No transition effect.
  97. *
  98. * Using "resize" on non-opaque layers can cause undesired visual
  99. * effects. Set transitionEffect to null in this case.
  100. */
  101. transitionEffect: "resize",
  102. /**
  103. * APIProperty: numLoadingTiles
  104. * {Integer} How many tiles are still loading?
  105. */
  106. numLoadingTiles: 0,
  107. /**
  108. * Property: serverResolutions
  109. * {Array(Number}} This property is documented in subclasses as
  110. * an API property.
  111. */
  112. serverResolutions: null,
  113. /**
  114. * Property: loading
  115. * {Boolean} Indicates if tiles are being loaded.
  116. */
  117. loading: false,
  118. /**
  119. * Property: backBuffer
  120. * {DOMElement} The back buffer.
  121. */
  122. backBuffer: null,
  123. /**
  124. * Property: gridResolution
  125. * {Number} The resolution of the current grid. Used for backbuffer and
  126. * client zoom. This property is updated every time the grid is
  127. * initialized.
  128. */
  129. gridResolution: null,
  130. /**
  131. * Property: backBufferResolution
  132. * {Number} The resolution of the current back buffer. This property is
  133. * updated each time a back buffer is created.
  134. */
  135. backBufferResolution: null,
  136. /**
  137. * Property: backBufferLonLat
  138. * {Object} The top-left corner of the current back buffer. Includes lon
  139. * and lat properties. This object is updated each time a back buffer
  140. * is created.
  141. */
  142. backBufferLonLat: null,
  143. /**
  144. * Property: backBufferTimerId
  145. * {Number} The id of the back buffer timer. This timer is used to
  146. * delay the removal of the back buffer, thereby preventing
  147. * flash effects caused by tile animation.
  148. */
  149. backBufferTimerId: null,
  150. /**
  151. * APIProperty: removeBackBufferDelay
  152. * {Number} Delay for removing the backbuffer when all tiles have finished
  153. * loading. Can be set to 0 when no css opacity transitions for the
  154. * olTileImage class are used. Default is 0 for <singleTile> layers,
  155. * 2500 for tiled layers. See <className> for more information on
  156. * tile animation.
  157. */
  158. removeBackBufferDelay: null,
  159. /**
  160. * APIProperty: className
  161. * {String} Name of the class added to the layer div. If not set in the
  162. * options passed to the constructor then className defaults to
  163. * "olLayerGridSingleTile" for single tile layers (see <singleTile>),
  164. * and "olLayerGrid" for non single tile layers.
  165. *
  166. * Note:
  167. *
  168. * The displaying of tiles is not animated by default for single tile
  169. * layers - OpenLayers' default theme (style.css) includes this:
  170. * (code)
  171. * .olLayerGrid .olTileImage {
  172. * -webkit-transition: opacity 0.2s linear;
  173. * -moz-transition: opacity 0.2s linear;
  174. * -o-transition: opacity 0.2s linear;
  175. * transition: opacity 0.2s linear;
  176. * }
  177. * (end)
  178. * To animate tile displaying for any grid layer the following
  179. * CSS rule can be used:
  180. * (code)
  181. * .olTileImage {
  182. * -webkit-transition: opacity 0.2s linear;
  183. * -moz-transition: opacity 0.2s linear;
  184. * -o-transition: opacity 0.2s linear;
  185. * transition: opacity 0.2s linear;
  186. * }
  187. * (end)
  188. * In that case, to avoid flash effects, <removeBackBufferDelay>
  189. * should not be zero.
  190. */
  191. className: null,
  192. /**
  193. * Register a listener for a particular event with the following syntax:
  194. * (code)
  195. * layer.events.register(type, obj, listener);
  196. * (end)
  197. *
  198. * Listeners will be called with a reference to an event object. The
  199. * properties of this event depends on exactly what happened.
  200. *
  201. * All event objects have at least the following properties:
  202. * object - {Object} A reference to layer.events.object.
  203. * element - {DOMElement} A reference to layer.events.element.
  204. *
  205. * Supported event types:
  206. * addtile - Triggered when a tile is added to this layer. Listeners receive
  207. * an object as first argument, which has a tile property that
  208. * references the tile that has been added.
  209. * tileloadstart - Triggered when a tile starts loading. Listeners receive
  210. * an object as first argument, which has a tile property that
  211. * references the tile that starts loading.
  212. * tileloaded - Triggered when each new tile is
  213. * loaded, as a means of progress update to listeners.
  214. * listeners can access 'numLoadingTiles' if they wish to keep
  215. * track of the loading progress. Listeners are called with an object
  216. * with a 'tile' property as first argument, making the loaded tile
  217. * available to the listener, and an 'aborted' property, which will be
  218. * true when loading was aborted and no tile data is available.
  219. * tileerror - Triggered before the tileloaded event (i.e. when the tile is
  220. * still hidden) if a tile failed to load. Listeners receive an object
  221. * as first argument, which has a tile property that references the
  222. * tile that could not be loaded.
  223. * retile - Triggered when the layer recreates its tile grid.
  224. */
  225. /**
  226. * Property: gridLayout
  227. * {Object} Object containing properties tilelon, tilelat, startcol,
  228. * startrow
  229. */
  230. gridLayout: null,
  231. /**
  232. * Property: rowSign
  233. * {Number} 1 for grids starting at the top, -1 for grids starting at the
  234. * bottom. This is used for several grid index and offset calculations.
  235. */
  236. rowSign: null,
  237. /**
  238. * Property: transitionendEvents
  239. * {Array} Event names for transitionend
  240. */
  241. transitionendEvents: [
  242. 'transitionend', 'webkitTransitionEnd', 'otransitionend',
  243. 'oTransitionEnd'
  244. ],
  245. /**
  246. * Constructor: OpenLayers.Layer.Grid
  247. * Create a new grid layer
  248. *
  249. * Parameters:
  250. * name - {String}
  251. * url - {String}
  252. * params - {Object}
  253. * options - {Object} Hashtable of extra options to tag onto the layer
  254. */
  255. initialize: function(name, url, params, options) {
  256. OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
  257. arguments);
  258. this.grid = [];
  259. this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this);
  260. this.initProperties();
  261. this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1;
  262. },
  263. /**
  264. * Method: initProperties
  265. * Set any properties that depend on the value of singleTile.
  266. * Currently sets removeBackBufferDelay and className
  267. */
  268. initProperties: function() {
  269. if (this.options.removeBackBufferDelay === undefined) {
  270. this.removeBackBufferDelay = this.singleTile ? 0 : 2500;
  271. }
  272. if (this.options.className === undefined) {
  273. this.className = this.singleTile ? 'olLayerGridSingleTile' :
  274. 'olLayerGrid';
  275. }
  276. },
  277. /**
  278. * Method: setMap
  279. *
  280. * Parameters:
  281. * map - {<OpenLayers.Map>} The map.
  282. */
  283. setMap: function(map) {
  284. OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map);
  285. OpenLayers.Element.addClass(this.div, this.className);
  286. },
  287. /**
  288. * Method: removeMap
  289. * Called when the layer is removed from the map.
  290. *
  291. * Parameters:
  292. * map - {<OpenLayers.Map>} The map.
  293. */
  294. removeMap: function(map) {
  295. this.removeBackBuffer();
  296. },
  297. /**
  298. * APIMethod: destroy
  299. * Deconstruct the layer and clear the grid.
  300. */
  301. destroy: function() {
  302. this.removeBackBuffer();
  303. this.clearGrid();
  304. this.grid = null;
  305. this.tileSize = null;
  306. OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments);
  307. },
  308. /**
  309. * APIMethod: mergeNewParams
  310. * Refetches tiles with new params merged, keeping a backbuffer. Each
  311. * loading new tile will have a css class of '.olTileReplacing'. If a
  312. * stylesheet applies a 'display: none' style to that class, any fade-in
  313. * transition will not apply, and backbuffers for each tile will be removed
  314. * as soon as the tile is loaded.
  315. *
  316. * Parameters:
  317. * newParams - {Object}
  318. *
  319. * Returns:
  320. * redrawn: {Boolean} whether the layer was actually redrawn.
  321. */
  322. /**
  323. * Method: clearGrid
  324. * Go through and remove all tiles from the grid, calling
  325. * destroy() on each of them to kill circular references
  326. */
  327. clearGrid:function() {
  328. if (this.grid) {
  329. for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
  330. var row = this.grid[iRow];
  331. for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
  332. var tile = row[iCol];
  333. this.destroyTile(tile);
  334. }
  335. }
  336. this.grid = [];
  337. this.gridResolution = null;
  338. this.gridLayout = null;
  339. }
  340. },
  341. /**
  342. * APIMethod: addOptions
  343. *
  344. * Parameters:
  345. * newOptions - {Object}
  346. * reinitialize - {Boolean} If set to true, and if resolution options of the
  347. * current baseLayer were changed, the map will be recentered to make
  348. * sure that it is displayed with a valid resolution, and a
  349. * changebaselayer event will be triggered.
  350. */
  351. addOptions: function (newOptions, reinitialize) {
  352. var singleTileChanged = newOptions.singleTile !== undefined &&
  353. newOptions.singleTile !== this.singleTile;
  354. OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
  355. if (this.map && singleTileChanged) {
  356. this.initProperties();
  357. this.clearGrid();
  358. this.tileSize = this.options.tileSize;
  359. this.setTileSize();
  360. this.moveTo(null, true);
  361. }
  362. },
  363. /**
  364. * APIMethod: clone
  365. * Create a clone of this layer
  366. *
  367. * Parameters:
  368. * obj - {Object} Is this ever used?
  369. *
  370. * Returns:
  371. * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
  372. */
  373. clone: function (obj) {
  374. if (obj == null) {
  375. obj = new OpenLayers.Layer.Grid(this.name,
  376. this.url,
  377. this.params,
  378. this.getOptions());
  379. }
  380. //get all additions from superclasses
  381. obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
  382. // copy/set any non-init, non-simple values here
  383. if (this.tileSize != null) {
  384. obj.tileSize = this.tileSize.clone();
  385. }
  386. // we do not want to copy reference to grid, so we make a new array
  387. obj.grid = [];
  388. obj.gridResolution = null;
  389. // same for backbuffer
  390. obj.backBuffer = null;
  391. obj.backBufferTimerId = null;
  392. obj.loading = false;
  393. obj.numLoadingTiles = 0;
  394. return obj;
  395. },
  396. /**
  397. * Method: moveTo
  398. * This function is called whenever the map is moved. All the moving
  399. * of actual 'tiles' is done by the map, but moveTo's role is to accept
  400. * a bounds and make sure the data that that bounds requires is pre-loaded.
  401. *
  402. * Parameters:
  403. * bounds - {<OpenLayers.Bounds>}
  404. * zoomChanged - {Boolean}
  405. * dragging - {Boolean}
  406. */
  407. moveTo:function(bounds, zoomChanged, dragging) {
  408. OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
  409. bounds = bounds || this.map.getExtent();
  410. if (bounds != null) {
  411. // if grid is empty or zoom has changed, we *must* re-tile
  412. var forceReTile = !this.grid.length || zoomChanged;
  413. // total bounds of the tiles
  414. var tilesBounds = this.getTilesBounds();
  415. // the new map resolution
  416. var resolution = this.map.getResolution();
  417. // the server-supported resolution for the new map resolution
  418. var serverResolution = this.getServerResolution(resolution);
  419. if (this.singleTile) {
  420. // We want to redraw whenever even the slightest part of the
  421. // current bounds is not contained by our tile.
  422. // (thus, we do not specify partial -- its default is false)
  423. if ( forceReTile ||
  424. (!dragging && !tilesBounds.containsBounds(bounds))) {
  425. // In single tile mode with no transition effect, we insert
  426. // a non-scaled backbuffer when the layer is moved. But if
  427. // a zoom occurs right after a move, i.e. before the new
  428. // image is received, we need to remove the backbuffer, or
  429. // an ill-positioned image will be visible during the zoom
  430. // transition.
  431. if(zoomChanged && this.transitionEffect !== 'resize') {
  432. this.removeBackBuffer();
  433. }
  434. if(!zoomChanged || this.transitionEffect === 'resize') {
  435. this.applyBackBuffer(resolution);
  436. }
  437. this.initSingleTile(bounds);
  438. }
  439. } else {
  440. // if the bounds have changed such that they are not even
  441. // *partially* contained by our tiles (e.g. when user has
  442. // programmatically panned to the other side of the earth on
  443. // zoom level 18), then moveGriddedTiles could potentially have
  444. // to run through thousands of cycles, so we want to reTile
  445. // instead (thus, partial true).
  446. forceReTile = forceReTile ||
  447. !tilesBounds.intersectsBounds(bounds, {
  448. worldBounds: this.map.baseLayer.wrapDateLine &&
  449. this.map.getMaxExtent()
  450. });
  451. if(forceReTile) {
  452. if(zoomChanged && (this.transitionEffect === 'resize' ||
  453. this.gridResolution === resolution)) {
  454. this.applyBackBuffer(resolution);
  455. }
  456. this.initGriddedTiles(bounds);
  457. } else {
  458. this.moveGriddedTiles();
  459. }
  460. }
  461. }
  462. },
  463. /**
  464. * Method: getTileData
  465. * Given a map location, retrieve a tile and the pixel offset within that
  466. * tile corresponding to the location. If there is not an existing
  467. * tile in the grid that covers the given location, null will be
  468. * returned.
  469. *
  470. * Parameters:
  471. * loc - {<OpenLayers.LonLat>} map location
  472. *
  473. * Returns:
  474. * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
  475. * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
  476. * offset from top left).
  477. */
  478. getTileData: function(loc) {
  479. var data = null,
  480. x = loc.lon,
  481. y = loc.lat,
  482. numRows = this.grid.length;
  483. if (this.map && numRows) {
  484. var res = this.map.getResolution(),
  485. tileWidth = this.tileSize.w,
  486. tileHeight = this.tileSize.h,
  487. bounds = this.grid[0][0].bounds,
  488. left = bounds.left,
  489. top = bounds.top;
  490. if (x < left) {
  491. // deal with multiple worlds
  492. if (this.map.baseLayer.wrapDateLine) {
  493. var worldWidth = this.map.getMaxExtent().getWidth();
  494. var worldsAway = Math.ceil((left - x) / worldWidth);
  495. x += worldWidth * worldsAway;
  496. }
  497. }
  498. // tile distance to location (fractional number of tiles);
  499. var dtx = (x - left) / (res * tileWidth);
  500. var dty = (top - y) / (res * tileHeight);
  501. // index of tile in grid
  502. var col = Math.floor(dtx);
  503. var row = Math.floor(dty);
  504. if (row >= 0 && row < numRows) {
  505. var tile = this.grid[row][col];
  506. if (tile) {
  507. data = {
  508. tile: tile,
  509. // pixel index within tile
  510. i: Math.floor((dtx - col) * tileWidth),
  511. j: Math.floor((dty - row) * tileHeight)
  512. };
  513. }
  514. }
  515. }
  516. return data;
  517. },
  518. /**
  519. * Method: destroyTile
  520. *
  521. * Parameters:
  522. * tile - {<OpenLayers.Tile>}
  523. */
  524. destroyTile: function(tile) {
  525. this.removeTileMonitoringHooks(tile);
  526. tile.destroy();
  527. },
  528. /**
  529. * Method: getServerResolution
  530. * Return the closest server-supported resolution.
  531. *
  532. * Parameters:
  533. * resolution - {Number} The base resolution. If undefined the
  534. * map resolution is used.
  535. *
  536. * Returns:
  537. * {Number} The closest server resolution value.
  538. */
  539. getServerResolution: function(resolution) {
  540. var distance = Number.POSITIVE_INFINITY;
  541. resolution = resolution || this.map.getResolution();
  542. if(this.serverResolutions &&
  543. OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
  544. var i, newDistance, newResolution, serverResolution;
  545. for(i=this.serverResolutions.length-1; i>= 0; i--) {
  546. newResolution = this.serverResolutions[i];
  547. newDistance = Math.abs(newResolution - resolution);
  548. if (newDistance > distance) {
  549. break;
  550. }
  551. distance = newDistance;
  552. serverResolution = newResolution;
  553. }
  554. resolution = serverResolution;
  555. }
  556. return resolution;
  557. },
  558. /**
  559. * Method: getServerZoom
  560. * Return the zoom value corresponding to the best matching server
  561. * resolution, taking into account <serverResolutions> and <zoomOffset>.
  562. *
  563. * Returns:
  564. * {Number} The closest server supported zoom. This is not the map zoom
  565. * level, but an index of the server's resolutions array.
  566. */
  567. getServerZoom: function() {
  568. var resolution = this.getServerResolution();
  569. return this.serverResolutions ?
  570. OpenLayers.Util.indexOf(this.serverResolutions, resolution) :
  571. this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0);
  572. },
  573. /**
  574. * Method: applyBackBuffer
  575. * Create, insert, scale and position a back buffer for the layer.
  576. *
  577. * Parameters:
  578. * resolution - {Number} The resolution to transition to.
  579. */
  580. applyBackBuffer: function(resolution) {
  581. if(this.backBufferTimerId !== null) {
  582. this.removeBackBuffer();
  583. }
  584. var backBuffer = this.backBuffer;
  585. if(!backBuffer) {
  586. backBuffer = this.createBackBuffer();
  587. if(!backBuffer) {
  588. return;
  589. }
  590. if (resolution === this.gridResolution) {
  591. this.div.insertBefore(backBuffer, this.div.firstChild);
  592. } else {
  593. this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div);
  594. }
  595. this.backBuffer = backBuffer;
  596. // set some information in the instance for subsequent
  597. // calls to applyBackBuffer where the same back buffer
  598. // is reused
  599. var topLeftTileBounds = this.grid[0][0].bounds;
  600. this.backBufferLonLat = {
  601. lon: topLeftTileBounds.left,
  602. lat: topLeftTileBounds.top
  603. };
  604. this.backBufferResolution = this.gridResolution;
  605. }
  606. var ratio = this.backBufferResolution / resolution;
  607. // scale the tiles inside the back buffer
  608. var tiles = backBuffer.childNodes, tile;
  609. for (var i=tiles.length-1; i>=0; --i) {
  610. tile = tiles[i];
  611. tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px';
  612. tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px';
  613. tile.style.width = Math.round(ratio * tile._w) + 'px';
  614. tile.style.height = Math.round(ratio * tile._h) + 'px';
  615. }
  616. // and position it (based on the grid's top-left corner)
  617. var position = this.getViewPortPxFromLonLat(
  618. this.backBufferLonLat, resolution);
  619. var leftOffset = this.map.layerContainerOriginPx.x;
  620. var topOffset = this.map.layerContainerOriginPx.y;
  621. backBuffer.style.left = Math.round(position.x - leftOffset) + 'px';
  622. backBuffer.style.top = Math.round(position.y - topOffset) + 'px';
  623. },
  624. /**
  625. * Method: createBackBuffer
  626. * Create a back buffer.
  627. *
  628. * Returns:
  629. * {DOMElement} The DOM element for the back buffer, undefined if the
  630. * grid isn't initialized yet.
  631. */
  632. createBackBuffer: function() {
  633. var backBuffer;
  634. if(this.grid.length > 0) {
  635. backBuffer = document.createElement('div');
  636. backBuffer.id = this.div.id + '_bb';
  637. backBuffer.className = 'olBackBuffer';
  638. backBuffer.style.position = 'absolute';
  639. var map = this.map;
  640. backBuffer.style.zIndex = this.transitionEffect === 'resize' ?
  641. this.getZIndex() - 1 :
  642. // 'map-resize':
  643. map.Z_INDEX_BASE.BaseLayer -
  644. (map.getNumLayers() - map.getLayerIndex(this));
  645. for(var i=0, lenI=this.grid.length; i<lenI; i++) {
  646. for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
  647. var tile = this.grid[i][j],
  648. markup = this.grid[i][j].createBackBuffer();
  649. if (markup) {
  650. markup._i = i;
  651. markup._j = j;
  652. markup._w = tile.size.w;
  653. markup._h = tile.size.h;
  654. markup.id = tile.id + '_bb';
  655. backBuffer.appendChild(markup);
  656. }
  657. }
  658. }
  659. }
  660. return backBuffer;
  661. },
  662. /**
  663. * Method: removeBackBuffer
  664. * Remove back buffer from DOM.
  665. */
  666. removeBackBuffer: function() {
  667. if (this._transitionElement) {
  668. for (var i=this.transitionendEvents.length-1; i>=0; --i) {
  669. OpenLayers.Event.stopObserving(this._transitionElement,
  670. this.transitionendEvents[i], this._removeBackBuffer);
  671. }
  672. delete this._transitionElement;
  673. }
  674. if(this.backBuffer) {
  675. if (this.backBuffer.parentNode) {
  676. this.backBuffer.parentNode.removeChild(this.backBuffer);
  677. }
  678. this.backBuffer = null;
  679. this.backBufferResolution = null;
  680. if(this.backBufferTimerId !== null) {
  681. window.clearTimeout(this.backBufferTimerId);
  682. this.backBufferTimerId = null;
  683. }
  684. }
  685. },
  686. /**
  687. * Method: moveByPx
  688. * Move the layer based on pixel vector.
  689. *
  690. * Parameters:
  691. * dx - {Number}
  692. * dy - {Number}
  693. */
  694. moveByPx: function(dx, dy) {
  695. if (!this.singleTile) {
  696. this.moveGriddedTiles();
  697. }
  698. },
  699. /**
  700. * APIMethod: setTileSize
  701. * Check if we are in singleTile mode and if so, set the size as a ratio
  702. * of the map size (as specified by the layer's 'ratio' property).
  703. *
  704. * Parameters:
  705. * size - {<OpenLayers.Size>}
  706. */
  707. setTileSize: function(size) {
  708. if (this.singleTile) {
  709. size = this.map.getSize();
  710. size.h = parseInt(size.h * this.ratio, 10);
  711. size.w = parseInt(size.w * this.ratio, 10);
  712. }
  713. OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
  714. },
  715. /**
  716. * APIMethod: getTilesBounds
  717. * Return the bounds of the tile grid.
  718. *
  719. * Returns:
  720. * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
  721. * currently loaded tiles (including those partially or not at all seen
  722. * onscreen).
  723. */
  724. getTilesBounds: function() {
  725. var bounds = null;
  726. var length = this.grid.length;
  727. if (length) {
  728. var bottomLeftTileBounds = this.grid[length - 1][0].bounds,
  729. width = this.grid[0].length * bottomLeftTileBounds.getWidth(),
  730. height = this.grid.length * bottomLeftTileBounds.getHeight();
  731. bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left,
  732. bottomLeftTileBounds.bottom,
  733. bottomLeftTileBounds.left + width,
  734. bottomLeftTileBounds.bottom + height);
  735. }
  736. return bounds;
  737. },
  738. /**
  739. * Method: initSingleTile
  740. *
  741. * Parameters:
  742. * bounds - {<OpenLayers.Bounds>}
  743. */
  744. initSingleTile: function(bounds) {
  745. this.events.triggerEvent("retile");
  746. //determine new tile bounds
  747. var center = bounds.getCenterLonLat();
  748. var tileWidth = bounds.getWidth() * this.ratio;
  749. var tileHeight = bounds.getHeight() * this.ratio;
  750. var tileBounds =
  751. new OpenLayers.Bounds(center.lon - (tileWidth/2),
  752. center.lat - (tileHeight/2),
  753. center.lon + (tileWidth/2),
  754. center.lat + (tileHeight/2));
  755. var px = this.map.getLayerPxFromLonLat({
  756. lon: tileBounds.left,
  757. lat: tileBounds.top
  758. });
  759. if (!this.grid.length) {
  760. this.grid[0] = [];
  761. }
  762. var tile = this.grid[0][0];
  763. if (!tile) {
  764. tile = this.addTile(tileBounds, px);
  765. this.addTileMonitoringHooks(tile);
  766. tile.draw();
  767. this.grid[0][0] = tile;
  768. } else {
  769. tile.moveTo(tileBounds, px);
  770. }
  771. //remove all but our single tile
  772. this.removeExcessTiles(1,1);
  773. // store the resolution of the grid
  774. this.gridResolution = this.getServerResolution();
  775. },
  776. /**
  777. * Method: calculateGridLayout
  778. * Generate parameters for the grid layout.
  779. *
  780. * Parameters:
  781. * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
  782. * object with a 'left' and 'top' properties.
  783. * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
  784. * object with a 'lon' and 'lat' properties.
  785. * resolution - {Number}
  786. *
  787. * Returns:
  788. * {Object} Object containing properties tilelon, tilelat, startcol,
  789. * startrow
  790. */
  791. calculateGridLayout: function(bounds, origin, resolution) {
  792. var tilelon = resolution * this.tileSize.w;
  793. var tilelat = resolution * this.tileSize.h;
  794. var offsetlon = bounds.left - origin.lon;
  795. var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
  796. var rowSign = this.rowSign;
  797. var offsetlat = rowSign * (origin.lat - bounds.top + tilelat);
  798. var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign;
  799. return {
  800. tilelon: tilelon, tilelat: tilelat,
  801. startcol: tilecol, startrow: tilerow
  802. };
  803. },
  804. /**
  805. * Method: getTileOrigin
  806. * Determine the origin for aligning the grid of tiles. If a <tileOrigin>
  807. * property is supplied, that will be returned. Otherwise, the origin
  808. * will be derived from the layer's <maxExtent> property. In this case,
  809. * the tile origin will be the corner of the <maxExtent> given by the
  810. * <tileOriginCorner> property.
  811. *
  812. * Returns:
  813. * {<OpenLayers.LonLat>} The tile origin.
  814. */
  815. getTileOrigin: function() {
  816. var origin = this.tileOrigin;
  817. if (!origin) {
  818. var extent = this.getMaxExtent();
  819. var edges = ({
  820. "tl": ["left", "top"],
  821. "tr": ["right", "top"],
  822. "bl": ["left", "bottom"],
  823. "br": ["right", "bottom"]
  824. })[this.tileOriginCorner];
  825. origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
  826. }
  827. return origin;
  828. },
  829. /**
  830. * Method: getTileBoundsForGridIndex
  831. *
  832. * Parameters:
  833. * row - {Number} The row of the grid
  834. * col - {Number} The column of the grid
  835. *
  836. * Returns:
  837. * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
  838. */
  839. getTileBoundsForGridIndex: function(row, col) {
  840. var origin = this.getTileOrigin();
  841. var tileLayout = this.gridLayout;
  842. var tilelon = tileLayout.tilelon;
  843. var tilelat = tileLayout.tilelat;
  844. var startcol = tileLayout.startcol;
  845. var startrow = tileLayout.startrow;
  846. var rowSign = this.rowSign;
  847. return new OpenLayers.Bounds(
  848. origin.lon + (startcol + col) * tilelon,
  849. origin.lat - (startrow + row * rowSign) * tilelat * rowSign,
  850. origin.lon + (startcol + col + 1) * tilelon,
  851. origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign
  852. );
  853. },
  854. /**
  855. * Method: initGriddedTiles
  856. *
  857. * Parameters:
  858. * bounds - {<OpenLayers.Bounds>}
  859. */
  860. initGriddedTiles:function(bounds) {
  861. this.events.triggerEvent("retile");
  862. // work out mininum number of rows and columns; this is the number of
  863. // tiles required to cover the viewport plus at least one for panning
  864. var viewSize = this.map.getSize();
  865. var origin = this.getTileOrigin();
  866. var resolution = this.map.getResolution(),
  867. serverResolution = this.getServerResolution(),
  868. ratio = resolution / serverResolution,
  869. tileSize = {
  870. w: this.tileSize.w / ratio,
  871. h: this.tileSize.h / ratio
  872. };
  873. var minRows = Math.ceil(viewSize.h/tileSize.h) +
  874. 2 * this.buffer + 1;
  875. var minCols = Math.ceil(viewSize.w/tileSize.w) +
  876. 2 * this.buffer + 1;
  877. var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution);
  878. this.gridLayout = tileLayout;
  879. var tilelon = tileLayout.tilelon;
  880. var tilelat = tileLayout.tilelat;
  881. var layerContainerDivLeft = this.map.layerContainerOriginPx.x;
  882. var layerContainerDivTop = this.map.layerContainerOriginPx.y;
  883. var tileBounds = this.getTileBoundsForGridIndex(0, 0);
  884. var startPx = this.map.getViewPortPxFromLonLat(
  885. new OpenLayers.LonLat(tileBounds.left, tileBounds.top)
  886. );
  887. startPx.x = Math.round(startPx.x) - layerContainerDivLeft;
  888. startPx.y = Math.round(startPx.y) - layerContainerDivTop;
  889. var tileData = [], center = this.map.getCenter();
  890. var rowidx = 0;
  891. do {
  892. var row = this.grid[rowidx];
  893. if (!row) {
  894. row = [];
  895. this.grid.push(row);
  896. }
  897. var colidx = 0;
  898. do {
  899. tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx);
  900. var px = startPx.clone();
  901. px.x = px.x + colidx * Math.round(tileSize.w);
  902. px.y = px.y + rowidx * Math.round(tileSize.h);
  903. var tile = row[colidx];
  904. if (!tile) {
  905. tile = this.addTile(tileBounds, px);
  906. this.addTileMonitoringHooks(tile);
  907. row.push(tile);
  908. } else {
  909. tile.moveTo(tileBounds, px, false);
  910. }
  911. var tileCenter = tileBounds.getCenterLonLat();
  912. tileData.push({
  913. tile: tile,
  914. distance: Math.pow(tileCenter.lon - center.lon, 2) +
  915. Math.pow(tileCenter.lat - center.lat, 2)
  916. });
  917. colidx += 1;
  918. } while ((tileBounds.right <= bounds.right + tilelon * this.buffer)
  919. || colidx < minCols);
  920. rowidx += 1;
  921. } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer)
  922. || rowidx < minRows);
  923. //shave off exceess rows and colums
  924. this.removeExcessTiles(rowidx, colidx);
  925. var resolution = this.getServerResolution();
  926. // store the resolution of the grid
  927. this.gridResolution = resolution;
  928. //now actually draw the tiles
  929. tileData.sort(function(a, b) {
  930. return a.distance - b.distance;
  931. });
  932. for (var i=0, ii=tileData.length; i<ii; ++i) {
  933. tileData[i].tile.draw();
  934. }
  935. },
  936. /**
  937. * Method: getMaxExtent
  938. * Get this layer's maximum extent. (Implemented as a getter for
  939. * potential specific implementations in sub-classes.)
  940. *
  941. * Returns:
  942. * {<OpenLayers.Bounds>}
  943. */
  944. getMaxExtent: function() {
  945. return this.maxExtent;
  946. },
  947. /**
  948. * APIMethod: addTile
  949. * Create a tile, initialize it, and add it to the layer div.
  950. *
  951. * Parameters
  952. * bounds - {<OpenLayers.Bounds>}
  953. * position - {<OpenLayers.Pixel>}
  954. *
  955. * Returns:
  956. * {<OpenLayers.Tile>} The added OpenLayers.Tile
  957. */
  958. addTile: function(bounds, position) {
  959. var tile = new this.tileClass(
  960. this, position, bounds, null, this.tileSize, this.tileOptions
  961. );
  962. this.events.triggerEvent("addtile", {tile: tile});
  963. return tile;
  964. },
  965. /**
  966. * Method: addTileMonitoringHooks
  967. * This function takes a tile as input and adds the appropriate hooks to
  968. * the tile so that the layer can keep track of the loading tiles.
  969. *
  970. * Parameters:
  971. * tile - {<OpenLayers.Tile>}
  972. */
  973. addTileMonitoringHooks: function(tile) {
  974. var replacingCls = 'olTileReplacing';
  975. tile.onLoadStart = function() {
  976. //if that was first tile then trigger a 'loadstart' on the layer
  977. if (this.loading === false) {
  978. this.loading = true;
  979. this.events.triggerEvent("loadstart");
  980. }
  981. this.events.triggerEvent("tileloadstart", {tile: tile});
  982. this.numLoadingTiles++;
  983. if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) {
  984. OpenLayers.Element.addClass(tile.getTile(), replacingCls);
  985. }
  986. };
  987. tile.onLoadEnd = function(evt) {
  988. this.numLoadingTiles--;
  989. var aborted = evt.type === 'unload';
  990. this.events.triggerEvent("tileloaded", {
  991. tile: tile,
  992. aborted: aborted
  993. });
  994. if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) {
  995. var tileDiv = tile.getTile();
  996. if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') {
  997. var bufferTile = document.getElementById(tile.id + '_bb');
  998. if (bufferTile) {
  999. bufferTile.parentNode.removeChild(bufferTile);
  1000. }
  1001. }
  1002. OpenLayers.Element.removeClass(tileDiv, replacingCls);
  1003. }
  1004. //if that was the last tile, then trigger a 'loadend' on the layer
  1005. if (this.numLoadingTiles === 0) {
  1006. if (this.backBuffer) {
  1007. if (this.backBuffer.childNodes.length === 0) {
  1008. // no tiles transitioning, remove immediately
  1009. this.removeBackBuffer();
  1010. } else {
  1011. // wait until transition has ended or delay has passed
  1012. this._transitionElement = aborted ?
  1013. this.div.lastChild : tile.imgDiv;
  1014. var transitionendEvents = this.transitionendEvents;
  1015. for (var i=transitionendEvents.length-1; i>=0; --i) {
  1016. OpenLayers.Event.observe(this._transitionElement,
  1017. transitionendEvents[i],
  1018. this._removeBackBuffer);
  1019. }
  1020. // the removal of the back buffer is delayed to prevent
  1021. // flash effects due to the animation of tile displaying
  1022. this.backBufferTimerId = window.setTimeout(
  1023. this._removeBackBuffer, this.removeBackBufferDelay
  1024. );
  1025. }
  1026. }
  1027. this.loading = false;
  1028. this.events.triggerEvent("loadend");
  1029. }
  1030. };
  1031. tile.onLoadError = function() {
  1032. this.events.triggerEvent("tileerror", {tile: tile});
  1033. };
  1034. tile.events.on({
  1035. "loadstart": tile.onLoadStart,
  1036. "loadend": tile.onLoadEnd,
  1037. "unload": tile.onLoadEnd,
  1038. "loaderror": tile.onLoadError,
  1039. scope: this
  1040. });
  1041. },
  1042. /**
  1043. * Method: removeTileMonitoringHooks
  1044. * This function takes a tile as input and removes the tile hooks
  1045. * that were added in addTileMonitoringHooks()
  1046. *
  1047. * Parameters:
  1048. * tile - {<OpenLayers.Tile>}
  1049. */
  1050. removeTileMonitoringHooks: function(tile) {
  1051. tile.unload();
  1052. tile.events.un({
  1053. "loadstart": tile.onLoadStart,
  1054. "loadend": tile.onLoadEnd,
  1055. "unload": tile.onLoadEnd,
  1056. "loaderror": tile.onLoadError,
  1057. scope: this
  1058. });
  1059. },
  1060. /**
  1061. * Method: moveGriddedTiles
  1062. */
  1063. moveGriddedTiles: function() {
  1064. var buffer = this.buffer + 1;
  1065. while(true) {
  1066. var tlTile = this.grid[0][0];
  1067. var tlViewPort = {
  1068. x: tlTile.position.x +
  1069. this.map.layerContainerOriginPx.x,
  1070. y: tlTile.position.y +
  1071. this.map.layerContainerOriginPx.y
  1072. };
  1073. var ratio = this.getServerResolution() / this.map.getResolution();
  1074. var tileSize = {
  1075. w: Math.round(this.tileSize.w * ratio),
  1076. h: Math.round(this.tileSize.h * ratio)
  1077. };
  1078. if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
  1079. this.shiftColumn(true, tileSize);
  1080. } else if (tlViewPort.x < -tileSize.w * buffer) {
  1081. this.shiftColumn(false, tileSize);
  1082. } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
  1083. this.shiftRow(true, tileSize);
  1084. } else if (tlViewPort.y < -tileSize.h * buffer) {
  1085. this.shiftRow(false, tileSize);
  1086. } else {
  1087. break;
  1088. }
  1089. }
  1090. },
  1091. /**
  1092. * Method: shiftRow
  1093. * Shifty grid work
  1094. *
  1095. * Parameters:
  1096. * prepend - {Boolean} if true, prepend to beginning.
  1097. * if false, then append to end
  1098. * tileSize - {Object} rendered tile size; object with w and h properties
  1099. */
  1100. shiftRow: function(prepend, tileSize) {
  1101. var grid = this.grid;
  1102. var rowIndex = prepend ? 0 : (grid.length - 1);
  1103. var sign = prepend ? -1 : 1;
  1104. var rowSign = this.rowSign;
  1105. var tileLayout = this.gridLayout;
  1106. tileLayout.startrow += sign * rowSign;
  1107. var modelRow = grid[rowIndex];
  1108. var row = grid[prepend ? 'pop' : 'shift']();
  1109. for (var i=0, len=row.length; i<len; i++) {
  1110. var tile = row[i];
  1111. var position = modelRow[i].position.clone();
  1112. position.y += tileSize.h * sign;
  1113. tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
  1114. }
  1115. grid[prepend ? 'unshift' : 'push'](row);
  1116. },
  1117. /**
  1118. * Method: shiftColumn
  1119. * Shift grid work in the other dimension
  1120. *
  1121. * Parameters:
  1122. * prepend - {Boolean} if true, prepend to beginning.
  1123. * if false, then append to end
  1124. * tileSize - {Object} rendered tile size; object with w and h properties
  1125. */
  1126. shiftColumn: function(prepend, tileSize) {
  1127. var grid = this.grid;
  1128. var colIndex = prepend ? 0 : (grid[0].length - 1);
  1129. var sign = prepend ? -1 : 1;
  1130. var tileLayout = this.gridLayout;
  1131. tileLayout.startcol += sign;
  1132. for (var i=0, len=grid.length; i<len; i++) {
  1133. var row = grid[i];
  1134. var position = row[colIndex].position.clone();
  1135. var tile = row[prepend ? 'pop' : 'shift']();
  1136. position.x += tileSize.w * sign;
  1137. tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
  1138. row[prepend ? 'unshift' : 'push'](tile);
  1139. }
  1140. },
  1141. /**
  1142. * Method: removeExcessTiles
  1143. * When the size of the map or the buffer changes, we may need to
  1144. * remove some excess rows and columns.
  1145. *
  1146. * Parameters:
  1147. * rows - {Integer} Maximum number of rows we want our grid to have.
  1148. * columns - {Integer} Maximum number of columns we want our grid to have.
  1149. */
  1150. removeExcessTiles: function(rows, columns) {
  1151. var i, l;
  1152. // remove extra rows
  1153. while (this.grid.length > rows) {
  1154. var row = this.grid.pop();
  1155. for (i=0, l=row.length; i<l; i++) {
  1156. var tile = row[i];
  1157. this.destroyTile(tile);
  1158. }
  1159. }
  1160. // remove extra columns
  1161. for (i=0, l=this.grid.length; i<l; i++) {
  1162. while (this.grid[i].length > columns) {
  1163. var row = this.grid[i];
  1164. var tile = row.pop();
  1165. this.destroyTile(tile);
  1166. }
  1167. }
  1168. },
  1169. /**
  1170. * Method: onMapResize
  1171. * For singleTile layers, this will set a new tile size according to the
  1172. * dimensions of the map pane.
  1173. */
  1174. onMapResize: function() {
  1175. if (this.singleTile) {
  1176. this.clearGrid();
  1177. this.setTileSize();
  1178. }
  1179. },
  1180. /**
  1181. * APIMethod: getTileBounds
  1182. * Returns The tile bounds for a layer given a pixel location.
  1183. *
  1184. * Parameters:
  1185. * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
  1186. *
  1187. * Returns:
  1188. * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
  1189. */
  1190. getTileBounds: function(viewPortPx) {
  1191. var maxExtent = this.maxExtent;
  1192. var resolution = this.getResolution();
  1193. var tileMapWidth = resolution * this.tileSize.w;
  1194. var tileMapHeight = resolution * this.tileSize.h;
  1195. var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
  1196. var tileLeft = maxExtent.left + (tileMapWidth *
  1197. Math.floor((mapPoint.lon -
  1198. maxExtent.left) /
  1199. tileMapWidth));
  1200. var tileBottom = maxExtent.bottom + (tileMapHeight *
  1201. Math.floor((mapPoint.lat -
  1202. maxExtent.bottom) /
  1203. tileMapHeight));
  1204. return new OpenLayers.Bounds(tileLeft, tileBottom,
  1205. tileLeft + tileMapWidth,
  1206. tileBottom + tileMapHeight);
  1207. },
  1208. CLASS_NAME: "OpenLayers.Layer.Grid"
  1209. });