Merge pull request #5 from elemoine/clientzoom

add continuous zooming support for tiled layers
This commit is contained in:
Éric Lemoine
2011-10-07 13:30:50 -07:00
18 changed files with 712 additions and 66 deletions

72
examples/clientzoom.html Normal file
View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>OpenLayers Client Zoom Example</title>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css"/>
<link rel="stylesheet" href="style.css" type="text/css">
<script src="../lib/Firebug/firebug.js"></script>
<script src="../lib/OpenLayers.js"></script>
<script src="clientzoom.js"></script>
<style>
.olControlAttribution {
bottom: 5px;
}
#map {
width: 600px;
height: 400px;
}
</style>
</head>
<body onload="init();">
<h1 id="title">Client Zoom</h1>
<div id="tags">
client zoom continuous zooming
</div>
<p id="shortdesc">
This example demonstrates the <strong>"client zoom"</strong>
functionality, where OpenLayers stretches the layer div when the
resolution is not supported by that layer's tile service.
</p>
<div id="map"></div>
<div id="docs">
<p>
The map of this example is configured with 22 resolutions, while
the OSM tile server supports the first 19 resolutions only. When
the zoom level is 19, 20 or 21 "client zoom" is applied to the OSM
layer, i.e. the OSM layer div is stretched as necessary. The map's
initial zoom is 18. So if you zoom in using the zoom bar's "+"
button you'll effectively trigger "client zoom".
</p>
<p>
For demonstration purpose the map of this example has
<code>fractionalZoom</code> set to true. So "client zoom" also
applies if you choose arbitrary zoom levels using the slider of the
zoom bar, or shift-drag boxes to zoom to arbitrary extents.
"client zoom" therefore allows continous zooming for tiled layers.
</p>
<p>
Enabling "client zoom" on a layer is done by passing
<code>serverResolutions</code> to the layer constructor.
<code>serverResolutions</code> is the list of resolutions supported
by the tile service. See the <a href="clientzoom.js"
target="_blank"> clientzoom.js source</a>.
</p>
</div>
</body>
</html>

43
examples/clientzoom.js Normal file
View File

@@ -0,0 +1,43 @@
var map;
function init() {
map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
units: "m",
maxExtent: new OpenLayers.Bounds(
-20037508.34, -20037508.34, 20037508.34, 20037508.34
),
controls: [],
fractionalZoom: true
});
var osm = new OpenLayers.Layer.OSM(null, null, {
resolutions: [156543.03390625, 78271.516953125, 39135.7584765625,
19567.87923828125, 9783.939619140625, 4891.9698095703125,
2445.9849047851562, 1222.9924523925781, 611.4962261962891,
305.74811309814453, 152.87405654907226, 76.43702827453613,
38.218514137268066, 19.109257068634033, 9.554628534317017,
4.777314267158508, 2.388657133579254, 1.194328566789627,
0.5971642833948135, 0.25, 0.1, 0.05],
serverResolutions: [156543.03390625, 78271.516953125, 39135.7584765625,
19567.87923828125, 9783.939619140625,
4891.9698095703125, 2445.9849047851562,
1222.9924523925781, 611.4962261962891,
305.74811309814453, 152.87405654907226,
76.43702827453613, 38.218514137268066,
19.109257068634033, 9.554628534317017,
4.777314267158508, 2.388657133579254,
1.194328566789627, 0.5971642833948135],
transitionEffect: 'resize'
});
map.addLayers([osm]);
map.addControls([
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.Attribution(),
new OpenLayers.Control.PanZoomBar()
]);
map.setCenter(new OpenLayers.LonLat(659688.852138, 5710701.2962197), 18);
}

View File

