diff --git a/examples/cache-write.js b/examples/cache-write.js index 8f4ec9e081..e9db31a326 100644 --- a/examples/cache-write.js +++ b/examples/cache-write.js @@ -8,13 +8,10 @@ function init() { div: "map", projection: "EPSG:900913", layers: [ - new OpenLayers.Layer.WMS("OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0", { - layers: "basic" - }, { - eventListeners: { - tileloaded: updateStatus - } - }) + new OpenLayers.Layer.WMS( + "OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0", + {layers: "basic"} + ) ], center: [0, 0], zoom: 1 @@ -38,6 +35,7 @@ function init() { }; // update the number of cached tiles and detect local storage support + map.layers[0].events.on({'tileloaded': updateStatus}); function updateStatus() { if (window.localStorage) { status.innerHTML = localStorage.length + " entries in cache."; diff --git a/examples/data/4_m_citylights_lg.gif b/examples/data/4_m_citylights_lg.gif new file mode 100644 index 0000000000..4bf9b87f69 Binary files /dev/null and b/examples/data/4_m_citylights_lg.gif differ diff --git a/examples/image-layer.html b/examples/image-layer.html index 3d9bbf2aa0..235f9fc8c9 100644 --- a/examples/image-layer.html +++ b/examples/image-layer.html @@ -19,14 +19,12 @@ function init(){ map = new OpenLayers.Map('map'); - var options = {numZoomLevels: 3}; - var graphic = new OpenLayers.Layer.Image( 'City Lights', - 'http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif', + 'data/4_m_citylights_lg.gif', new OpenLayers.Bounds(-180, -88.759, 180, 88.759), new OpenLayers.Size(580, 288), - options + {numZoomLevels: 3} ); graphic.events.on({ @@ -38,9 +36,12 @@ } }); - var jpl_wms = new OpenLayers.Layer.WMS( "NASA Global Mosaic", - "http://t1.hypercube.telascience.org/cgi-bin/landsat7", - {layers: "landsat7"}, options); + var jpl_wms = new OpenLayers.Layer.WMS( + "Global Imagery", + "http://demo.opengeo.org/geoserver/wms", + {layers: "bluemarble"}, + {maxExtent: [-160, -88.759, 160, 88.759], numZoomLevels: 3} + ); map.addLayers([graphic, jpl_wms]); map.addControl(new OpenLayers.Control.LayerSwitcher()); diff --git a/lib/OpenLayers/Control/CacheWrite.js b/lib/OpenLayers/Control/CacheWrite.js index 8b4e787e2f..fdd8afce68 100644 --- a/lib/OpenLayers/Control/CacheWrite.js +++ b/lib/OpenLayers/Control/CacheWrite.js @@ -111,7 +111,7 @@ OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, { addLayer: function(evt) { evt.layer.events.on({ tileloadstart: this.makeSameOrigin, - tileloaded: this.cache, + tileloaded: this.onTileLoaded, scope: this }); }, @@ -128,7 +128,7 @@ OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, { removeLayer: function(evt) { evt.layer.events.un({ tileloadstart: this.makeSameOrigin, - tileloaded: this.cache, + tileloaded: this.onTileLoaded, scope: this }); }, @@ -156,6 +156,22 @@ OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, { } }, + /** + * Method: onTileLoaded + * Decides whether a tile can be cached and calls the cache method. + * + * Parameters: + * evt - {Event} + */ + onTileLoaded: function(evt) { + if (this.active && !evt.aborted && + evt.tile instanceof OpenLayers.Tile.Image && + evt.tile.url.substr(0, 5) !== 'data:') { + this.cache({tile: evt.tile}); + delete OpenLayers.Control.CacheWrite.urlMap[evt.tile.url]; + } + }, + /** * Method: cache * Adds a tile to the cache. When the cache is full, the "cachefull" event @@ -166,29 +182,25 @@ OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, { * with the data to add to the cache */ cache: function(obj) { - if (this.active && window.localStorage) { + if (window.localStorage) { var tile = obj.tile; - if (tile instanceof OpenLayers.Tile.Image && - tile.url.substr(0, 5) !== 'data:') { - try { - var canvasContext = tile.getCanvasContext(); - if (canvasContext) { - var urlMap = OpenLayers.Control.CacheWrite.urlMap; - var url = urlMap[tile.url] || tile.url; - window.localStorage.setItem( - "olCache_" + url, - canvasContext.canvas.toDataURL(this.imageFormat) - ); - delete urlMap[tile.url]; - } - } catch(e) { - // local storage full or CORS violation - var reason = e.name || e.message; - if (reason && this.quotaRegEx.test(reason)) { - this.events.triggerEvent("cachefull", {tile: tile}); - } else { - OpenLayers.Console.error(e.toString()); - } + try { + var canvasContext = tile.getCanvasContext(); + if (canvasContext) { + var urlMap = OpenLayers.Control.CacheWrite.urlMap; + var url = urlMap[tile.url] || tile.url; + window.localStorage.setItem( + "olCache_" + url, + canvasContext.canvas.toDataURL(this.imageFormat) + ); + } + } catch(e) { + // local storage full or CORS violation + var reason = e.name || e.message; + if (reason && this.quotaRegEx.test(reason)) { + this.events.triggerEvent("cachefull", {tile: tile}); + } else { + OpenLayers.Console.error(e.toString()); } } } diff --git a/lib/OpenLayers/Layer/ArcGISCache.js b/lib/OpenLayers/Layer/ArcGISCache.js index 27173392c4..e52ea645e6 100644 --- a/lib/OpenLayers/Layer/ArcGISCache.js +++ b/lib/OpenLayers/Layer/ArcGISCache.js @@ -358,6 +358,17 @@ OpenLayers.Layer.ArcGISCache = OpenLayers.Class(OpenLayers.Layer.XYZ, { return OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); }, + /** + * Method: initGriddedTiles + * + * Parameters: + * bounds - {} + */ + initGriddedTiles: function(bounds) { + delete this._tileOrigin; + OpenLayers.Layer.XYZ.prototype.initGriddedTiles.apply(this, arguments); + }, + /** * Method: getMaxExtent * Get this layer's maximum extent. @@ -379,8 +390,11 @@ OpenLayers.Layer.ArcGISCache = OpenLayers.Class(OpenLayers.Layer.XYZ, { * {} The tile origin. */ getTileOrigin: function() { - var extent = this.getMaxExtent(); - return new OpenLayers.LonLat(extent.left, extent.bottom); + if (!this._tileOrigin) { + var extent = this.getMaxExtent(); + this._tileOrigin = new OpenLayers.LonLat(extent.left, extent.bottom); + } + return this._tileOrigin; }, /** diff --git a/lib/OpenLayers/Layer/Grid.js b/lib/OpenLayers/Layer/Grid.js index fcb2b42e64..0985f231bb 100644 --- a/lib/OpenLayers/Layer/Grid.js +++ b/lib/OpenLayers/Layer/Grid.js @@ -262,14 +262,29 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * loaded, as a means of progress update to listeners. * listeners can access 'numLoadingTiles' if they wish to keep * track of the loading progress. Listeners are called with an object - * with a tile property as first argument, making the loded tile - * available to the listener. + * with a 'tile' property as first argument, making the loaded tile + * available to the listener, and an 'aborted' property, which will be + * true when loading was aborted and no tile data is available. * tileerror - Triggered before the tileloaded event (i.e. when the tile is * still hidden) if a tile failed to load. Listeners receive an object * as first argument, which has a tile property that references the * tile that could not be loaded. */ + /** + * Property: gridLayout + * {Object} Object containing properties tilelon, tilelat, startcol, + * startrow + */ + gridLayout: null, + + /** + * Property: rowSign + * {Number} 1 for grids starting at the top, -1 for grids starting at the + * bottom. This is used for several grid index and offset calculations. + */ + rowSign: null, + /** * Constructor: OpenLayers.Layer.Grid * Create a new grid layer @@ -301,6 +316,8 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { this.moveTimerId = null; }, this); } + + this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1; }, /** @@ -363,6 +380,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { } this.grid = []; this.gridResolution = null; + this.gridLayout = null; } }, @@ -866,35 +884,24 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * resolution - {Number} * * Returns: - * {Object} containing properties tilelon, tilelat, tileoffsetlat, - * tileoffsetlat, tileoffsetx, tileoffsety + * {Object} Object containing properties tilelon, tilelat, startcol, + * startrow */ calculateGridLayout: function(bounds, origin, resolution) { var tilelon = resolution * this.tileSize.w; var tilelat = resolution * this.tileSize.h; - var ratio = resolution / this.map.getResolution(), - tileSize = { - w: Math.round(this.tileSize.w * ratio), - h: Math.round(this.tileSize.h * ratio) - }; - var offsetlon = bounds.left - origin.lon; var tilecol = Math.floor(offsetlon/tilelon) - this.buffer; - var tilecolremain = offsetlon/tilelon - tilecol; - var tileoffsetx = -tilecolremain * 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 * tileSize.h; - var tileoffsetlat = origin.lat + tilerow * tilelat; + var rowSign = this.rowSign; + + var offsetlat = rowSign * (origin.lat - bounds.top + tilelat); + var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign; return { tilelon: tilelon, tilelat: tilelat, - tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat, - tileoffsetx: tileoffsetx, tileoffsety: tileoffsety + startcol: tilecol, startrow: tilerow }; }, @@ -925,6 +932,32 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { return origin; }, + /** + * Method: getTileBoundsForGridIndex + * + * Parameters: + * row - {Number} The row of the grid + * col - {Number} The column of the grid + * + * Returns: + * {} The bounds for the tile at (row, col) + */ + getTileBoundsForGridIndex: function(row, col) { + var origin = this.getTileOrigin(); + var tileLayout = this.gridLayout; + var tilelon = tileLayout.tilelon; + var tilelat = tileLayout.tilelat; + var startcol = tileLayout.startcol; + var startrow = tileLayout.startrow; + var rowSign = this.rowSign; + return new OpenLayers.Bounds( + origin.lon + (startcol + col) * tilelon, + origin.lat - (startrow + row * rowSign) * tilelat * rowSign, + origin.lon + (startcol + col + 1) * tilelon, + origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign + ); + }, + /** * Method: initGriddedTiles * @@ -954,50 +987,38 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { 2 * this.buffer + 1; var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution); - var tileoffsetx = Math.round(tileLayout.tileoffsetx); // heaven help us - var tileoffsety = Math.round(tileLayout.tileoffsety); - - var tileoffsetlon = tileLayout.tileoffsetlon; - var tileoffsetlat = tileLayout.tileoffsetlat; + this.gridLayout = tileLayout; var tilelon = tileLayout.tilelon; var tilelat = tileLayout.tilelat; - - var startX = tileoffsetx; - var startLon = tileoffsetlon; - - var rowidx = 0; var layerContainerDivLeft = this.map.layerContainerOriginPx.x; var layerContainerDivTop = this.map.layerContainerOriginPx.y; + var tileBounds = this.getTileBoundsForGridIndex(0, 0); + var startPx = this.map.getViewPortPxFromLonLat( + new OpenLayers.LonLat(tileBounds.left, tileBounds.top) + ); + startPx.x = Math.round(startPx.x) - layerContainerDivLeft; + startPx.y = Math.round(startPx.y) - layerContainerDivTop; + var tileData = [], center = this.map.getCenter(); + + var rowidx = 0; do { - var row = this.grid[rowidx++]; + 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++]; + tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx); + var px = startPx.clone(); + px.x = px.x + colidx * Math.round(tileSize.w); + px.y = px.y + rowidx * Math.round(tileSize.h); + var tile = row[colidx]; if (!tile) { tile = this.addTile(tileBounds, px); this.addTileMonitoringHooks(tile); @@ -1012,14 +1033,12 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { Math.pow(tileCenter.lat - center.lat, 2) }); - tileoffsetlon += tilelon; - tileoffsetx += Math.round(tileSize.w); - } while ((tileoffsetlon <= bounds.right + tilelon * this.buffer) + colidx += 1; + } while ((tileBounds.right <= bounds.right + tilelon * this.buffer) || colidx < minCols); - tileoffsetlat -= tilelat; - tileoffsety += Math.round(tileSize.h); - } while((tileoffsetlat >= bounds.bottom - tilelat * this.buffer) + rowidx += 1; + } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer) || rowidx < minRows); //shave off exceess rows and colums @@ -1090,9 +1109,12 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { this.numLoadingTiles++; }; - tile.onLoadEnd = function() { + tile.onLoadEnd = function(evt) { this.numLoadingTiles--; - this.events.triggerEvent("tileloaded", {tile: tile}); + this.events.triggerEvent("tileloaded", { + tile: tile, + aborted: evt.type === "unload" + }); //if that was the last tile, then trigger a 'loadend' on the layer if (this.tileQueue.length === 0 && this.numLoadingTiles === 0) { this.loading = false; @@ -1194,31 +1216,23 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * if false, then append to end * tileSize - {Object} rendered tile size; object with w and h properties */ - shiftRow:function(prepend, tileSize) { - var modelRowIndex = (prepend) ? 0 : (this.grid.length - 1); + shiftRow: function(prepend, tileSize) { var grid = this.grid; - var modelRow = grid[modelRowIndex]; - + var rowIndex = prepend ? 0 : (grid.length - 1); var sign = prepend ? -1 : 1; - var deltaLat = this.getServerResolution() * -sign * this.tileSize.h; + var rowSign = this.rowSign; + var tileLayout = this.gridLayout; + tileLayout.startrow += sign * rowSign; - var row = (prepend) ? grid.pop() : grid.shift(); - - for (var i=0, len=modelRow.length; i} The bounds for the tile at (row, col) + */ + getTileBoundsForGridIndex: function(row, col) { + var origin = this.getTileOrigin(); + var tileLayout = this.gridLayout; + var tilelon = tileLayout.tilelon; + var tilelat = tileLayout.tilelat; + var minX = (tileLayout.startcol + col) * tilelon; + var minY = (tileLayout.startrow - row) * tilelat; + return new OpenLayers.Bounds( + minX, minY, + minX + tilelon, minY + tilelat + ); + }, + /** * APIMethod: clone * diff --git a/lib/OpenLayers/Layer/MapGuide.js b/lib/OpenLayers/Layer/MapGuide.js index 7bf95018ec..6824b8ad51 100644 --- a/lib/OpenLayers/Layer/MapGuide.js +++ b/lib/OpenLayers/Layer/MapGuide.js @@ -439,41 +439,5 @@ OpenLayers.Layer.MapGuide = OpenLayers.Class(OpenLayers.Layer.Grid, { return requestString; }, - /** - * Method: calculateGridLayout - * Generate parameters for the grid layout. This - * - * Parameters: - * bounds - {} - * origin - {} - * resolution - {Number} - * - * Returns: - * {Object} 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 = origin.lat - bounds.top + tilelat; - var tilerow = Math.floor(offsetlat/tilelat) - this.buffer; - var tilerowremain = tilerow - offsetlat/tilelat; - var tileoffsety = tilerowremain * this.tileSize.h; - var tileoffsetlat = origin.lat - tilelat*tilerow; - - return { - tilelon: tilelon, tilelat: tilelat, - tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat, - tileoffsetx: tileoffsetx, tileoffsety: tileoffsety - }; - }, - CLASS_NAME: "OpenLayers.Layer.MapGuide" }); diff --git a/lib/OpenLayers/Layer/Zoomify.js b/lib/OpenLayers/Layer/Zoomify.js index d0b482ab29..8c5a8a9e4d 100644 --- a/lib/OpenLayers/Layer/Zoomify.js +++ b/lib/OpenLayers/Layer/Zoomify.js @@ -256,41 +256,5 @@ OpenLayers.Layer.Zoomify = OpenLayers.Class(OpenLayers.Layer.Grid, { this.map.maxExtent.top); }, - /** - * Method: calculateGridLayout - * Generate parameters for the grid layout. This - * - * Parameters: - * bounds - {} - * origin - {} - * resolution - {Number} - * - * Returns: - * {Object} 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 = origin.lat - bounds.top + tilelat; - var tilerow = Math.floor(offsetlat/tilelat) - this.buffer; - var tilerowremain = tilerow - offsetlat/tilelat; - var tileoffsety = tilerowremain * this.tileSize.h; - var tileoffsetlat = origin.lat - tilelat*tilerow; - - return { - tilelon: tilelon, tilelat: tilelat, - tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat, - tileoffsetx: tileoffsetx, tileoffsety: tileoffsety - }; - }, - CLASS_NAME: "OpenLayers.Layer.Zoomify" }); diff --git a/lib/OpenLayers/Tile/Image.js b/lib/OpenLayers/Tile/Image.js index e3c412a8ed..8d2fa84f47 100644 --- a/lib/OpenLayers/Tile/Image.js +++ b/lib/OpenLayers/Tile/Image.js @@ -217,8 +217,8 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { if (this.layer instanceof OpenLayers.Layer.Grid) { ratio = this.layer.getServerResolution() / this.layer.map.getResolution(); } - style.left = (this.position.x | 0) + "px"; - style.top = (this.position.y | 0) + "px"; + style.left = this.position.x + "px"; + style.top = this.position.y + "px"; style.width = Math.round(ratio * size.w) + "px"; style.height = Math.round(ratio * size.h) + "px"; }, diff --git a/tests/Control/CacheWrite.html b/tests/Control/CacheWrite.html index fdb6cabeaf..9922569332 100644 --- a/tests/Control/CacheWrite.html +++ b/tests/Control/CacheWrite.html @@ -40,7 +40,7 @@ return; } - t.plan(3); + t.plan(4); OpenLayers.Control.CacheWrite.clearCache(); var length = window.localStorage.length; @@ -68,6 +68,9 @@ // content will be null for browsers that have localStorage but no canvas support var content = canvasContext ? canvasContext.canvas.toDataURL("image/png") : null; t.eq(window.localStorage.getItem("olCache_"+url), content, "localStorage contains correct image data"); + + layer.events.triggerEvent('tileloaded', {aborted: true, tile: layer.grid[1][1]}); + t.eq(window.localStorage.length, length + (canvasContext ? tiles-1 : 0), "tile aborted during load not cached"); var key = Math.random(); window.localStorage.setItem(key, "bar"); diff --git a/tests/Layer/WMS.html b/tests/Layer/WMS.html index 0e9fb9a8fc..cdc00705a3 100644 --- a/tests/Layer/WMS.html +++ b/tests/Layer/WMS.html @@ -533,6 +533,30 @@ map.destroy(); } + + function test_tileBounds(t) { + // do not defer moveGriddedTiles + var isNative = OpenLayers.Animation.isNative; + OpenLayers.Animation.isNative = true; + t.plan(3); + + var map = new OpenLayers.Map("map", {projection: "EPSG:3857"}); + var layer = new OpenLayers.Layer.WMS("wms", "../../img/blank.gif"); + map.addLayer(layer); + map.setCenter([0, 0], 1); + map.pan(2, -100); + map.zoomIn(); + t.eq(layer.grid[1][0].bounds, new OpenLayers.Bounds(-10018754.17, 0, 0, 10018754.17), "no floating point errors after zooming"); + map.setCenter([0, 0], 14); + var bounds = layer.grid[0][0].bounds.clone(); + map.pan(260, 520); + map.pan(-260, -520); + t.eq(layer.grid[0][0].bounds, bounds, "no floating point errors after dragging back and forth"); + t.eq(bounds.right, 0, "0 is 0, and not some super small number"); + + map.destroy(); + OpenLayers.Animation.isNative = isNative; + } diff --git a/tests/Layer/WrapDateLine.html b/tests/Layer/WrapDateLine.html index 700abf3f34..6b84362d59 100644 --- a/tests/Layer/WrapDateLine.html +++ b/tests/Layer/WrapDateLine.html @@ -154,9 +154,9 @@ var m = new OpenLayers.Map('map', {adjustZoom: function(z) {return z;}}); m.addLayer(layer); m.zoomToMaxExtent(); - t.eq(layer.grid[5][7].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=-256&s=221471921.25", "grid[5][7] kamap is okay"); - t.eq(layer.grid[5][6].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=0&s=221471921.25", "grid[5][6] kamap is okay"); - t.eq(layer.grid[5][5].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=-256&s=221471921.25", "grid[5][5] is okay"); + t.eq(layer.grid[4][7].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=-256&s=221471921.25", "grid[5][7] kamap is okay"); + t.eq(layer.grid[4][6].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=0&s=221471921.25", "grid[5][6] kamap is okay"); + t.eq(layer.grid[4][5].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=-256&s=221471921.25", "grid[5][5] is okay"); t.ok(layer.grid[7][6].url == null, "no latitudinal wrapping - tile not loaded if outside maxExtent"); m.destroy(); }