text: /* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for 
   * full list of contributors). Published under the Clear BSD license.  
   * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
   * full text of the license. */
  
  
  /**
   * @requires OpenLayers/Layer/HTTPRequest.js
   * @requires OpenLayers/Console.js
   */
  
  /**
   * Class: OpenLayers.Layer.Grid
   * Base class for layers that use a lattice of tiles.  Create a new grid
   * layer with the <OpenLayers.Layer.Grid> constructor.
   *
   * Inherits from:
   *  - <OpenLayers.Layer.HTTPRequest>
   */
  OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
      
      /**
       * APIProperty: tileSize
       * {<OpenLayers.Size>}
       */
      tileSize: null,
  
      /**
       * Property: tileOriginCorner
       * {String} If the <tileOrigin> property is not provided, the tile origin 
       *     will be derived from the layer's <maxExtent>.  The corner of the 
       *     <maxExtent> used is determined by this property.  Acceptable values
       *     are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
       *     (bottom right).  Default is "bl".
       */
      tileOriginCorner: "bl",
      
      /**
       * APIProperty: tileOrigin
       * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
       *     If provided, requests for tiles at all resolutions will be aligned
       *     with this location (no tiles shall overlap this location).  If
       *     not provided, the grid of tiles will be aligned with the layer's
       *     <maxExtent>.  Default is ``null``.
       */
      tileOrigin: null,
      
      /** APIProperty: tileOptions
       *  {Object} optional configuration options for <OpenLayers.Tile> instances
       *  created by this Layer, if supported by the tile class.
       */
      tileOptions: null,
      
      /**
       * Property: grid
       * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is 
       *     an array of tiles.
       */
      grid: null,
  
      /**
       * APIProperty: singleTile
       * {Boolean} Moves the layer into single-tile mode, meaning that one tile 
       *     will be loaded. The tile's size will be determined by the 'ratio'
       *     property. When the tile is dragged such that it does not cover the 
       *     entire viewport, it is reloaded.
       */
      singleTile: false,
  
      /** APIProperty: ratio
       *  {Float} Used only when in single-tile mode, this specifies the 
       *          ratio of the size of the single tile to the size of the map.
       */
      ratio: 1.5,
  
      /**
       * APIProperty: buffer
       * {Integer} Used only when in gridded mode, this specifies the number of 
       *           extra rows and colums of tiles on each side which will
       *           surround the minimum grid tiles to cover the map.
       *           For very slow loading layers, a larger value may increase
       *           performance somewhat when dragging, but will increase bandwidth
       *           use significantly. 
       */
      buffer: 0,
  
      /**
       * APIProperty: numLoadingTiles
       * {Integer} How many tiles are still loading?
       */
      numLoadingTiles: 0,
  
      /**
       * APIProperty: tileLoadingDelay
       * {Integer} - Number of milliseconds before we shift and load
       *     tiles. Default is 100.
       */
      tileLoadingDelay: 100,
  
      /**
       * Property: timerId
       * {Number} - The id of the tileLoadingDelay timer.
       */
      timerId: null,
  
      /**
       * Constructor: OpenLayers.Layer.Grid
       * Create a new grid layer
       *
       * Parameters:
       * name - {String}
       * url - {String}
       * params - {Object}
       * options - {Object} Hashtable of extra options to tag onto the layer
       */
      initialize: function(name, url, params, options) {
          OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, 
                                                                  arguments);
          
          //grid layers will trigger 'tileloaded' when each new tile is 
          // loaded, as a means of progress update to listeners.
          // listeners can access 'numLoadingTiles' if they wish to keep track
          // of the loading progress
          //
          this.events.addEventType("tileloaded");
  
          this.grid = [];
          
          this._moveGriddedTiles = OpenLayers.Function.bind(
              this.moveGriddedTiles, this
          );
      },
  
      /**
       * Method: removeMap
       * Called when the layer is removed from the map.
       *
       * Parameters:
       * map - {<OpenLayers.Map>} The map.
       */
      removeMap: function(map) {
          if(this.timerId != null) {
              window.clearTimeout(this.timerId);
              this.timerId = null;
          }
      },
  
      /**
       * APIMethod: destroy
       * Deconstruct the layer and clear the grid.
       */
      destroy: function() {
          this.clearGrid();
          this.grid = null;
          this.tileSize = null;
          OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); 
      },
  
      /**
       * Method: clearGrid
       * Go through and remove all tiles from the grid, calling
       *    destroy() on each of them to kill circular references
       */
      clearGrid:function() {
          if (this.grid) {
              for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
                  var row = this.grid[iRow];
                  for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
                      var tile = row[iCol];
                      this.removeTileMonitoringHooks(tile);
                      tile.destroy();
                  }
              }
              this.grid = [];
          }
      },
  
      /**
       * APIMethod: clone
       * Create a clone of this layer
       *
       * Parameters:
       * obj - {Object} Is this ever used?
       * 
       * Returns:
       * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
       */
      clone: function (obj) {
          
          if (obj == null) {
              obj = new OpenLayers.Layer.Grid(this.name,
                                              this.url,
                                              this.params,
                                              this.getOptions());
          }
  
          //get all additions from superclasses
          obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
  
          // copy/set any non-init, non-simple values here
          if (this.tileSize != null) {
              obj.tileSize = this.tileSize.clone();
          }
          
          // we do not want to copy reference to grid, so we make a new array
          obj.grid = [];
  
          return obj;
      },    
  
      /**
       * Method: moveTo
       * This function is called whenever the map is moved. All the moving
       * of actual 'tiles' is done by the map, but moveTo's role is to accept
       * a bounds and make sure the data that that bounds requires is pre-loaded.
       *
       * Parameters:
       * bounds - {<OpenLayers.Bounds>}
       * zoomChanged - {Boolean}
       * dragging - {Boolean}
       */
      moveTo:function(bounds, zoomChanged, dragging) {
          OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
          
          bounds = bounds || this.map.getExtent();
  
          if (bounds != null) {
               
              // if grid is empty or zoom has changed, we *must* re-tile
              var forceReTile = !this.grid.length || zoomChanged;
  
              // total bounds of the tiles
              var tilesBounds = this.getTilesBounds();            
        
              if (this.singleTile) {
                  
                  // We want to redraw whenever even the slightest part of the 
                  //  current bounds is not contained by our tile.
                  //  (thus, we do not specify partial -- its default is false)
                  if ( forceReTile || 
                       (!dragging && !tilesBounds.containsBounds(bounds))) {
                      this.initSingleTile(bounds);
                  }
              } else {
               
                  // if the bounds have changed such that they are not even 
                  //  *partially* contained by our tiles (IE user has 
                  //  programmatically panned to the other side of the earth) 
                  //  then we want to reTile (thus, partial true).  
                  //
                  if (forceReTile || !tilesBounds.containsBounds(bounds, true)) {
                      this.initGriddedTiles(bounds);
                  } else {
                      this.scheduleMoveGriddedTiles();
                  }
              }
          }
      },
  
      /**
       * Method: moveByPx
       * Move the layer based on pixel vector.
       *
       * Parameters:
       * dx - {Number}
       * dy - {Number}
       */
      moveByPx: function(dx, dy) {
          this.scheduleMoveGriddedTiles();
      },
  
      /**
       * Method: scheduleMoveGriddedTiles
       * Schedule the move of tiles.
       */
      scheduleMoveGriddedTiles: function() {
          if (this.timerId != null) {
              window.clearTimeout(this.timerId);
          }
          this.timerId = window.setTimeout(
              this._moveGriddedTiles,
              this.tileLoadingDelay
          );
      },
      
      /**
       * APIMethod: setTileSize
       * Check if we are in singleTile mode and if so, set the size as a ratio
       *     of the map size (as specified by the layer's 'ratio' property).
       * 
       * Parameters:
       * size - {<OpenLayers.Size>}
       */
      setTileSize: function(size) { 
          if (this.singleTile) {
              size = this.map.getSize();
              size.h = parseInt(size.h * this.ratio);
              size.w = parseInt(size.w * this.ratio);
          } 
          OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
      },
          
      /**
       * Method: getGridBounds
       * Deprecated. This function will be removed in 3.0. Please use 
       *     getTilesBounds() instead.
       * 
       * Returns:
       * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
       * currently loaded tiles (including those partially or not at all seen 
       * onscreen)
       */
      getGridBounds: function() {
          var msg = "The getGridBounds() function is deprecated. It will be " +
                    "removed in 3.0. Please use getTilesBounds() instead.";
          OpenLayers.Console.warn(msg);
          return this.getTilesBounds();
      },
  
      /**
       * APIMethod: getTilesBounds
       * Return the bounds of the tile grid.
       *
       * Returns:
       * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
       *     currently loaded tiles (including those partially or not at all seen 
       *     onscreen).
       */
      getTilesBounds: function() {    
          var bounds = null; 
          
          if (this.grid.length) {
              var bottom = this.grid.length - 1;
              var bottomLeftTile = this.grid[bottom][0];
      
              var right = this.grid[0].length - 1; 
              var topRightTile = this.grid[0][right];
      
              bounds = new OpenLayers.Bounds(bottomLeftTile.bounds.left, 
                                             bottomLeftTile.bounds.bottom,
                                             topRightTile.bounds.right, 
                                             topRightTile.bounds.top);
              
          }   
          return bounds;
      },
  
      /**
       * Method: initSingleTile
       * 
       * Parameters: 
       * bounds - {<OpenLayers.Bounds>}
       */
      initSingleTile: function(bounds) {
  
          //determine new tile bounds
          var center = bounds.getCenterLonLat();
          var tileWidth = bounds.getWidth() * this.ratio;
          var tileHeight = bounds.getHeight() * this.ratio;
                                         
          var tileBounds = 
              new OpenLayers.Bounds(center.lon - (tileWidth/2),
                                    center.lat - (tileHeight/2),
                                    center.lon + (tileWidth/2),
                                    center.lat + (tileHeight/2));
    
          var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
          var px = this.map.getLayerPxFromLonLat(ul);
  
          if (!this.grid.length) {
              this.grid[0] = [];
          }
  
          var tile = this.grid[0][0];
          if (!tile) {
              tile = this.addTile(tileBounds, px);
              
              this.addTileMonitoringHooks(tile);
              tile.draw();
              this.grid[0][0] = tile;
          } else {
              tile.moveTo(tileBounds, px);
          }           
          
          //remove all but our single tile
          this.removeExcessTiles(1,1);
      },
  
      /** 
       * Method: calculateGridLayout
       * Generate parameters for the grid layout.
       *
       * Parameters:
       * bounds - {<OpenLayers.Bound>}
       * origin - {<OpenLayers.LonLat>}
       * resolution - {Number}
       *
       * Returns:
       * Object containing properties tilelon, tilelat, tileoffsetlat,
       * tileoffsetlat, tileoffsetx, tileoffsety
       */
      calculateGridLayout: function(bounds, origin, resolution) {
          var tilelon = resolution * this.tileSize.w;
          var tilelat = resolution * this.tileSize.h;
          
          var offsetlon = bounds.left - origin.lon;
          var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
          var tilecolremain = offsetlon/tilelon - tilecol;
          var tileoffsetx = -tilecolremain * this.tileSize.w;
          var tileoffsetlon = origin.lon + tilecol * tilelon;
          
          var offsetlat = bounds.top - (origin.lat + tilelat);  
          var tilerow = Math.ceil(offsetlat/tilelat) + this.buffer;
          var tilerowremain = tilerow - offsetlat/tilelat;
          var tileoffsety = -tilerowremain * this.tileSize.h;
          var tileoffsetlat = origin.lat + tilerow * tilelat;
          
          return { 
            tilelon: tilelon, tilelat: tilelat,
            tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat,
            tileoffsetx: tileoffsetx, tileoffsety: tileoffsety
          };
  
      },
      
      /**
       * Method: getTileOrigin
       * Determine the origin for aligning the grid of tiles.  If a <tileOrigin>
       *     property is supplied, that will be returned.  Otherwise, the origin
       *     will be derived from the layer's <maxExtent> property.  In this case,
       *     the tile origin will be the corner of the <maxExtent> given by the 
       *     <tileOriginCorner> property.
       *
       * Returns:
       * {<OpenLayers.LonLat>} The tile origin.
       */
      getTileOrigin: function() {
          var origin = this.tileOrigin;
          if (!origin) {
              var extent = this.getMaxExtent();
              var edges = ({
                  "tl": ["left", "top"],
                  "tr": ["right", "top"],
                  "bl": ["left", "bottom"],
                  "br": ["right", "bottom"]
              })[this.tileOriginCorner];
              origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
          }
          return origin;
      },
  
      /**
       * Method: initGriddedTiles
       * 
       * Parameters:
       * bounds - {<OpenLayers.Bounds>}
       */
      initGriddedTiles:function(bounds) {
          
          // work out mininum number of rows and columns; this is the number of
          // tiles required to cover the viewport plus at least one for panning
  
          var viewSize = this.map.getSize();
          var minRows = Math.ceil(viewSize.h/this.tileSize.h) + 
                        Math.max(1, 2 * this.buffer);
          var minCols = Math.ceil(viewSize.w/this.tileSize.w) +
                        Math.max(1, 2 * this.buffer);
          
          var origin = this.getTileOrigin();
          var resolution = this.map.getResolution();
          
          var tileLayout = this.calculateGridLayout(bounds, origin, resolution);
  
          var tileoffsetx = Math.round(tileLayout.tileoffsetx); // heaven help us
          var tileoffsety = Math.round(tileLayout.tileoffsety);
  
          var tileoffsetlon = tileLayout.tileoffsetlon;
          var tileoffsetlat = tileLayout.tileoffsetlat;
          
          var tilelon = tileLayout.tilelon;
          var tilelat = tileLayout.tilelat;
  
          this.origin = new OpenLayers.Pixel(tileoffsetx, tileoffsety);
  
          var startX = tileoffsetx; 
          var startLon = tileoffsetlon;
  
          var rowidx = 0;
          
          var layerContainerDivLeft = parseInt(this.map.layerContainerDiv.style.left);
          var layerContainerDivTop = parseInt(this.map.layerContainerDiv.style.top);
          
      
          do {
              var row = this.grid[rowidx++];
              if (!row) {
                  row = [];
                  this.grid.push(row);
              }
  
              tileoffsetlon = startLon;
              tileoffsetx = startX;
              var colidx = 0;
   
              do {
                  var tileBounds = 
                      new OpenLayers.Bounds(tileoffsetlon, 
                                            tileoffsetlat, 
                                            tileoffsetlon + tilelon,
                                            tileoffsetlat + tilelat);
  
                  var x = tileoffsetx;
                  x -= layerContainerDivLeft;
  
                  var y = tileoffsety;
                  y -= layerContainerDivTop;
  
                  var px = new OpenLayers.Pixel(x, y);
                  var tile = row[colidx++];
                  if (!tile) {
                      tile = this.addTile(tileBounds, px);
                      this.addTileMonitoringHooks(tile);
                      row.push(tile);
                  } else {
                      tile.moveTo(tileBounds, px, false);
                  }
       
                  tileoffsetlon += tilelon;       
                  tileoffsetx += this.tileSize.w;
              } while ((tileoffsetlon <= bounds.right + tilelon * this.buffer)
                       || colidx < minCols);
               
              tileoffsetlat -= tilelat;
              tileoffsety += this.tileSize.h;
          } while((tileoffsetlat >= bounds.bottom - tilelat * this.buffer)
                  || rowidx < minRows);
          
          //shave off exceess rows and colums
          this.removeExcessTiles(rowidx, colidx);
  
          //now actually draw the tiles
          this.spiralTileLoad();
      },
  
      /**
       * Method: getMaxExtent
       * Get this layer's maximum extent. (Implemented as a getter for
       *     potential specific implementations in sub-classes.)
       *
       * Returns:
       * {OpenLayers.Bounds}
       */
      getMaxExtent: function() {
          return this.maxExtent;
      },
      
      /**
       * Method: spiralTileLoad
       *   Starts at the top right corner of the grid and proceeds in a spiral 
       *    towards the center, adding tiles one at a time to the beginning of a 
       *    queue. 
       * 
       *   Once all the grid's tiles have been added to the queue, we go back 
       *    and iterate through the queue (thus reversing the spiral order from 
       *    outside-in to inside-out), calling draw() on each tile. 
       */
      spiralTileLoad: function() {
          var tileQueue = [];
   
          var directions = ["right", "down", "left", "up"];
  
          var iRow = 0;
          var iCell = -1;
          var direction = OpenLayers.Util.indexOf(directions, "right");
          var directionsTried = 0;
          
          while( directionsTried < directions.length) {
  
              var testRow = iRow;
              var testCell = iCell;
  
              switch (directions[direction]) {
                  case "right":
                      testCell++;
                      break;
                  case "down":
                      testRow++;
                      break;
                  case "left":
                      testCell--;
                      break;
                  case "up":
                      testRow--;
                      break;
              } 
      
              // if the test grid coordinates are within the bounds of the 
              //  grid, get a reference to the tile.
              var tile = null;
              if ((testRow < this.grid.length) && (testRow >= 0) &&
                  (testCell < this.grid[0].length) && (testCell >= 0)) {
                  tile = this.grid[testRow][testCell];
              }
              
              if ((tile != null) && (!tile.queued)) {
                  //add tile to beginning of queue, mark it as queued.
                  tileQueue.unshift(tile);
                  tile.queued = true;
                  
                  //restart the directions counter and take on the new coords
                  directionsTried = 0;
                  iRow = testRow;
                  iCell = testCell;
              } else {
                  //need to try to load a tile in a different direction
                  direction = (direction + 1) % 4;
                  directionsTried++;
              }
          } 
          
          // now we go through and draw the tiles in forward order
          for(var i=0, len=tileQueue.length; i<len; i++) {
              var tile = tileQueue[i];
              tile.draw();
              //mark tile as unqueued for the next time (since tiles are reused)
              tile.queued = false;       
          }
      },
  
      /**
       * APIMethod: addTile
       * Create a tile, initialize it, and add it to the layer div. 
       *
       * Parameters
       * bounds - {<OpenLayers.Bounds>}
       * position - {<OpenLayers.Pixel>}
       *
       * Returns:
       * {<OpenLayers.Tile>} The added OpenLayers.Tile
       */
      addTile:function(bounds, position) {
          return new OpenLayers.Tile.Image(this, position, bounds, null, 
                                           this.tileSize, this.tileOptions);
      },
      
      /** 
       * Method: addTileMonitoringHooks
       * This function takes a tile as input and adds the appropriate hooks to 
       *     the tile so that the layer can keep track of the loading tiles.
       * 
       * Parameters: 
       * tile - {<OpenLayers.Tile>}
       */
      addTileMonitoringHooks: function(tile) {
          
          tile.onLoadStart = function() {
              //if that was first tile then trigger a 'loadstart' on the layer
              if (this.numLoadingTiles == 0) {
                  this.events.triggerEvent("loadstart");
              }
              this.numLoadingTiles++;
          };
          tile.events.register("loadstart", this, tile.onLoadStart);
        
          tile.onLoadEnd = function() {
              this.numLoadingTiles--;
              this.events.triggerEvent("tileloaded");
              //if that was the last tile, then trigger a 'loadend' on the layer
              if (this.numLoadingTiles == 0) {
                  this.events.triggerEvent("loadend");
              }
          };
          tile.events.register("loadend", this, tile.onLoadEnd);
          tile.events.register("unload", this, tile.onLoadEnd);
      },
  
      /** 
       * Method: removeTileMonitoringHooks
       * This function takes a tile as input and removes the tile hooks 
       *     that were added in addTileMonitoringHooks()
       * 
       * Parameters: 
       * tile - {<OpenLayers.Tile>}
       */
      removeTileMonitoringHooks: function(tile) {
          tile.unload();
          tile.events.un({
              "loadstart": tile.onLoadStart,
              "loadend": tile.onLoadEnd,
              "unload": tile.onLoadEnd,
              scope: this
          });
      },
      
      /**
       * Method: moveGriddedTiles
       */
      moveGriddedTiles: function() {
          var shifted = true;
          var buffer = this.buffer || 1;
          var tlLayer = this.grid[0][0].position;
          var offsetX = parseInt(this.map.layerContainerDiv.style.left);
          var offsetY = parseInt(this.map.layerContainerDiv.style.top);
          var tlViewPort = tlLayer.add(offsetX, offsetY);
          if (tlViewPort.x > -this.tileSize.w * (buffer - 1)) {
              this.shiftColumn(true);
          } else if (tlViewPort.x < -this.tileSize.w * buffer) {
              this.shiftColumn(false);
          } else if (tlViewPort.y > -this.tileSize.h * (buffer - 1)) {
              this.shiftRow(true);
          } else if (tlViewPort.y < -this.tileSize.h * buffer) {
              this.shiftRow(false);
          } else {
              shifted = false;
          }
          if (shifted) {
              // we may have other row or columns to shift, schedule it
              // with a setTimeout, to give the user a chance to sneak
              // in moveTo's
              this.timerId = window.setTimeout(this._moveGriddedTiles, 0);
          }
      },
  
      /**
       * Method: shiftRow
       * Shifty grid work
       *
       * Parameters:
       * prepend - {Boolean} if true, prepend to beginning.
       *                          if false, then append to end
       */
      shiftRow:function(prepend) {
          var modelRowIndex = (prepend) ? 0 : (this.grid.length - 1);
          var grid = this.grid;
          var modelRow = grid[modelRowIndex];
  
          var resolution = this.map.getResolution();
          var deltaY = (prepend) ? -this.tileSize.h : this.tileSize.h;
          var deltaLat = resolution * -deltaY;
  
          var row = (prepend) ? grid.pop() : grid.shift();
  
          for (var i=0, len=modelRow.length; i<len; i++) {
              var modelTile = modelRow[i];
              var bounds = modelTile.bounds.clone();
              var position = modelTile.position.clone();
              bounds.bottom = bounds.bottom + deltaLat;
              bounds.top = bounds.top + deltaLat;
              position.y = position.y + deltaY;
              row[i].moveTo(bounds, position);
          }
  
          if (prepend) {
              grid.unshift(row);
          } else {
              grid.push(row);
          }
      },
  
      /**
       * Method: shiftColumn
       * Shift grid work in the other dimension
       *
       * Parameters:
       * prepend - {Boolean} if true, prepend to beginning.
       *                          if false, then append to end
       */
      shiftColumn: function(prepend) {
          var deltaX = (prepend) ? -this.tileSize.w : this.tileSize.w;
          var resolution = this.map.getResolution();
          var deltaLon = resolution * deltaX;
  
          for (var i=0, len=this.grid.length; i<len; i++) {
              var row = this.grid[i];
              var modelTileIndex = (prepend) ? 0 : (row.length - 1);
              var modelTile = row[modelTileIndex];
              
              var bounds = modelTile.bounds.clone();
              var position = modelTile.position.clone();
              bounds.left = bounds.left + deltaLon;
              bounds.right = bounds.right + deltaLon;
              position.x = position.x + deltaX;
  
              var tile = prepend ? this.grid[i].pop() : this.grid[i].shift();
              tile.moveTo(bounds, position);
              if (prepend) {
                  row.unshift(tile);
              } else {
                  row.push(tile);
              }
          }
      },
      
      /**
       * Method: removeExcessTiles
       * When the size of the map or the buffer changes, we may need to
       *     remove some excess rows and columns.
       * 
       * Parameters:
       * rows - {Integer} Maximum number of rows we want our grid to have.
       * columns - {Integer} Maximum number of columns we want our grid to have.
       */
      removeExcessTiles: function(rows, columns) {
          
          // remove extra rows
          while (this.grid.length > rows) {
              var row = this.grid.pop();
              for (var i=0, l=row.length; i<l; i++) {
                  var tile = row[i];
                  this.removeTileMonitoringHooks(tile);
                  tile.destroy();
              }
          }
          
          // remove extra columns
          while (this.grid[0].length > columns) {
              for (var i=0, l=this.grid.length; i<l; i++) {
                  var row = this.grid[i];
                  var tile = row.pop();
                  this.removeTileMonitoringHooks(tile);
                  tile.destroy();
              }
          }
      },
  
      /**
       * Method: onMapResize
       * For singleTile layers, this will set a new tile size according to the
       * dimensions of the map pane.
       */
      onMapResize: function() {
          if (this.singleTile) {
              this.clearGrid();
              this.setTileSize();
          }
      },
      
      /**
       * APIMethod: getTileBounds
       * Returns The tile bounds for a layer given a pixel location.
       *
       * Parameters:
       * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
       *
       * Returns:
       * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
       */
      getTileBounds: function(viewPortPx) {
          var maxExtent = this.maxExtent;
          var resolution = this.getResolution();
          var tileMapWidth = resolution * this.tileSize.w;
          var tileMapHeight = resolution * this.tileSize.h;
          var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
          var tileLeft = maxExtent.left + (tileMapWidth *
                                           Math.floor((mapPoint.lon -
                                                       maxExtent.left) /
                                                      tileMapWidth));
          var tileBottom = maxExtent.bottom + (tileMapHeight *
                                               Math.floor((mapPoint.lat -
                                                           maxExtent.bottom) /
                                                          tileMapHeight));
          return new OpenLayers.Bounds(tileLeft, tileBottom,
                                       tileLeft + tileMapWidth,
                                       tileBottom + tileMapHeight);
      },
      
      CLASS_NAME: "OpenLayers.Layer.Grid"
  });
  