@@ -1245,11 +1245,11 @@ OpenLayers.Layer = OpenLayers.Class({
* {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in
* <OpenLayers.LonLat>,translated into view port pixels. * <OpenLayers.LonLat>,translated into view port pixels.
*/ */
getViewPortPxFromLonLat: function (lonlat) { getViewPortPxFromLonLat: function (lonlat, resolution) {
var px = null; var px = null;
if (lonlat != null) { if (lonlat != null) {
var resolution = this.map.getResolution(); resolution = resolution || this.map.getResolution();
var extent = this.map.getExtent(); var extent = this.map.calculateBounds(null, resolution);
px = new OpenLayers.Pixel( px = new OpenLayers.Pixel(
(1/resolution * (lonlat.lon - extent.left)), (1/resolution * (lonlat.lon - extent.left)),
(1/resolution * (extent.top - lonlat.lat)) (1/resolution * (extent.top - lonlat.lat))

View File

@@ -98,6 +98,13 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
*/ */
tileLoadingDelay: 100, tileLoadingDelay: 100,
/**
* Property: serverResolutions
* {Array(Number}} This property is documented in subclasses as
* an API property.
*/
serverResolutions: null,
/** /**
* Property: timerId * Property: timerId
* {Number} - The id of the tileLoadingDelay timer. * {Number} - The id of the tileLoadingDelay timer.
@@ -221,8 +228,9 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
* dragging - {Boolean} * dragging - {Boolean}
*/ */
moveTo:function(bounds, zoomChanged, dragging) { moveTo:function(bounds, zoomChanged, dragging) {
OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments); OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
bounds = bounds || this.map.getExtent(); bounds = bounds || this.map.getExtent();
if (bounds != null) { if (bounds != null) {
@@ -238,18 +246,41 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
// We want to redraw whenever even the slightest part of the // We want to redraw whenever even the slightest part of the
// current bounds is not contained by our tile. // current bounds is not contained by our tile.
// (thus, we do not specify partial -- its default is false) // (thus, we do not specify partial -- its default is false)
if ( forceReTile || if ( forceReTile ||
(!dragging && !tilesBounds.containsBounds(bounds))) { (!dragging && !tilesBounds.containsBounds(bounds))) {
this.initSingleTile(bounds); this.initSingleTile(bounds);
} }
} else { } else {
// if the bounds have changed such that they are not even // if the bounds have changed such that they are not even
// *partially* contained by our tiles (IE user has // *partially* contained by our tiles (IE user has
// programmatically panned to the other side of the earth) // programmatically panned to the other side of the earth)
// then we want to reTile (thus, partial true). // then we want to reTile (thus, partial true).
//
if (forceReTile || !tilesBounds.containsBounds(bounds, true)) { forceReTile = forceReTile ||
!tilesBounds.containsBounds(bounds, true);
var resolution = this.map.getResolution();
var serverResolution =
this.getServerResolution(resolution);
if(resolution !== serverResolution) {
bounds = this.map.calculateBounds(null, serverResolution);
if(forceReTile) {
// stretch the layer div
var scale = serverResolution / resolution;
this.transformDiv(scale);
}
} else {
// reset the layer width, height, left, top, to deal with
// the case where the layer was previously transformed
this.div.style.width = '100%';
this.div.style.height = '100%';
this.div.style.left = '0%';
this.div.style.top = '0%';
}
if(forceReTile) {
this.initGriddedTiles(bounds); this.initGriddedTiles(bounds);
} else { } else {
this.scheduleMoveGriddedTiles(); this.scheduleMoveGriddedTiles();
@@ -258,6 +289,88 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
} }
}, },
/**
* Method: getServerResolution
* Return the server-supported resolution that is the closest to
* the resolution passed as a parameter. If no resolution is
* passed the map resolution is used.
*
* Parameters:
* resolution - {Number} The base resolution.
*
* Returns:
* {Number} The closest server supported resolution.
*/
getServerResolution: function(resolution) {
resolution = resolution || this.map.getResolution();
if(this.serverResolutions &&
OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
var i, serverResolution;
for(var i=this.serverResolutions.length-1; i>= 0; i--) {
serverResolution = this.serverResolutions[i];
if(serverResolution > resolution) {
resolution = serverResolution;
break;
}
}
if(i === -1) {
throw 'no appropriate resolution in serverResolutions';
}
}
return resolution;
},
/**
* Method: getServerZoom
* Return the zoom value corresponding to the best zoom supported by the server
* resolution.
*
* Returns:
* {Number} The closest server supported zoom.
*/
getServerZoom: function() {
return this.map.getZoomForResolution(this.getServerResolution());
},
/**
* Method: transformDiv
* Transform the layer div.
*
* Parameters:
* scale - {Number} The value by which the layer div is to
* be scaled.
*/
transformDiv: function(scale) {
// scale the layer div
this.div.style.width = 100 * scale + '%';
this.div.style.height = 100 * scale + '%';
// and translate the layer div as necessary
var size = this.map.getSize();
var lcX = parseInt(this.map.layerContainerDiv.style.left, 10);
var lcY = parseInt(this.map.layerContainerDiv.style.top, 10);
var x = (lcX - (size.w / 2.)) * (scale - 1);
var y = (lcY - (size.h / 2.)) * (scale - 1);
this.div.style.left = x + '%';
this.div.style.top = y + '%';
},
/**
* Method: getResolutionScale
* Return the value by which the layer is currently scaled.
*
* Returns:
* {Number} The resolution scale.
*/
getResolutionScale: function() {
return parseInt(this.div.style.width, 10) / 100;
},
/** /**
* Method: moveByPx * Method: moveByPx
* Move the layer based on pixel vector. * Move the layer based on pixel vector.
@@ -471,7 +584,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
* bounds - {<OpenLayers.Bounds>} * bounds - {<OpenLayers.Bounds>}
*/ */
initGriddedTiles:function(bounds) { initGriddedTiles:function(bounds) {
// work out mininum number of rows and columns; this is the number of // 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 // tiles required to cover the viewport plus at least one for panning
@@ -482,7 +595,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
Math.max(1, 2 * this.buffer); Math.max(1, 2 * this.buffer);
var origin = this.getTileOrigin(); var origin = this.getTileOrigin();
var resolution = this.map.getResolution(); var resolution = this.getServerResolution();
var tileLayout = this.calculateGridLayout(bounds, origin, resolution); var tileLayout = this.calculateGridLayout(bounds, origin, resolution);
@@ -504,7 +617,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
var layerContainerDivLeft = parseInt(this.map.layerContainerDiv.style.left); var layerContainerDivLeft = parseInt(this.map.layerContainerDiv.style.left);
var layerContainerDivTop = parseInt(this.map.layerContainerDiv.style.top); var layerContainerDivTop = parseInt(this.map.layerContainerDiv.style.top);
do { do {
var row = this.grid[rowidx++]; var row = this.grid[rowidx++];
@@ -713,17 +826,25 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
moveGriddedTiles: function() { moveGriddedTiles: function() {
var shifted = true; var shifted = true;
var buffer = this.buffer || 1; var buffer = this.buffer || 1;
var tlLayer = this.grid[0][0].position; var scale = this.getResolutionScale();
var tlLayer = this.grid[0][0].position.clone();
tlLayer.x *= scale;
tlLayer.y *= scale;
tlLayer = tlLayer.add(parseInt(this.div.style.left, 10),
parseInt(this.div.style.top, 10));
var offsetX = parseInt(this.map.layerContainerDiv.style.left); var offsetX = parseInt(this.map.layerContainerDiv.style.left);
var offsetY = parseInt(this.map.layerContainerDiv.style.top); var offsetY = parseInt(this.map.layerContainerDiv.style.top);
var tlViewPort = tlLayer.add(offsetX, offsetY); var tlViewPort = tlLayer.add(offsetX, offsetY);
if (tlViewPort.x > -this.tileSize.w * (buffer - 1)) { var tileSize = this.tileSize.clone();
tileSize.w *= scale;
tileSize.h *= scale;
if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
this.shiftColumn(true); this.shiftColumn(true);
} else if (tlViewPort.x < -this.tileSize.w * buffer) { } else if (tlViewPort.x < -tileSize.w * buffer) {
this.shiftColumn(false); this.shiftColumn(false);
} else if (tlViewPort.y > -this.tileSize.h * (buffer - 1)) { } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
this.shiftRow(true); this.shiftRow(true);
} else if (tlViewPort.y < -this.tileSize.h * buffer) { } else if (tlViewPort.y < -tileSize.h * buffer) {
this.shiftRow(false); this.shiftRow(false);
} else { } else {
shifted = false; shifted = false;
@@ -749,7 +870,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
var grid = this.grid; var grid = this.grid;
var modelRow = grid[modelRowIndex]; var modelRow = grid[modelRowIndex];
var resolution = this.map.getResolution(); var resolution = this.getServerResolution();
var deltaY = (prepend) ? -this.tileSize.h : this.tileSize.h; var deltaY = (prepend) ? -this.tileSize.h : this.tileSize.h;
var deltaLat = resolution * -deltaY; var deltaLat = resolution * -deltaY;
@@ -782,7 +903,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
*/ */
shiftColumn: function(prepend) { shiftColumn: function(prepend) {
var deltaX = (prepend) ? -this.tileSize.w : this.tileSize.w; var deltaX = (prepend) ? -this.tileSize.w : this.tileSize.w;
var resolution = this.map.getResolution(); var resolution = this.getServerResolution();
var deltaLon = resolution * deltaX; var deltaLon = resolution * deltaX;
for (var i=0, len=this.grid.length; i<len; i++) { for (var i=0, len=this.grid.length; i<len; i++) {
@@ -805,7 +926,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
} }
} }
}, },
/** /**
* Method: removeExcessTiles * Method: removeExcessTiles
* When the size of the map or the buffer changes, we may need to * When the size of the map or the buffer changes, we may need to

View File

@@ -87,8 +87,17 @@ OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
/** /**
* APIProperty: serverResolutions * APIProperty: serverResolutions
* {Array} A list of all resolutions available on the server. Only set this * {Array} A list of all resolutions available on the server. Only set this
* property if the map resolutions differs from the server. * property if the map resolutions differ from the server. This
* property serves two purposes. (a) <serverResolutions> can include
* resolutions that the server supports and that you don't want to
* provide with this layer; you can also look at <zoomOffset>, which is
* an alternative to <serverResolutions> for that specific purpose.
* (b) The map can work with resolutions that aren't supported by
* the server, i.e. that aren't in <serverResolutions>. When the
* map is displayed in such a resolution data for the closest
* server-supported resolution is loaded and the layer div is
* stretched as necessary.
*/ */
serverResolutions: null, serverResolutions: null,
@@ -162,12 +171,12 @@ OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
*/ */
getURL: function (bounds) { getURL: function (bounds) {
bounds = this.adjustBounds(bounds); bounds = this.adjustBounds(bounds);
var res = this.map.getResolution(); var res = this.getServerResolution();
var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w)); var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h)); var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h));
var z = this.serverResolutions != null ? var z = this.serverResolutions != null ?
OpenLayers.Util.indexOf(this.serverResolutions, res) : OpenLayers.Util.indexOf(this.serverResolutions, res) :
this.map.getZoom() + this.zoomOffset; this.getServerZoom() + this.zoomOffset;
var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type; var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type;
var url = this.url; var url = this.url;
if (OpenLayers.Util.isArray(url)) { if (OpenLayers.Util.isArray(url)) {

View File

@@ -37,8 +37,15 @@ OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
/** /**
* APIProperty: serverResolutions * APIProperty: serverResolutions
* {Array} A list of all resolutions available on the server. Only set this * {Array} A list of all resolutions available on the server. Only set this
* property if the map resolutions differs from the server. * property if the map resolutions differ from the server. This
* property serves two purposes. (a) <serverResolutions> can include
* resolutions that the server supports and that you don't want to
* provide with this layer. (b) The map can work with resolutions
* that aren't supported by the server, i.e. that aren't in
* <serverResolutions>. When the map is displayed in such a resolution
* data for the closest server-supported resolution is loaded and the
* layer div is stretched as necessary.
*/ */
serverResolutions: null, serverResolutions: null,
@@ -102,7 +109,7 @@ OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
* passed-in bounds and appropriate tile size specified as parameters. * passed-in bounds and appropriate tile size specified as parameters.
*/ */
getURL: function(bounds) { getURL: function(bounds) {
var res = this.map.getResolution(); var res = this.getServerResolution();
var bbox = this.maxExtent; var bbox = this.maxExtent;
var size = this.tileSize; var size = this.tileSize;
var tileX = Math.round((bounds.left - bbox.left) / (res * size.w)); var tileX = Math.round((bounds.left - bbox.left) / (res * size.w));

View File

@@ -150,7 +150,23 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
* the <matrixIds> property. Defaults to 0 (no zoom offset). * the <matrixIds> property. Defaults to 0 (no zoom offset).
*/ */
zoomOffset: 0, zoomOffset: 0,
/**
* APIProperty: serverResolutions
* {Array} A list of all resolutions available on the server. Only set this
* property if the map resolutions differ from the server. This
* property serves two purposes. (a) <serverResolutions> can include
* resolutions that the server supports and that you don't want to
* provide with this layer; you can also look at <zoomOffset>, which is
* an alternative to <serverResolutions> for that specific purpose.
* (b) The map can work with resolutions that aren't supported by
* the server, i.e. that aren't in <serverResolutions>. When the
* map is displayed in such a resolution data for the closest
* server-supported resolution is loaded and the layer div is
* stretched as necessary.
*/
serverResolutions: null,
/** /**
* Property: formatSuffixMap * Property: formatSuffixMap
* {Object} a map between WMTS 'format' request parameter and tile image file suffix * {Object} a map between WMTS 'format' request parameter and tile image file suffix
@@ -308,6 +324,17 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
// copy/set any non-init, non-simple values here // copy/set any non-init, non-simple values here
return obj; return obj;
}, },
/**
* Method: getIdentifier
* Get the current index in the matrixIds array.
*/
getIdentifier: function() {
return this.serverResolutions != null ?
OpenLayers.Util.indexOf(this.serverResolutions,
this.getServerResolution()) :
this.getServerZoom() + this.zoomOffset;
},
/** /**
* Method: getMatrix * Method: getMatrix
@@ -316,7 +343,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
getMatrix: function() { getMatrix: function() {
var matrix; var matrix;
if (!this.matrixIds || this.matrixIds.length === 0) { if (!this.matrixIds || this.matrixIds.length === 0) {
matrix = {identifier: this.map.getZoom() + this.zoomOffset}; matrix = {identifier: this.getIdentifier()};
} else { } else {
// get appropriate matrix given the map scale if possible // get appropriate matrix given the map scale if possible
if ("scaleDenominator" in this.matrixIds[0]) { if ("scaleDenominator" in this.matrixIds[0]) {
@@ -324,7 +351,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
var denom = var denom =
OpenLayers.METERS_PER_INCH * OpenLayers.METERS_PER_INCH *
OpenLayers.INCHES_PER_UNIT[this.units] * OpenLayers.INCHES_PER_UNIT[this.units] *
this.map.getResolution() / 0.28E-3; this.getServerResolution() / 0.28E-3;
var diff = Number.POSITIVE_INFINITY; var diff = Number.POSITIVE_INFINITY;
var delta; var delta;
for (var i=0, ii=this.matrixIds.length; i<ii; ++i) { for (var i=0, ii=this.matrixIds.length; i<ii; ++i) {
@@ -336,7 +363,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
} }
} else { } else {
// fall back on zoom as index // fall back on zoom as index
matrix = this.matrixIds[this.map.getZoom() + this.zoomOffset]; matrix = this.matrixIds[this.getIdentifier()];
} }
} }
return matrix; return matrix;
@@ -356,7 +383,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
* (respectively) of the given location within the target tile. * (respectively) of the given location within the target tile.
*/ */
getTileInfo: function(loc) { getTileInfo: function(loc) {
var res = this.map.getResolution(); var res = this.getServerResolution();
var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w); var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w);
var fy = (this.tileOrigin.lat - loc.lat) / (res * this.tileSize.h); var fy = (this.tileOrigin.lat - loc.lat) / (res * this.tileSize.h);

View File

@@ -49,7 +49,16 @@ OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
/** /**
* APIProperty: serverResolutions * APIProperty: serverResolutions
* {Array} A list of all resolutions available on the server. Only set this * {Array} A list of all resolutions available on the server. Only set this
* property if the map resolutions differs from the server. * property if the map resolutions differ from the server. This
* property serves two purposes. (a) <serverResolutions> can include
* resolutions that the server supports and that you don't want to
* provide with this layer; you can also look at <zoomOffset>, which is
* an alternative to <serverResolutions> for that specific purpose.
* (b) The map can work with resolutions that aren't supported by
* the server, i.e. that aren't in <serverResolutions>. When the
* map is displayed in such a resolution data for the closest
* server-supported resolution is loaded and the layer div is
* stretched as necessary.
*/ */
serverResolutions: null, serverResolutions: null,
@@ -139,7 +148,7 @@ OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
* {Object} - an object with x, y and z properties. * {Object} - an object with x, y and z properties.
*/ */
getXYZ: function(bounds) { getXYZ: function(bounds) {
var res = this.map.getResolution(); var res = this.getServerResolution();
var x = Math.round((bounds.left - this.maxExtent.left) / var x = Math.round((bounds.left - this.maxExtent.left) /
(res * this.tileSize.w)); (res * this.tileSize.w));
var y = Math.round((this.maxExtent.top - bounds.top) / var y = Math.round((this.maxExtent.top - bounds.top) /
@@ -147,7 +156,7 @@ OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
var resolutions = this.serverResolutions || this.resolutions; var resolutions = this.serverResolutions || this.resolutions;
var z = this.zoomOffset == 0 ? var z = this.zoomOffset == 0 ?
OpenLayers.Util.indexOf(resolutions, res) : OpenLayers.Util.indexOf(resolutions, res) :
this.map.getZoom() + this.zoomOffset; this.getServerZoom() + this.zoomOffset;
var limit = Math.pow(2, z); var limit = Math.pow(2, z);
if (this.wrapDateLine) if (this.wrapDateLine)

View File

@@ -520,6 +520,8 @@ OpenLayers.Map = OpenLayers.Class({
// the layerContainerDiv is the one that holds all the layers // the layerContainerDiv is the one that holds all the layers
id = this.id + "_OpenLayers_Container"; id = this.id + "_OpenLayers_Container";
this.layerContainerDiv = OpenLayers.Util.createDiv(id); this.layerContainerDiv = OpenLayers.Util.createDiv(id);
this.layerContainerDiv.style.width = '100px';
this.layerContainerDiv.style.height = '100px';
this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1; this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
this.eventsDiv.appendChild(this.layerContainerDiv); this.eventsDiv.appendChild(this.layerContainerDiv);

View File

@@ -90,6 +90,22 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
* Returns: * Returns:
* {DOMElement} A copy of the tile's markup. * {DOMElement} A copy of the tile's markup.
*/ */
/**
* Method: getTileResolution
* Get the tile's actual resolution.
*
* Returns:
* {Number}
*/
getTileResolution: function() {
var layer = this.layer,
map = layer.map,
mapResolution = map.getResolution();
return layer.getServerResolution ?
layer.getServerResolution(mapResolution) :
mapResolution;
},
/** /**
* Method: setBackBufferData * Method: setBackBufferData
@@ -99,7 +115,7 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
setBackBufferData: function() { setBackBufferData: function() {
this.backBufferData = OpenLayers.Util.extend(this.backBufferData, { this.backBufferData = OpenLayers.Util.extend(this.backBufferData, {
bounds: this.bounds, bounds: this.bounds,
resolution: this.layer.map.getResolution() resolution: this.getTileResolution()
}); });
}, },
@@ -119,8 +135,9 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
data = this.backBufferData, data = this.backBufferData,
tile = this.getTile(), tile = this.getTile(),
backBuffer = data.tile, backBuffer = data.tile,
resolution = data.resolution, prevResolution = data.resolution,
ratio = resolution ? resolution / map.getResolution() : 1, nextResolution = this.getTileResolution(),
ratio = prevResolution ? prevResolution / nextResolution : 1,
// Cases where we don't position and return a back buffer, but only // Cases where we don't position and return a back buffer, but only
// update backBufferData and return undefined: // update backBufferData and return undefined:
@@ -131,7 +148,8 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
// (3) we don't have a tile available that we could use as buffer // (3) we don't have a tile available that we could use as buffer
noTile = !(tile && tile.childNodes.length > 0), noTile = !(tile && tile.childNodes.length > 0),
// (4) no backbuffer is displayed for a tile that's still loading // (4) no backbuffer is displayed for a tile that's still loading
noBackBuffer = !backBuffer && this.isLoading; noBackBuffer = !backBuffer && this.isLoading;
if (notNeeded || noParent || noTile || noBackBuffer) { if (notNeeded || noParent || noTile || noBackBuffer) {
this.setBackBufferData(); this.setBackBufferData();
return; return;
@@ -139,26 +157,26 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
// Create a back buffer tile and add it to the DOM // Create a back buffer tile and add it to the DOM
if (!backBuffer) { if (!backBuffer) {
backBuffer = this.createBackBuffer(); backBuffer = this.insertBackBuffer();
// some browsers fire the onload event before the image is // some browsers fire the onload event before the image is
// displayed, so we keep the buffer until the whole layer finished // displayed, so we keep the buffer until the whole layer finished
// loading to avoid visual glitches // loading to avoid visual glitches
layer.events.register("loadend", this, this.resetBackBuffer); layer.events.register("loadend", this, this.resetBackBuffer);
data.tile = backBuffer; data.tile = backBuffer;
layer.div.insertBefore(backBuffer, tile);
} }
// Position the back buffer now that we have one // Position the back buffer now that we have one
var lonLat = {lon: data.bounds.left, lat: data.bounds.top}, var lonLat = {lon: data.bounds.left, lat: data.bounds.top},
position = map.getPixelFromLonLat(lonLat), position = layer.getViewPortPxFromLonLat(lonLat, nextResolution),
containerStyle = map.layerContainerDiv.style, containerStyle = map.layerContainerDiv.style,
leftOffset = parseInt(containerStyle.left, 10), leftOffset = parseInt(containerStyle.left, 10),
topOffset = parseInt(containerStyle.top, 10), topOffset = parseInt(containerStyle.top, 10),
style = backBuffer.style; style = backBuffer.style;
style.left = (position.x - leftOffset) + "px";
style.top = (position.y - topOffset) + "px"; style.left = (position.x - leftOffset) + "%";
style.width = (this.size.w * ratio) + "px"; style.top = (position.y - topOffset) + "%";
style.height = (this.size.h * ratio) + "px"; style.width = (this.size.w * ratio) + "%";
style.height = (this.size.h * ratio) + "%";
return backBuffer; return backBuffer;
}, },

View File

@@ -187,10 +187,10 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile.BackBufferable, {
*/ */
positionTile: function() { positionTile: function() {
var style = this.frame.style; var style = this.frame.style;
style.left = this.position.x + "px"; style.left = this.position.x + "%";
style.top = this.position.y + "px"; style.top = this.position.y + "%";
style.width = this.size.w + "px"; style.width = this.size.w + "%";
style.height = this.size.h + "px"; style.height = this.size.h + "%";
}, },
/** /**
@@ -320,14 +320,16 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile.BackBufferable, {
}, },
/** /**
* Method: createBackBuffer * Method: insertBackBuffer
* Create a copy of this tile's markup for the <layer>'s backBufferDiv * Create a copy of this tile's markup and insert it to the layer
* div.
* *
* Returns: * Returns:
* {DOMElement} a clone of the tile content * {DOMElement} The back buffer.
*/ */
createBackBuffer: function() { insertBackBuffer: function() {
var frame = this.frame.cloneNode(false); var frame = this.frame.cloneNode(false);
this.layer.div.insertBefore(frame, this.frame);
frame.appendChild(this.imgDiv); frame.appendChild(this.imgDiv);
this.imgDiv = null; this.imgDiv = null;
return frame; return frame;

View File

@@ -75,8 +75,8 @@
t.eq( tile.url, t.eq( tile.url,
url + "?" + OpenLayers.Util.getParameterString(tParams), url + "?" + OpenLayers.Util.getParameterString(tParams),
"image src is created correctly via addtile" ); "image src is created correctly via addtile" );
t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.top, "6%", "image top is set correctly via addtile" );
t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.left, "5%", "image top is set correctly via addtile" );
var firstChild = layer.div.firstChild.firstChild; var firstChild = layer.div.firstChild.firstChild;
t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" ); t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );

View File

@@ -624,6 +624,195 @@
t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" ); t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" );
} }
function test_getServerResolution(t) {
t.plan(4);
var layer = new OpenLayers.Layer.Grid('', '', {}, {});
var res;
res = layer.getServerResolution(1);
t.eq(res, 1, '[1] getServerResolution return value is correct');
layer.serverResolutions = [2, 1];
res = layer.getServerResolution(1);
t.eq(res, 1, '[2] getServerResolution return value is correct');
layer.serverResolutions = [2];
res = layer.getServerResolution(1);
t.eq(res, 2, '[3] getServerResolution return value is correct');
var exc;
layer.serverResolutions = [0.5];
try {
res = layer.getServerResolution(1);
} catch(e) {
exc = e;
}
t.ok(exc != undefined, '[4] getServerResolution generates exception');
}
function test_getServerZoom(t) {
t.plan(5);
var resolution, zoom;
var map = new OpenLayers.Map('map', {
resolutions: [8, 4, 2, 1, 0.5],
getResolution: function() {
return resolution;
}
});
var layer = new OpenLayers.Layer.WMS('', '', {}, {isBaseLayer: true});
map.addLayer(layer);
resolution = 8;
zoom = layer.getServerZoom();
t.eq(zoom, 0, '[1] getServerZoom return value is correct');
resolution = 4;
zoom = layer.getServerZoom();
t.eq(zoom, 1, '[2] getServerZoom return value is correct');
layer.serverResolutions = [2, 1];
resolution = 1;
zoom = layer.getServerZoom();
t.eq(zoom, 3, '[3] getServerZoom return value is correct');
layer.serverResolutions = [2];
resolution = 0.5;
zoom = layer.getServerZoom();
t.eq(zoom, 2, '[4] getServerZoom return value is correct');
var exc;
layer.serverResolutions = [0.5];
resolution = 1;
try {
zoom = layer.getServerZoom();
} catch(e) {
exc = e;
}
t.ok(exc != undefined, '[4] getServerZoom generates exception');
map.destroy();
}
function test_moveTo_scale(t) {
t.plan(11);
var map = new OpenLayers.Map('map', {
resolutions: [32, 16, 8, 4, 2, 1]
});
var layer = new OpenLayers.Layer.WMS('', '', {}, {
isBaseLayer: true,
serverResolutions: [32, 16, 8]
});
map.addLayer(layer);
// initial resolution is 8
map.setCenter(new OpenLayers.LonLat(0, 0), 2);
// test initial conditions
t.eq(layer.div.style.width, '100%', 'layer div scale is 1');
// change from resolution 8 to 4
map.zoomTo(3);
t.eq(layer.div.style.width, '200%', '[8->4] layer div scale is 2');
// change from resolution 8 to 2
map.zoomTo(2); map.zoomTo(4);
t.eq(layer.div.style.width, '400%', '[8->2] layer div scale is 4');
// change from resolution 8 to 1
map.zoomTo(2); map.zoomTo(5);
t.eq(layer.div.style.width, '800%', '[8->1] layer div scale is 8');
// change from resolution 4 to 2
map.zoomTo(3); map.zoomTo(4);
t.eq(layer.div.style.width, '400%', '[4->2] layer div scale is 4');
// change from resolution 4 to 1
map.zoomTo(3); map.zoomTo(5);
t.eq(layer.div.style.width, '800%', '[4->1] layer div scale is 8');
// change from resolution 2 to 1
map.zoomTo(4); map.zoomTo(5);
t.eq(layer.div.style.width, '800%', '[2->1] layer div scale is 8');
// change from resolution 1 to 2
map.zoomTo(5); map.zoomTo(4);
t.eq(layer.div.style.width, '400%', '[1->2] layer div scale is 4');
// change from resolution 1 to 4
map.zoomTo(5); map.zoomTo(3);
t.eq(layer.div.style.width, '200%', '[1->4] layer div scale is 2');
// change from resolution 1 to 8
map.zoomTo(5); map.zoomTo(2);
t.eq(layer.div.style.width, '100%', '[1->8] layer div scale is 1');
// change from resolution 1 to 16
map.zoomTo(5); map.zoomTo(1);
t.eq(layer.div.style.width, '100%', '[1->16] layer div scale is 1');
map.destroy();
}
function test_transformDiv(t) {
t.plan(8);
var map = new OpenLayers.Map('map4');
var layer = new OpenLayers.Layer.WMS('', '', {}, {
isBaseLayer: true
});
map.addLayer(layer);
map.zoomToMaxExtent();
// the layer container's dimensions are 100px width 100px height
// the position of the viewport center is 384, 256
layer.transformDiv(2);
t.eq(layer.div.style.width, '200%', '[1] layer div has correct width');
t.eq(layer.div.style.height, '200%', '[1] layer div has correct height');
t.eq(layer.div.style.left, '-384%', '[1] layer div has correct left');
t.eq(layer.div.style.top, '-256%', '[1] layer div has correct top');
// now move the layer container and test again
map.layerContainerDiv.style.left = '-1024px';
map.layerContainerDiv.style.top = '768px';
layer.transformDiv(2);
t.eq(layer.div.style.width, '200%', '[2] layer div has correct width');
t.eq(layer.div.style.height, '200%', '[2] layer div has correct height');
t.eq(layer.div.style.left, '-1408%', '[2] layer div has correct left');
t.eq(layer.div.style.top, '512%', '[2] layer div has correct top');
map.destroy();
}
function test_getResolutionScale(t) {
t.plan(1);
var map = new OpenLayers.Map('map');
var layer = new OpenLayers.Layer.WMS('', '', {}, {
isBaseLayer: true
});
map.addLayer(layer);
map.zoomToMaxExtent();
layer.transformDiv(2);
var scale = layer.getResolutionScale();
t.eq(scale, 2, 'getResolutionScale returns correct value');
map.destroy();
}
</script> </script>
</head> </head>
@@ -631,5 +820,6 @@
<div id="map" style="width:499px;height:549px;display:none"></div> <div id="map" style="width:499px;height:549px;display:none"></div>
<div id="map2" style="width:500px;height:550px;display:none"></div> <div id="map2" style="width:500px;height:550px;display:none"></div>
<div id="map3" style="width:594px;height:464px;display:none"></div> <div id="map3" style="width:594px;height:464px;display:none"></div>
<div id="map4" style="width:768px;height:512px;display:none"></div>
</body> </body>
</html> </html>

View File

@@ -56,8 +56,8 @@
t.eq( tile.url, t.eq( tile.url,
url + "?" + OpenLayers.Util.getParameterString(tParams).replace(/,/g, "+"), url + "?" + OpenLayers.Util.getParameterString(tParams).replace(/,/g, "+"),
"image src is created correctly via addtile" ); "image src is created correctly via addtile" );
t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.top, "6%", "image top is set correctly via addtile" );
t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.left, "5%", "image top is set correctly via addtile" );
var firstChild = layer.div.firstChild.firstChild; var firstChild = layer.div.firstChild.firstChild;
t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" ); t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );

View File

@@ -86,8 +86,8 @@
t.eq( tile.url, t.eq( tile.url,
layer.getFullRequestString(tParams), layer.getFullRequestString(tParams),
"image src is created correctly via addtile" ); "image src is created correctly via addtile" );
t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.top, "6%", "image top is set correctly via addtile" );
t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.left, "5%", "image top is set correctly via addtile" );
var firstChild = layer.div.firstChild.firstChild; var firstChild = layer.div.firstChild.firstChild;
t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" ); t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
@@ -117,8 +117,8 @@
t.eq( tile.url, t.eq( tile.url,
layer.getFullRequestString(tParams), layer.getFullRequestString(tParams),
"image src is created correctly via addtile" ); "image src is created correctly via addtile" );
t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.top, "6%", "image top is set correctly via addtile" );
t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" ); t.eq( tile.frame.style.left, "5%", "image top is set correctly via addtile" );
var firstChild = layer.div.firstChild.firstChild; var firstChild = layer.div.firstChild.firstChild;
t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" ); t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );

View File

@@ -258,6 +258,37 @@
t.ok( layer2.grid == null, "tiles appropriately destroyed"); t.ok( layer2.grid == null, "tiles appropriately destroyed");
map.destroy(); map.destroy();
} }
function test_getIdentifier(t) {
t.plan(2);
var map = new OpenLayers.Map('map');
var layer, identifier;
layer = new OpenLayers.Layer.WMTS({
name: "Blue Marble WMTS",
url: "http://example.com/wmts/",
layer: "world",
style: "blue_marble",
matrixSet: "arcgis_online",
tileSize: new OpenLayers.Size(512, 512),
requestEncoding: "REST",
});
map.addLayer(layer);
map.setCenter(new OpenLayers.LonLat(0,0), 5);
layer.zoomOffset = 2;
identifier = layer.getIdentifier();
t.eq(identifier, 7, '[zoomOffset] getIdentifier return value is correct');
layer.serverResolutions = ['offset', 1.40625, 0.703125, 0.3515625, 0.17578125,
0.087890625, 0.0439453125];
identifier = layer.getIdentifier();
t.eq(identifier, 6, '[serverResolutions] getIdentifier return value is correct');
map.destroy();
}
</script> </script>
</head> </head>
<body> <body>

View File

@@ -80,7 +80,7 @@
tile.isLoading = false; tile.isLoading = false;
map.zoomIn(); map.zoomIn();
tile.updateBackBuffer(); tile.updateBackBuffer();
t.eq(tile.backBufferData.tile.style.width, (layer.tileSize.w*2)+"px", t.eq(tile.backBufferData.tile.style.width, (layer.tileSize.w*2)+"%",
"backBuffer frame correctly resized"); "backBuffer frame correctly resized");
map.removeLayer(layer); map.removeLayer(layer);
map.destroy(); map.destroy();
@@ -106,9 +106,123 @@
map.destroy(); map.destroy();
} }
function test_updateBackBuffer_scaled_layer(t) {
t.plan(16);
//
// set up
//
var backBuffer;
var map = new OpenLayers.Map('map', {
resolutions: [32, 16, 8, 4, 2, 1]
});
var serverResolitions = layer.serverResolutions;
layer.serverResolutions = [32, 16, 8];
map.addLayer(layer);
map.setCenter(new OpenLayers.LonLat(0, 0), 2);
tile = new OpenLayers.Tile.Image(layer, position, bounds, null);
tile.backBufferMode = 2; // transition effect
// mock createBackBuffer to avoid removing the image
// div from the tile
tile.insertBackBuffer = function() {
return this.frame.cloneNode(false);
};
tile.removeBackBuffer = function() {
};
//
// test
//
tile.draw();
// check initial state
t.eq(tile.backBufferData.resolution, 8,
'resolution 8 is set in the back buffer data');
tile.isLoading = false;
// change resolution from 8 to 4
map.zoomTo(3);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer == undefined,
'[8->4] updateBackBuffer returns undefined');
// change resolution from 8 to 2
map.zoomTo(2); tile.setBackBufferData(); map.zoomTo(4);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer == undefined,
'[8->2] updateBackBuffer returns undefined');
// change resolution from 16 to 4
map.zoomTo(1); tile.setBackBufferData(); map.zoomTo(3);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer != undefined,
'[16->4] updateBackBuffer returns a back buffer tile');
t.eq(backBuffer.style.width, '512%',
'[16->4] back buffer width is as expected');
t.eq(backBuffer.style.width, '512%',
'[16->4] back buffer height is as expected');
// change resolution from 32 to 1
map.zoomTo(0); tile.setBackBufferData(); map.zoomTo(5);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer != undefined,
'[32->1] updateBackBuffer returns a back buffer tile');
t.eq(backBuffer.style.width, '1024%',
'[32->1] back buffer width is as expected');
t.eq(backBuffer.style.width, '1024%',
'[32->1] back buffer height is as expected');
// change resolution from 4 to 2
map.zoomTo(3); tile.setBackBufferData(); map.zoomTo(4);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer == undefined,
'[4->2] updateBackBuffer returns undefined');
// change resolution from 4 to 1
map.zoomTo(3); tile.setBackBufferData(); map.zoomTo(5);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer == undefined,
'[4->1] updateBackBuffer returns undefined');
// change resolution from 1 to 4
map.zoomTo(5); tile.setBackBufferData(); map.zoomTo(3);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer == undefined,
'[1->4] updateBackBuffer returns undefined');
// change resolution from 4 to 8
map.zoomTo(3); tile.setBackBufferData(); map.zoomTo(2);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer == undefined,
'[4->8] updateBackBuffer returns undefined');
// change resolution from 4 to 16
map.zoomTo(3); tile.setBackBufferData(); map.zoomTo(1);
backBuffer = tile.updateBackBuffer();
t.ok(backBuffer != undefined,
'[4->16] updateBackBuffer returns a back buffer tile');
t.eq(backBuffer.style.width, '128%',
'[4->16] back buffer width is as expected');
t.eq(backBuffer.style.width, '128%',
'[4->16] back buffer height is as expected');
//
// tear down
//
map.removeLayer(layer);
map.destroy();
}
</script> </script>
</head> </head>
<body> <body>
<div id="map" style="height:550px;width:500px"></div> <div id="map" style="height:550px;width:500px"></div>
</body> </body>
</html> </html>

