Re #933. Apply transition effect patch to trunk, many thanks to Erik, Tim and Chris for support. r=crschmidt, tschaub.

git-svn-id: http://svn.openlayers.org/trunk/openlayers@6452 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
Paul Spencer
2008-03-06 22:50:44 +00:00
parent e7b9a9a41b
commit 3fa5487c37
4 changed files with 349 additions and 9 deletions

69
examples/transition.html Normal file
View File

@@ -0,0 +1,69 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>OpenLayers Transitions Example</title>
<style type="text/css">
#mapDiv {
width: 400px;
height: 400px;
border: 1px solid black;
}
</style>
<script src="../lib/OpenLayers.js"></script>
<script type="text/javascript">
var map;
function init(){
map = new OpenLayers.Map('mapDiv', {maxResolution: 'auto'});
var single_default_effect = new OpenLayers.Layer.WMS(
"WMS untiled default",
"http://labs.metacarta.com/wms/vmap0?",
{layers: 'basic'},
{singleTile: true}
);
var single_resize_effect = new OpenLayers.Layer.WMS(
"WMS untiled resize",
"http://labs.metacarta.com/wms/vmap0?",
{layers: 'basic'},
{singleTile: true, transitionEffect: 'resize'}
);
var tiled_default_effect = new OpenLayers.Layer.WMS(
"WMS tiled default ",
"http://labs.metacarta.com/wms/vmap0?",
{layers: 'basic'}
);
var tiled_resize_effect = new OpenLayers.Layer.WMS(
"WMS tiled resize",
"http://labs.metacarta.com/wms/vmap0?",
{layers: 'basic'},
{transitionEffect: 'resize'}
);
map.addLayers([single_default_effect, single_resize_effect,
tiled_default_effect, tiled_resize_effect]);
map.addControl(new OpenLayers.Control.LayerSwitcher());
map.setCenter(new OpenLayers.LonLat(6.5, 40.5), 4);
}
</script>
</head>
<body onload="init()">
<h1 id="title">Transition Example</h1>
<p id="shortdesc">
Demonstrates the use of transition effects in tiled and untiled layers.
</p>
<div id="mapDiv"></div>
<div id="docs">
There are two transitions that are currently implemented: null (the
default) and 'resize'. The default transition effect is used when no
transition is specified and is implemented as no transition effect except
for panning singleTile layers. The 'resize' effect resamples the current
tile and displays it stretched or compressed until the new tile is available.
<ul>
<li>The first layer is an untiled WMS layer with no transition effect.</li>
<li>The second layer is an untiled WMS layer with a 'resize' effect. </li>
<li>The third layer is a tiled WMS layer with no transition effect. </li>
<li>The fourth layer is a tiled WMS layer with a 'resize' effect. </li>
</ul>
</div>
</body>
</body>
</html>

View File

