patch for #831 - simplify the notion of untiled (now an option on grid layers called 'singleTile') and implementing loading events for gridded/untiled layers. thanks tim and chris for reviewing this beast.

git-svn-id: http://svn.openlayers.org/trunk/openlayers@3725 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
euzuro
2007-07-12 22:42:34 +00:00
parent 51b31bc0a0
commit 921347a81a
12 changed files with 1021 additions and 383 deletions

View File

@@ -29,12 +29,26 @@ OpenLayers.Layer.Grid.prototype =
*/
grid: null,
/** APIProperty: ratio
* {Float} Used only when in single-tile mode, this specifies the
* ratio of the size of the single tile to the size of the map.
*/
ratio: 1.5,
/**
* APIProperty: buffer
* {Integer}
* {Integer} Used only when in gridded mode, this specifies the number of
* extra rows and colums of tiles which will surround the minimum
* grid tiles to cover the map.
*/
buffer: 2,
/**
* APIProperty: numLoadingTiles
* {Integer} How many tiles are still loading?
*/
numLoadingTiles: 0,
/**
* Constructor: OpenLayers.Layer.Grid
* Create a new grid layer
@@ -44,10 +58,18 @@ OpenLayers.Layer.Grid.prototype =
* url - {String}
* params - {Object}
* options - {Object} Hashtable of extra options to tag onto the layer
*/
*/
initialize: function(name, url, params, options) {
OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
arguments);
//grid layers will trigger 'tileloaded' when each new tile is
// loaded, as a means of progress update to listeners.
// listeners can access 'numLoadingTiles' if they wish to keep track
// of the loading progress
//
this.events.addEventType("tileloaded");
this.grid = new Array();
},
@@ -72,7 +94,9 @@ OpenLayers.Layer.Grid.prototype =
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();
var tile = row[iCol];
this.removeTileMonitoringHooks(tile);
tile.destroy();
}
}
this.grid = [];
@@ -112,21 +136,6 @@ OpenLayers.Layer.Grid.prototype =
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 - {<OpenLayers.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
@@ -141,48 +150,78 @@ OpenLayers.Layer.Grid.prototype =
moveTo:function(bounds, zoomChanged, dragging) {
OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
if (bounds == null) {
bounds = this.map.getExtent();
}
bounds = bounds || this.map.getExtent();
if (bounds != null) {
if (!this.grid.length || zoomChanged
|| !this.getGridBounds().containsBounds(bounds, true)) {
this._initTiles();
// if grid is empty or zoom has changed, we *must* re-tile
var forceReTile = !this.grid.length || zoomChanged;
// total bounds of the tiles
var tilesBounds = this.getTilesBounds();
if (this.singleTile) {
// We want to redraw whenever even the slightest part of the
// current bounds is not contained by our tile.
// (thus, we do not specify partial -- its default is false)
if ( forceReTile ||
(!dragging && !tilesBounds.containsBounds(bounds))) {
this.initSingleTile(bounds);
}
} else {
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<rl; r++) {
var row = this.grid[r];
for (var c=0, cl=row.length; c<cl; c++) {
var tile = row[c];
if (!tile.drawn && tile.bounds.intersectsBounds(bounds, false)) {
tile.draw();
}
}
}
// if the bounds have changed such that they are not even
// *partially* contained by our tiles (IE user has
// programmatically panned to the other side of the earth)
// then we want to reTile (thus, partial true).
//
if (forceReTile || !tilesBounds.containsBounds(bounds, true)) {
this.initGriddedTiles(bounds);
} else {
//we might have to shift our buffer tiles
this.moveGriddedTiles(bounds);
}
}
}
},
/**
* APIMethod: setTileSize
* Check if we are in singleTile mode and if so, set the size as a ratio
* of the map size (as specified by the layer's 'ratio' property).
*
* Parameters:
* size - {<OpenLayers.Size>}
*/
setTileSize: function(size) {
if (this.singleTile) {
var size = this.map.getSize().clone();
size.h = size.h * this.ratio;
size.w = size.w * this.ratio;
}
OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
},
/**
* Method: getGridBounds
* Deprecated. This function will be removed in 3.0. Please use
* getTilesBounds() instead.
*
* Return:
* {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
* currently loaded tiles (including those partially or not at all seen
* onscreen)
*/
getGridBounds: function() {
var msg = "The getGridBounds() function is deprecated. It will be " +
"removed in 3.0. Please use getTilesBounds() instead.";
OpenLayers.Console.warn(msg);
return getTilesBounds();
},
/**
* Method: getTilesBounds
* Get the bounds of the grid
*
* Return:
@@ -190,33 +229,80 @@ OpenLayers.Layer.Grid.prototype =
* currently loaded tiles (including those partially or not at all seen
* onscreen)
*/
getGridBounds:function() {
getTilesBounds: function() {
var bounds = null;
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);
if (this.grid.length) {
var bottom = this.grid.length - 1;
var bottomLeftTile = this.grid[bottom][0];
var right = this.grid[0].length - 1;
var topRightTile = this.grid[0][right];
bounds = new OpenLayers.Bounds(bottomLeftTile.bounds.left,
bottomLeftTile.bounds.bottom,
topRightTile.bounds.right,
topRightTile.bounds.top);
}
return bounds;
},
/**
* Method: _initTiles
* Initialize the tiles
* Method: initSingleTile
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*/
_initTiles:function() {
initSingleTile: function(bounds) {
//determine new tile bounds
var center = bounds.getCenterLonLat();
var tileWidth = bounds.getWidth() * this.ratio;
var tileHeight = bounds.getHeight() * this.ratio;
var tileBounds =
new OpenLayers.Bounds(center.lon - (tileWidth/2),
center.lat - (tileHeight/2),
center.lon + (tileWidth/2),
center.lat + (tileHeight/2));
var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
var px = this.map.getLayerPxFromLonLat(ul);
if (!this.grid.length) {
this.grid[0] = new Array();
}
var tile = this.grid[0][0];
if (!tile) {
tile = this.addTile(tileBounds, px);
this.addTileMonitoringHooks(tile);
tile.draw();
this.grid[0][0] = tile;
} else {
tile.moveTo(tileBounds, px);
}
//remove all but our single tile
this.removeExcessTiles(1,1);
},
/**
* Method: initGriddedTiles
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*/
initGriddedTiles:function(bounds) {
// work out mininum number of rows and columns; this is the number of
// tiles required to cover the viewport plus 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;
@@ -256,10 +342,11 @@ OpenLayers.Layer.Grid.prototype =
var colidx = 0;
do {
var tileBounds = new OpenLayers.Bounds(tileoffsetlon,
tileoffsetlat,
tileoffsetlon + tilelon,
tileoffsetlat + tilelat);
var tileBounds =
new OpenLayers.Bounds(tileoffsetlon,
tileoffsetlat,
tileoffsetlon + tilelon,
tileoffsetlat + tilelat);
var x = tileoffsetx;
x -= parseInt(this.map.layerContainerDiv.style.left);
@@ -271,6 +358,7 @@ OpenLayers.Layer.Grid.prototype =
var tile = row[colidx++];
if (!tile) {
tile = this.addTile(tileBounds, px);
this.addTileMonitoringHooks(tile);
row.push(tile);
} else {
tile.moveTo(tileBounds, px, false);
@@ -286,23 +374,9 @@ OpenLayers.Layer.Grid.prototype =
} 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<l; i++) {
row[i].destroy();
}
}
// remove extra columns
while (this.grid[0].length > colidx) {
for (var i=0, l=this.grid.length; i<l; i++) {
var row = this.grid[i];
var tile = row.pop();
tile.destroy();
}
}
//shave off exceess rows and colums
this.removeExcessTiles(rowidx, colidx);
//now actually draw the tiles
this.spiralTileLoad();
},
@@ -396,6 +470,87 @@ OpenLayers.Layer.Grid.prototype =
// Should be implemented by subclasses
},
/**
* Method: addTileMonitoringHooks
* This function takes a tile as input and adds the appropriate hooks to
* the tile so that the layer can keep track of the loading tiles.
*
* Parameters:
* tile - {<OpenLayers.Tile>}
*/
addTileMonitoringHooks: function(tile) {
tile.onLoadStart = function() {
//if that was first tile then trigger a 'loadstart' on the layer
if (this.numLoadingTiles == 0) {
this.events.triggerEvent("loadstart");
}
this.numLoadingTiles++;
};
tile.events.register("loadstart", this, tile.onLoadStart);
tile.onLoadEnd = function() {
this.numLoadingTiles--;
this.events.triggerEvent("tileloaded");
//if that was the last tile, then trigger a 'loadend' on the layer
if (this.numLoadingTiles == 0) {
this.events.triggerEvent("loadend");
}
};
tile.events.register("loadend", this, tile.onLoadEnd);
},
/**
* Method: removeTileMonitoringHooks
* This function takes a tile as input and removes the tile hooks
* that were added in addTileMonitoringHooks()
*
* Parameters:
* tile - {<OpenLayers.Tile>}
*/
removeTileMonitoringHooks: function(tile) {
tile.events.unregister("loadstart", this, tile.onLoadStart);
tile.events.unregister("loadend", this, tile.onLoadEnd);
},
/**
* Method: moveGriddedTiles
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*/
moveGriddedTiles: function(bounds) {
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<rl; r++) {
var row = this.grid[r];
for (var c=0, cl=row.length; c<cl; c++) {
var tile = row[c];
if (!tile.drawn &&
tile.bounds.intersectsBounds(bounds, false)) {
tile.draw();
}
}
}
}
},
/**
* Method: shiftRow
* Shifty grid work
@@ -465,6 +620,38 @@ OpenLayers.Layer.Grid.prototype =
}
},
/**
* Method: removeExcessTiles
* When the size of the map or the buffer changes, we may need to
* remove some excess rows and columns.
*
* Parameters:
* rows - {Integer} Maximum number of rows we want our grid to have.
* colums - {Integer} Maximum number of columns we want our grid to have.
*/
removeExcessTiles: function(rows, columns) {
// remove extra rows
while (this.grid.length > rows) {
var row = this.grid.pop();
for (var i=0, l=row.length; i<l; i++) {
var tile = row[i];
this.removeTileMonitoringHooks(tile)
tile.destroy();
}
}
// remove extra columns
while (this.grid[0].length > columns) {
for (var i=0, l=this.grid.length; i<l; i++) {
var row = this.grid[i];
var tile = row.pop();
this.removeTileMonitoringHooks(tile);
tile.destroy();
}
}
},
/** @final @type String */
CLASS_NAME: "OpenLayers.Layer.Grid"
});