View File

@@ -300,8 +300,8 @@
map.destroy(); map.destroy();
} }
function test_createBackBuffer(t) { function test_insertBackBuffer(t) {
t.plan(3); t.plan(4);
var map = new OpenLayers.Map('map'); var map = new OpenLayers.Map('map');
var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS", var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
@@ -310,8 +310,9 @@
map.setCenter(new OpenLayers.LonLat(0,0), 5); map.setCenter(new OpenLayers.LonLat(0,0), 5);
var tile = layer.grid[0][0]; var tile = layer.grid[0][0];
var img = tile.imgDiv; var img = tile.imgDiv;
var backBuffer = tile.createBackBuffer(); var backBuffer = tile.insertBackBuffer();
t.eq(backBuffer.style.left, tile.frame.style.left, "backBuffer tile has same left style as frame"); t.eq(backBuffer.style.left, tile.frame.style.left, "backBuffer tile has same left style as frame");
t.ok(backBuffer.parentNode === layer.div, "backBuffer inserted into layer div");
t.ok(backBuffer.firstChild === img, "image appended to backBuffer"); t.ok(backBuffer.firstChild === img, "image appended to backBuffer");
t.ok(tile.imgDiv == null, "image reference removed from tile"); t.ok(tile.imgDiv == null, "image reference removed from tile");
map.destroy(); map.destroy();