@@ -257,6 +257,26 @@ OpenLayers.Layer = OpenLayers.Class({
*/
wrapDateLine: false,
/**
* APIProperty: transitionEffect
* {String} The transition effect to use when the map is panned or
* zoomed.
*
* There are currently two supported values:
* - *null* No transition effect (the default).
* - *resize* Existing tiles are resized on zoom to provide a visual
* effect of the zoom having taken place immediately. As the
* new tiles become available, they are drawn over top of the
* resized tiles.
*/
transitionEffect: null,
/**
* Property: SUPPORTED_TRANSITIONS
* {Array} An immutable (that means don't change it!) list of supported
* transitionEffect values.
*/
SUPPORTED_TRANSITIONS: ['resize'],
/**
* Constructor: OpenLayers.Layer

View File

@@ -80,6 +80,38 @@ OpenLayers.Tile = OpenLayers.Class({
*/
isLoading: false,
/**
* Property: isBackBuffer
* {Boolean} Is this tile a back buffer tile?
*/
isBackBuffer: false,
/**
* Property: lastRatio
* {Float} Used in transition code only. This is the previous ratio
* of the back buffer tile resolution to the map resolution. Compared
* with the current ratio to determine if zooming occurred.
*/
lastRatio: 1,
/**
* Property: isFirstDraw
* {Boolean} Is this the first time the tile is being drawn?
* This is used to force resetBackBuffer to synchronize
* the backBufferTile with the foreground tile the first time
* the foreground tile loads so that if the user zooms
* before the layer has fully loaded, the backBufferTile for
* tiles that have been loaded can be used.
*/
isFirstDraw: true,
/**
* Property: backBufferTile
* {<OpenLayers.Tile>} A clone of the tile used to create transition
* effects when the tile is moved or changes resolution.
*/
backBufferTile: null,
/** 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.
*
@@ -111,6 +143,13 @@ OpenLayers.Tile = OpenLayers.Class({
* Nullify references to prevent circular references and memory leaks.
*/
destroy:function() {
if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,
this.layer.transitionEffect) != -1) {
this.layer.events.unregister("loadend", this, this.resetBackBuffer);
this.events.unregister('loadend', this, this.resetBackBuffer);
} else {
this.events.unregister('loadend', this, this.showTile);
}
this.layer = null;
this.bounds = null;
this.size = null;
@@ -118,6 +157,12 @@ OpenLayers.Tile = OpenLayers.Class({
this.events.destroy();
this.events = null;
/* clean up the backBufferTile if it exists */
if (this.backBufferTile) {
this.backBufferTile.destroy();
this.backBufferTile = null;
}
},
/**
@@ -156,17 +201,56 @@ OpenLayers.Tile = OpenLayers.Class({
* depend on the return to know if they should draw or not.
*/
draw: function() {
//clear tile's contents and mark as not drawn
this.clear();
var maxExtent = this.layer.maxExtent;
var withinMaxExtent = (maxExtent &&
this.bounds.intersectsBounds(maxExtent, false));
// The only case where we *wouldn't* want to draw the tile is if the
// tile is outside its layer's maxExtent.
return (withinMaxExtent || this.layer.displayOutsideMaxExtent);
var drawTile = (withinMaxExtent || this.layer.displayOutsideMaxExtent);
if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) {
if (drawTile) {
//we use a clone of this tile to create a double buffer for visual
//continuity. The backBufferTile is used to create transition
//effects while the tile in the grid is repositioned and redrawn
if (!this.backBufferTile) {
this.backBufferTile = this.clone();
this.backBufferTile.hide();
// this is important. It allows the backBuffer to place itself
// appropriately in the DOM. The Image subclass needs to put
// the backBufferTile behind the main tile so the tiles can
// load over top and display as soon as they are loaded.
this.backBufferTile.isBackBuffer = true;
// potentially end any transition effects when the tile loads
this.events.register('loadend', this, this.resetBackBuffer);
// clear transition back buffer tile only after all tiles in
// this layer have loaded to avoid visual glitches
this.layer.events.register("loadend", this, this.resetBackBuffer);
}
// run any transition effects
this.startTransition();
} else {
// if we aren't going to draw the tile, then the backBuffer should
// be hidden too!
if (this.backBufferTile) {
this.backBufferTile.clear();
}
}
} else {
if (drawTile && this.isFirstDraw) {
this.events.register('loadend', this, this.showTile);
this.isFirstDraw = false;
}
}
this.shouldDraw = drawTile;
//clear tile's contents and mark as not drawn
this.clear();
return drawTile;
},
/**
@@ -237,6 +321,71 @@ OpenLayers.Tile = OpenLayers.Class({
topLeft.lat);
return bounds;
},
/**
* Method: startTransition
* Prepare the tile for a transition effect. To be
* implemented by subclasses.
*/
startTransition: function() {},
/**
* Method: resetBackBuffer
* Triggered by two different events, layer loadend, and tile loadend.
* In any of these cases, we check to see if we can hide the
* backBufferTile yet and update its parameters to match the
* foreground tile.
*
* Basic logic:
* - If the backBufferTile hasn't been drawn yet, reset it
* - If layer is still loading, show foreground tile but don't hide
* the backBufferTile yet
* - If layer is done loading, reset backBuffer tile and show
* foreground tile
*/
resetBackBuffer: function() {
this.showTile();
if (this.backBufferTile &&
(this.isFirstDraw || !this.layer.numLoadingTiles)) {
this.isFirstDraw = false;
// check to see if the backBufferTile is within the max extents
// before rendering it
var maxExtent = this.layer.maxExtent;
var withinMaxExtent = (maxExtent &&
this.bounds.intersectsBounds(maxExtent, false));
if (withinMaxExtent) {
this.backBufferTile.position = this.position;
this.backBufferTile.bounds = this.bounds;
this.backBufferTile.size = this.size;
this.backBufferTile.imageSize = this.layer.imageSize || this.size;
this.backBufferTile.imageOffset = this.layer.imageOffset;
this.backBufferTile.resolution = this.layer.getResolution();
this.backBufferTile.renderTile();
}
}
},
/**
* Method: showTile
* Show the tile only if it should be drawn.
*/
showTile: function() {
if (this.shouldDraw) {
this.show();
}
},
/**
* Method: show
* Show the tile. To be implemented by subclasses.
*/
show: function() { },
/**
* Method: hide
* Hide the tile. To be implemented by subclasses.
*/
hide: function() { },
CLASS_NAME: "OpenLayers.Tile"
});

