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

View File

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

View File

@@ -98,6 +98,13 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
*/
tileLoadingDelay: 100,
/**
* Property: serverResolutions
* {Array(Number}} This property is documented in subclasses as
* an API property.
*/
serverResolutions: null,
/**
* Property: timerId
* {Number} - The id of the tileLoadingDelay timer.
@@ -221,8 +228,9 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
* dragging - {Boolean}
*/
moveTo:function(bounds, zoomChanged, dragging) {
OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
bounds = bounds || this.map.getExtent();
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
// current bounds is not contained by our tile.
// (thus, we do not specify partial -- its default is false)
if ( forceReTile ||
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)) {
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);
} else {
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
* Move the layer based on pixel vector.
@@ -471,7 +584,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
* 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
@@ -482,7 +595,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
Math.max(1, 2 * this.buffer);
var origin = this.getTileOrigin();
var resolution = this.map.getResolution();
var resolution = this.getServerResolution();
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 layerContainerDivTop = parseInt(this.map.layerContainerDiv.style.top);
do {
var row = this.grid[rowidx++];
@@ -713,17 +826,25 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
moveGriddedTiles: function() {
var shifted = true;
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 offsetY = parseInt(this.map.layerContainerDiv.style.top);
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);
} else if (tlViewPort.x < -this.tileSize.w * buffer) {
} else if (tlViewPort.x < -tileSize.w * buffer) {
this.shiftColumn(false);
} else if (tlViewPort.y > -this.tileSize.h * (buffer - 1)) {
} else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
this.shiftRow(true);
} else if (tlViewPort.y < -this.tileSize.h * buffer) {
} else if (tlViewPort.y < -tileSize.h * buffer) {
this.shiftRow(false);
} else {
shifted = false;
@@ -749,7 +870,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
var grid = this.grid;
var modelRow = grid[modelRowIndex];
var resolution = this.map.getResolution();
var resolution = this.getServerResolution();
var deltaY = (prepend) ? -this.tileSize.h : this.tileSize.h;
var deltaLat = resolution * -deltaY;
@@ -782,7 +903,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
*/
shiftColumn: function(prepend) {
var deltaX = (prepend) ? -this.tileSize.w : this.tileSize.w;
var resolution = this.map.getResolution();
var resolution = this.getServerResolution();
var deltaLon = resolution * deltaX;
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
* 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
* {Array} A list of all resolutions available on the server. Only set this
* property if the map resolutions differs from the server.
* {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,
@@ -162,12 +171,12 @@ OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
*/
getURL: function (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 y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h));
var z = this.serverResolutions != null ?
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 url = this.url;
if (OpenLayers.Util.isArray(url)) {

View File

@@ -37,8 +37,15 @@ OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
/**
* APIProperty: serverResolutions
* {Array} A list of all resolutions available on the server. Only set this
* property if the map resolutions differs from the server.
* {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. (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,
@@ -102,7 +109,7 @@ OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
* passed-in bounds and appropriate tile size specified as parameters.
*/
getURL: function(bounds) {
var res = this.map.getResolution();
var res = this.getServerResolution();
var bbox = this.maxExtent;
var size = this.tileSize;
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).
*/
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
* {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
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
@@ -316,7 +343,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
getMatrix: function() {
var matrix;
if (!this.matrixIds || this.matrixIds.length === 0) {
matrix = {identifier: this.map.getZoom() + this.zoomOffset};
matrix = {identifier: this.getIdentifier()};
} else {
// get appropriate matrix given the map scale if possible
if ("scaleDenominator" in this.matrixIds[0]) {
@@ -324,7 +351,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
var denom =
OpenLayers.METERS_PER_INCH *
OpenLayers.INCHES_PER_UNIT[this.units] *
this.map.getResolution() / 0.28E-3;
this.getServerResolution() / 0.28E-3;
var diff = Number.POSITIVE_INFINITY;
var delta;
for (var i=0, ii=this.matrixIds.length; i<ii; ++i) {
@@ -336,7 +363,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
}
} else {
// fall back on zoom as index
matrix = this.matrixIds[this.map.getZoom() + this.zoomOffset];
matrix = this.matrixIds[this.getIdentifier()];
}
}
return matrix;
@@ -356,7 +383,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
* (respectively) of the given location within the target tile.
*/
getTileInfo: function(loc) {
var res = this.map.getResolution();
var res = this.getServerResolution();
var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w);
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
* {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,
@@ -139,7 +148,7 @@ OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
* {Object} - an object with x, y and z properties.
*/
getXYZ: function(bounds) {
var res = this.map.getResolution();
var res = this.getServerResolution();
var x = Math.round((bounds.left - this.maxExtent.left) /
(res * this.tileSize.w));
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 z = this.zoomOffset == 0 ?
OpenLayers.Util.indexOf(resolutions, res) :
this.map.getZoom() + this.zoomOffset;
this.getServerZoom() + this.zoomOffset;
var limit = Math.pow(2, z);
if (this.wrapDateLine)

View File

@@ -520,6 +520,8 @@ OpenLayers.Map = OpenLayers.Class({
// the layerContainerDiv is the one that holds all the layers
id = this.id + "_OpenLayers_Container";
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.eventsDiv.appendChild(this.layerContainerDiv);

View File

@@ -90,6 +90,22 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
* Returns:
* {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
@@ -99,7 +115,7 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
setBackBufferData: function() {
this.backBufferData = OpenLayers.Util.extend(this.backBufferData, {
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,
tile = this.getTile(),
backBuffer = data.tile,
resolution = data.resolution,
ratio = resolution ? resolution / map.getResolution() : 1,
prevResolution = data.resolution,
nextResolution = this.getTileResolution(),
ratio = prevResolution ? prevResolution / nextResolution : 1,
// Cases where we don't position and return a back buffer, but only
// 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
noTile = !(tile && tile.childNodes.length > 0),
// (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) {
this.setBackBufferData();
return;
@@ -139,26 +157,26 @@ OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, {
// Create a back buffer tile and add it to the DOM
if (!backBuffer) {
backBuffer = this.createBackBuffer();
backBuffer = this.insertBackBuffer();
// some browsers fire the onload event before the image is
// displayed, so we keep the buffer until the whole layer finished
// loading to avoid visual glitches
layer.events.register("loadend", this, this.resetBackBuffer);
data.tile = backBuffer;
layer.div.insertBefore(backBuffer, tile);
}
// Position the back buffer now that we have one
var lonLat = {lon: data.bounds.left, lat: data.bounds.top},
position = map.getPixelFromLonLat(lonLat),
position = layer.getViewPortPxFromLonLat(lonLat, nextResolution),
containerStyle = map.layerContainerDiv.style,
leftOffset = parseInt(containerStyle.left, 10),
topOffset = parseInt(containerStyle.top, 10),
style = backBuffer.style;
style.left = (position.x - leftOffset) + "px";
style.top = (position.y - topOffset) + "px";
style.width = (this.size.w * ratio) + "px";
style.height = (this.size.h * ratio) + "px";
style.left = (position.x - leftOffset) + "%";
style.top = (position.y - topOffset) + "%";
style.width = (this.size.w * ratio) + "%";
style.height = (this.size.h * ratio) + "%";
return backBuffer;
},

View File

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