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:
@@ -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>
|
||||||
@@ -257,6 +257,26 @@ OpenLayers.Layer = OpenLayers.Class({
|
|||||||
*/
|
*/
|
||||||
wrapDateLine: false,
|
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
|
* Constructor: OpenLayers.Layer
|
||||||
|
|||||||
+155
-6
@@ -80,6 +80,38 @@ OpenLayers.Tile = OpenLayers.Class({
|
|||||||
*/
|
*/
|
||||||
isLoading: false,
|
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.
|
/** 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.
|
* 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.
|
* Nullify references to prevent circular references and memory leaks.
|
||||||
*/
|
*/
|
||||||
destroy:function() {
|
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.layer = null;
|
||||||
this.bounds = null;
|
this.bounds = null;
|
||||||
this.size = null;
|
this.size = null;
|
||||||
@@ -118,6 +157,12 @@ OpenLayers.Tile = OpenLayers.Class({
|
|||||||
|
|
||||||
this.events.destroy();
|
this.events.destroy();
|
||||||
this.events = null;
|
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.
|
* depend on the return to know if they should draw or not.
|
||||||
*/
|
*/
|
||||||
draw: function() {
|
draw: function() {
|
||||||
|
|
||||||
//clear tile's contents and mark as not drawn
|
|
||||||
this.clear();
|
|
||||||
|
|
||||||
var maxExtent = this.layer.maxExtent;
|
var maxExtent = this.layer.maxExtent;
|
||||||
var withinMaxExtent = (maxExtent &&
|
var withinMaxExtent = (maxExtent &&
|
||||||
this.bounds.intersectsBounds(maxExtent, false));
|
this.bounds.intersectsBounds(maxExtent, false));
|
||||||
|
|
||||||
// The only case where we *wouldn't* want to draw the tile is if the
|
// The only case where we *wouldn't* want to draw the tile is if the
|
||||||
// tile is outside its layer's maxExtent.
|
// 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);
|
topLeft.lat);
|
||||||
return bounds;
|
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"
|
CLASS_NAME: "OpenLayers.Tile"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -146,6 +146,15 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
|
|||||||
this.events.triggerEvent("loadstart");
|
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) {
|
if (this.imgDiv == null) {
|
||||||
this.initImgDiv();
|
this.initImgDiv();
|
||||||
}
|
}
|
||||||
@@ -162,9 +171,9 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
|
|||||||
OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,
|
OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,
|
||||||
null, null, imageSize, this.url);
|
null, null, imageSize, this.url);
|
||||||
} else {
|
} else {
|
||||||
this.imgDiv.src = this.url;
|
|
||||||
OpenLayers.Util.modifyDOMElement(this.imgDiv,
|
OpenLayers.Util.modifyDOMElement(this.imgDiv,
|
||||||
null, null, imageSize) ;
|
null, null, imageSize) ;
|
||||||
|
this.imgDiv.src = this.url;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -176,7 +185,7 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
|
|||||||
*/
|
*/
|
||||||
clear: function() {
|
clear: function() {
|
||||||
if(this.imgDiv) {
|
if(this.imgDiv) {
|
||||||
this.imgDiv.style.display = "none";
|
this.hide();
|
||||||
if (OpenLayers.Tile.Image.useBlankTile) {
|
if (OpenLayers.Tile.Image.useBlankTile) {
|
||||||
this.imgDiv.src = OpenLayers.Util.getImagesLocation() + "blank.gif";
|
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.Event.observe( this.imgDiv, "load",
|
||||||
OpenLayers.Function.bind(this.checkImgURL, this) );
|
OpenLayers.Function.bind(this.checkImgURL, this) );
|
||||||
*/
|
*/
|
||||||
|
this.frame.style.zIndex = this.isBackBuffer ? 0 : 1;
|
||||||
this.frame.appendChild(this.imgDiv);
|
this.frame.appendChild(this.imgDiv);
|
||||||
this.layer.div.appendChild(this.frame);
|
this.layer.div.appendChild(this.frame);
|
||||||
|
|
||||||
@@ -300,11 +310,103 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
|
|||||||
if (this.layer) {
|
if (this.layer) {
|
||||||
var loaded = this.layerAlphaHack ? this.imgDiv.firstChild.src : this.imgDiv.src;
|
var loaded = this.layerAlphaHack ? this.imgDiv.firstChild.src : this.imgDiv.src;
|
||||||
if (!OpenLayers.Util.isEquivalentUrl(loaded, this.url)) {
|
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"
|
CLASS_NAME: "OpenLayers.Tile.Image"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user