View File

@@ -146,6 +146,15 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
this.events.triggerEvent("loadstart");
}
return this.renderTile();
},
/**
* Method: renderTile
* Internal function to actually initialize the image tile,
* position it correctly, and set its url.
*/
renderTile: function() {
if (this.imgDiv == null) {
this.initImgDiv();
}
@@ -162,9 +171,9 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,
null, null, imageSize, this.url);
} else {
this.imgDiv.src = this.url;
OpenLayers.Util.modifyDOMElement(this.imgDiv,
null, null, imageSize) ;
this.imgDiv.src = this.url;
}
return true;
},
@@ -176,7 +185,7 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
*/
clear: function() {
if(this.imgDiv) {
this.imgDiv.style.display = "none";
this.hide();
if (OpenLayers.Tile.Image.useBlankTile) {
this.imgDiv.src = OpenLayers.Util.getImagesLocation() + "blank.gif";
}
@@ -223,6 +232,7 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
OpenLayers.Event.observe( this.imgDiv, "load",
OpenLayers.Function.bind(this.checkImgURL, this) );
*/
this.frame.style.zIndex = this.isBackBuffer ? 0 : 1;
this.frame.appendChild(this.imgDiv);
this.layer.div.appendChild(this.frame);
@@ -300,11 +310,103 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
if (this.layer) {
var loaded = this.layerAlphaHack ? this.imgDiv.firstChild.src : this.imgDiv.src;
if (!OpenLayers.Util.isEquivalentUrl(loaded, this.url)) {
this.imgDiv.style.display = "none";
this.hide();
}
}
},
/**
* Method: startTransition
* This method is invoked on tiles that are backBuffers for tiles in the
* grid. The grid tile is about to be cleared and a new tile source
* loaded. This is where the transition effect needs to be started
* to provide visual continuity.
*/
startTransition: function() {
// backBufferTile has to be valid and ready to use
if (!this.backBufferTile || !this.backBufferTile.imgDiv) {
return;
}
// calculate the ratio of change between the current resolution of the
// backBufferTile and the layer. If several animations happen in a
// row, then the backBufferTile will scale itself appropriately for
// each request.
var ratio = 1;
if (this.backBufferTile.resolution) {
ratio = this.backBufferTile.resolution / this.layer.getResolution();
}
// if the ratio is not the same as it was last time (i.e. we are
// zooming), then we need to adjust the backBuffer tile
if (ratio != this.lastRatio) {
if (this.layer.transitionEffect == 'resize') {
// In this case, we can just immediately resize the
// backBufferTile.
var upperLeft = new OpenLayers.LonLat(
this.backBufferTile.bounds.left,
this.backBufferTile.bounds.top
);
var size = new OpenLayers.Size(
this.backBufferTile.size.w * ratio,
this.backBufferTile.size.h * ratio
);
var px = this.layer.map.getLayerPxFromLonLat(upperLeft);
OpenLayers.Util.modifyDOMElement(this.backBufferTile.frame,
null, px, size);
var imageSize = this.backBufferTile.imageSize;
imageSize = new OpenLayers.Size(imageSize.w * ratio,
imageSize.h * ratio);
var imageOffset = this.backBufferTile.imageOffset;
if(imageOffset) {
imageOffset = new OpenLayers.Pixel(
imageOffset.x * ratio, imageOffset.y * ratio
);
}
OpenLayers.Util.modifyDOMElement(
this.backBufferTile.imgDiv, null, imageOffset, imageSize
) ;
this.backBufferTile.show();
}
} else {
// default effect is just to leave the existing tile
// until the new one loads if this is a singleTile and
// there was no change in resolution. Otherwise we
// don't bother to show the backBufferTile at all
if (this.layer.singleTile) {
this.backBufferTile.show();
} else {
this.backBufferTile.hide();
}
}
this.lastRatio = ratio;
},
/**
* Method: show
* Show the tile by showing its frame.
*/
show: function() {
this.frame.style.display = '';
// Force a reflow on gecko based browsers to actually show the element
// before continuing execution.
if (navigator.userAgent.toLowerCase().indexOf("gecko") != -1) {
this.frame.scrollLeft = this.frame.scrollLeft;
}
},
/**
* Method: hide
* Hide the tile by hiding its frame.
*/
hide: function() {
this.frame.style.display = 'none';
},
CLASS_NAME: "OpenLayers.Tile.Image"
}
);