/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license. * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt * for the full text of the license. */ /** * @requires OpenLayers/Layer/HTTPRequest.js * * Class: OpenLayers.Layer.Grid * Base class for layers that use a lattice of tiles. Create a new grid * layer with the constructor. * * Inherits from: * - */ OpenLayers.Layer.Grid = OpenLayers.Class.create(); OpenLayers.Layer.Grid.prototype = OpenLayers.Class.inherit( OpenLayers.Layer.HTTPRequest, { /** * APIProperty: tileSize * {} */ tileSize: null, /** * Property: grid * {Array} This is an array of rows, each row is an array of tiles */ grid: null, /** * APIProperty: buffer * {Integer} */ buffer: 2, /** * 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); this.grid = new Array(); }, /** * 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; iRow < this.grid.length; iRow++) { var row = this.grid[iRow]; for(var iCol=0; iCol < row.length; iCol++) { row[iCol].destroy(); } } this.grid = []; } }, /** * APIMethod: clone * Create a clone of this layer * * Parameters: * obj - {Object} Is this ever used? * * Return: * {} 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.options); } //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 = new Array(); return obj; }, /** * Method: setMap * When the layer is added to a map, then we can ask the map for * its default tile size * * Parameters: * map - {} */ setMap: function(map) { OpenLayers.Layer.HTTPRequest.prototype.setMap.apply(this, arguments); if (this.tileSize == null) { this.tileSize = this.map.getTileSize(); } }, /** * 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 - {} * zoomChanged - {Boolean} * dragging - {Boolean} */ moveTo:function(bounds, zoomChanged, dragging) { OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments); if (bounds == null) { bounds = this.map.getExtent(); } if (bounds != null) { if (!this.grid.length || zoomChanged || !this.getGridBounds().containsBounds(bounds, true)) { this._initTiles(); } else { var buffer = (this.buffer) ? this.buffer*1.5 : 1; while (true) { var tlLayer = this.grid[0][0].position; var tlViewPort = this.map.getViewPortPxFromLayerPx(tlLayer); 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 { break; } }; if (this.buffer == 0) { for (var r=0, rl=this.grid.length; r} A Bounds object representing the bounds of all the * currently loaded tiles (including those partially or not at all seen * onscreen) */ getGridBounds:function() { 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]; return new OpenLayers.Bounds(bottomLeftTile.bounds.left, bottomLeftTile.bounds.bottom, topRightTile.bounds.right, topRightTile.bounds.top); }, /** * Method: _initTiles * Initialize the tiles */ _initTiles:function() { // work out mininum number of rows and columns; this is the number of // tiles required to cover the viewport plus one for panning var viewSize = this.map.getSize(); var minRows = Math.ceil(viewSize.h/this.tileSize.h) + 1; var minCols = Math.ceil(viewSize.w/this.tileSize.w) + 1; var bounds = this.map.getExtent(); var extent = this.map.getMaxExtent(); var resolution = this.map.getResolution(); var tilelon = resolution * this.tileSize.w; var tilelat = resolution * this.tileSize.h; var offsetlon = bounds.left - extent.left; var tilecol = Math.floor(offsetlon/tilelon) - this.buffer; var tilecolremain = offsetlon/tilelon - tilecol; var tileoffsetx = -tilecolremain * this.tileSize.w; var tileoffsetlon = extent.left + tilecol * tilelon; var offsetlat = bounds.top - (extent.bottom + tilelat); var tilerow = Math.ceil(offsetlat/tilelat) + this.buffer; var tilerowremain = tilerow - offsetlat/tilelat; var tileoffsety = -tilerowremain * this.tileSize.h; var tileoffsetlat = extent.bottom + tilerow * tilelat; tileoffsetx = Math.round(tileoffsetx); // heaven help us tileoffsety = Math.round(tileoffsety); this.origin = new OpenLayers.Pixel(tileoffsetx, tileoffsety); var startX = tileoffsetx; var startLon = tileoffsetlon; var rowidx = 0; do { var row = this.grid[rowidx++]; if (!row) { row = new Array(); 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 -= parseInt(this.map.layerContainerDiv.style.left); var y = tileoffsety; y -= parseInt(this.map.layerContainerDiv.style.top); var px = new OpenLayers.Pixel(x, y); var tile = row[colidx++]; if (!tile) { tile = this.addTile(tileBounds, px); 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) // remove extra rows while (this.grid.length > rowidx) { var row = this.grid.pop(); for (var i=0, l=row.length; i colidx) { for (var i=0, l=this.grid.length; i= 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; i < tileQueue.length; i++) { var tile = tileQueue[i] tile.draw(); //mark tile as unqueued for the next time (since tiles are reused) tile.queued = false; } }, /** * APIMethod: addTile * Gives subclasses of Grid the opportunity to create an * OpenLayer.Tile of their choosing. The implementer should initialize * the new tile and take whatever steps necessary to display it. * * Parameters * bounds - {} * * Return: * {} The added OpenLayers.Tile */ addTile:function(bounds, position) { // Should be implemented by subclasses }, /** * APIMethod: mergeNewParams * Once params have been changed, we will need to re-init our tiles * * Parameters: * newParams - {Object} Hashtable of new params to use */ mergeNewParams:function(newArguments) { OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this, [newArguments]); if (this.map != null) { this._initTiles(); } }, /** * 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 modelRow = this.grid[modelRowIndex]; var resolution = this.map.getResolution(); var deltaY = (prepend) ? -this.tileSize.h : this.tileSize.h; var deltaLat = resolution * -deltaY; var row = (prepend) ? this.grid.pop() : this.grid.shift(); for (var i=0; i < modelRow.length; 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) { this.grid.unshift(row); } else { this.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; i