View File

@@ -105,9 +105,9 @@ OpenLayers.Layer.KaMap.prototype =
},
/**
* Method: _initTiles
* Method: initGriddedTiles
*/
_initTiles:function() {
initGriddedTiles:function() {
var viewSize = this.map.getSize();
var bounds = this.map.getExtent();

View File

@@ -4,280 +4,43 @@
/**
* @requires OpenLayers/Layer/HTTPRequest.js
* @requires OpenLayers/Layer/WMS.js
*
* Class: OpenLayers.Layer.WMS
* Deprecated, to be removed in 3.0 - instead use OpenLayers.Layer.WMS and
* pass the option 'singleTile' as true.
*
* Inherits from:
* - <OpenLayers.Layer.HTTPRequest>
* - <OpenLayers.Layer.WMS>
*/
OpenLayers.Layer.WMS.Untiled = OpenLayers.Class.create();
OpenLayers.Layer.WMS.Untiled.prototype =
OpenLayers.Class.inherit( OpenLayers.Layer.HTTPRequest, {
/**
* Property: DEFAULT_PARAMS
* Hashtable of default parameter key/value pairs
*/
DEFAULT_PARAMS: { service: "WMS",
version: "1.1.1",
request: "GetMap",
styles: "",
exceptions: "application/vnd.ogc.se_inimage",
format: "image/jpeg"
},
/**
* APIProperty: reproject
* Whether or not to use the base layer as a basis for 'staking' the corners
* of the image geographically.
*/
reproject: true,
/**
* APIProperty: ratio
* {Float} the ratio of image/tile size to map size (this is the untiled
* buffer)
*/
ratio: 2,
OpenLayers.Class.inherit( OpenLayers.Layer.WMS, {
/**
* Property: tile
* {<OpenLayers.Tile.Image>}
* APIProperty: singleTile
* {singleTile} Always true for untiled.
*/
tile: null,
singleTile: true,
/**
* Property: doneLoading
* {Boolean} did the image finish loading before a new draw was initiated?
* Constructor: OpenLayers.Layer.WMS.Untiled
*
* Parameters:
* name - {String}
* url - {String}
* params - {Object}
* options - {Object}
*/
doneLoading: false,
/**
* Constructor: OpenLayers.Layer.WMS.Untiled
*
* Parameters:
* name - {String}
* url - {String}
* params - {Object}
* options - {Object}
*/
initialize: function(name, url, params, options) {
var newArguments = new Array();
//uppercase params
params = OpenLayers.Util.upperCaseObject(params);
newArguments.push(name, url, params, options);
OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
newArguments);
OpenLayers.Util.applyDefaults(
this.params,
OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
);
//layer is transparent
if (this.params.TRANSPARENT &&
this.params.TRANSPARENT.toString().toLowerCase() == "true") {
// unless explicitly set in options, make layer an overlay
if ( (options == null) || (!options.isBaseLayer) ) {
this.isBaseLayer = false;
}
// jpegs can never be transparent, so intelligently switch the
// format, depending on teh browser's capabilities
if (this.params.FORMAT == "image/jpeg") {
this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif"
: "image/png";
}
}
OpenLayers.Layer.WMS.prototype.initialize.apply(this, arguments);
var msg = "The OpenLayers.Layer.WMS.Untiled class is deprecated and " +
"will be removed in 3.0. Instead, you should use the " +
"normal OpenLayers.Layer.WMS class, passing it the option " +
"'singleTile' as true.";
OpenLayers.Console.warn(msg);
},
/**
* APIMethod: destroy
*/
destroy: function() {
if (this.tile) {
this.tile.destroy();
this.tile = null;
}
OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments);
},
/**
* APIMethod: clone
* Makes an exact clone of this OpenLayers.Layer.WMS.Untiled
*
* Parameters:
* obj - {Object}
*
* Returns:
* {<OpenLayers.Layer.WMS.Untiled>}
*/
clone: function (obj) {
if (obj == null) {
obj = new OpenLayers.Layer.WMS.Untiled(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
obj.tile = null;
return obj;
},
/**
* Method: setMap
* Once HTTPRequest has set the map, we can load the image div
*
* Parameters:
* map - {<OpenLayers.Map>}
*/
setMap: function(map) {
OpenLayers.Layer.HTTPRequest.prototype.setMap.apply(this, arguments);
},
/**
* Method: setTileSize
* Set the tile size based on the map size.
*/
setTileSize: function() {
var tileSize = this.map.getSize();
tileSize.w = tileSize.w * this.ratio;
tileSize.h = tileSize.h * this.ratio;
this.tileSize = tileSize;
},
/**
* Method: moveTo
* When it is not a dragging move (ie when done dragging)
* reload and recenter the div.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
* zoomChanged - {Boolean}
* dragging - {Boolean}
*/
moveTo:function(bounds, zoomChanged, dragging) {
if (!this.doneLoading) {
this.events.triggerEvent("loadcancel");
this.doneLoading = true;
}
OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);
if (bounds == null) {
bounds = this.map.getExtent();
}
var firstRendering = (this.tile == null);
//does the new bounds to which we need to move fall outside of the
// current tile's bounds?
var outOfBounds = (!firstRendering &&
!this.tile.bounds.containsBounds(bounds));
if ( zoomChanged || firstRendering || (!dragging && outOfBounds) ) {
//clear out the old tile
if (this.tile) {
this.tile.clear();
}
//determine new tile bounds
var center = bounds.getCenterLonLat();
var tileWidth = bounds.getWidth() * this.ratio;
var tileHeight = bounds.getHeight() * this.ratio;
var tileBounds =
new OpenLayers.Bounds(center.lon - (tileWidth / 2),
center.lat - (tileHeight / 2),
center.lon + (tileWidth / 2),
center.lat + (tileHeight / 2));
//determine new tile size
this.setTileSize();
//formulate request url string
var url = this.getURL(tileBounds);
//determine new position (upper left corner of new bounds)
var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
var pos = this.map.getLayerPxFromLonLat(ul);
if ( this.tile && !this.tile.size.equals(this.tileSize)) {
this.tile.destroy();
this.tile = null;
}
this.events.triggerEvent("loadstart");
this.doneLoading = false;
if (!this.tile) {
this.tile = new OpenLayers.Tile.Image(this, pos, tileBounds,
url, this.tileSize);
this.tile.draw();
var onload = function() {
this.doneLoading = true;
this.events.triggerEvent("loadend");
}
OpenLayers.Event.observe(this.tile.imgDiv, 'load',
onload.bindAsEventListener(this));
} else {
this.tile.moveTo(tileBounds, pos);
}
}
},
/**
* Method: getURL
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*/
getURL: function(bounds) {
return this.getFullRequestString( {'BBOX': bounds.toBBOX(),
'WIDTH': this.tileSize.w,
'HEIGHT': this.tileSize.h} );
},
/**
* Method: setURL
* Once HTTPRequest has updated the url, reload the image div
*
* Parameters:
* newUrl - {String}
*/
setUrl: function(newUrl) {
OpenLayers.Layer.HTTPRequest.prototype.setUrl.apply(this, arguments);
this.redraw();
},
/**
* APIMethod: getFullRequestString
* combine the layer's url with its params and these newParams.
*
* Add the SRS parameter from 'projection' -- this is probably
* more eloquently done via a setProjection() method, but this
* works for now and always.
*
* Parameters:
* newParams - {Object}
*/
getFullRequestString:function(newParams) {
var projection = this.map.getProjection();
this.params.SRS = (projection == "none") ? null : projection;
return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
this, arguments);
},
/** @final @type String */
CLASS_NAME: "OpenLayers.Layer.WMS.Untiled"

View File

@@ -21,6 +21,19 @@ OpenLayers.Tile = OpenLayers.Class.create();
OpenLayers.Tile.prototype = {
/**
* Constant: EVENT_TYPES
* {Array(String)} Supported application event types
*/
EVENT_TYPES: [ "loadstart", "loadend", "reload"],
/**
* APIProperty: events
* {<OpenLayers.Events>} An events object that handles all
* events on the tile.
*/
events: null,
/**
* Property: id
* {String} null
*/
@@ -66,6 +79,12 @@ OpenLayers.Tile.prototype = {
*/
drawn: false,
/**
* Property: isLoading
* {Boolean} Is the tile loading?
*/
isLoading: false,
/** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
* there is no need for the base tile class to have a url.
*
@@ -88,6 +107,8 @@ OpenLayers.Tile.prototype = {
//give the tile a unique id based on its BBOX.
this.id = OpenLayers.Util.createUniqueID("Tile_");
this.events = new OpenLayers.Events(this, null, this.EVENT_TYPES);
},
/**
@@ -99,6 +120,9 @@ OpenLayers.Tile.prototype = {
this.bounds = null;
this.size = null;
this.position = null;
this.events.destroy();
this.events = null;
},
/**

View File

@@ -96,6 +96,15 @@ OpenLayers.Tile.Image.prototype =
if (!OpenLayers.Tile.prototype.draw.apply(this, arguments)) {
return false;
}
if (this.isLoading) {
//if we're already loading, send 'reload' instead of 'loadstart'.
this.events.triggerEvent("reload");
} else {
this.isLoading = true;
this.events.triggerEvent("loadstart");
}
if (this.imgDiv == null) {
this.initImgDiv();
}
@@ -202,6 +211,23 @@ OpenLayers.Tile.Image.prototype =
// we need this reference to check back the viewRequestID
this.imgDiv.map = this.layer.map;
//bind a listener to the onload of the image div so that we
// can register when a tile has finished loading.
var onload = function() {
//normally isLoading should always be true here but there are some
// right funky conditions where loading and then reloading a tile
// with the same url *really*fast*. this check prevents sending
// a 'loadend' if the msg has already been sent
//
if (this.isLoading) {
this.isLoading = false;
this.events.triggerEvent("loadend");
}
}
OpenLayers.Event.observe(this.imgDiv, 'load',
onload.bindAsEventListener(this));
},
/**