OpenLayers OpenLayers

Ticket #933: tranny.patch

File tranny.patch, 16.8 kB (added by tschaub, 10 months ago)

layer transitions

  • lib/OpenLayers/Tile/Image.js

    old new  
    146146            this.events.triggerEvent("loadstart"); 
    147147        } 
    148148         
     149        return this.renderTile(); 
     150    }, 
     151     
     152    /** 
     153     * Method: renderTile 
     154     * Internal function to actually initialize the image tile, 
     155     *     position it correctly, and set its url. 
     156     */ 
     157    renderTile: function() { 
    149158        if (this.imgDiv == null) { 
    150159            this.initImgDiv(); 
    151160        } 
     
    162171            OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv, 
    163172                    null, null, imageSize, this.url); 
    164173        } else { 
    165             this.imgDiv.src = this.url; 
    166174            OpenLayers.Util.modifyDOMElement(this.imgDiv, 
    167175                    null, null, imageSize) ; 
     176            this.imgDiv.src = this.url; 
    168177        } 
    169178        return true; 
    170179    }, 
     
    176185     */ 
    177186    clear: function() { 
    178187        if(this.imgDiv) { 
    179             this.imgDiv.style.display = "none"
     188            this.hide()
    180189            if (OpenLayers.Tile.Image.useBlankTile) {  
    181190                this.imgDiv.src = OpenLayers.Util.getImagesLocation() + "blank.gif"; 
    182191            }     
     
    223232        OpenLayers.Event.observe( this.imgDiv, "load", 
    224233            OpenLayers.Function.bind(this.checkImgURL, this) ); 
    225234        */ 
     235        this.frame.style.zIndex = this.isBackBuffer ? 0 : 1; 
    226236        this.frame.appendChild(this.imgDiv);  
    227237        this.layer.div.appendChild(this.frame);  
    228238 
     
    300310        if (this.layer) { 
    301311            var loaded = this.layerAlphaHack ? this.imgDiv.firstChild.src : this.imgDiv.src; 
    302312            if (!OpenLayers.Util.isEquivalentUrl(loaded, this.url)) { 
    303                 this.imgDiv.style.display = "none"
     313                this.hide()
    304314            } 
    305315        } 
    306316    }, 
     317     
     318    /** 
     319     * Method: startTransition 
     320     * This method is invoked on tiles that are backBuffers for tiles in the 
     321     *     grid.  The grid tile is about to be cleared and a new tile source 
     322     *     loaded.  This is where the transition effect needs to be started 
     323     *     to provide visual continuity. 
     324     */ 
     325    startTransition: function() { 
     326        // backBufferTile has to be valid and ready to use 
     327        if (!this.backBufferTile || !this.backBufferTile.imgDiv) { 
     328            return; 
     329        } 
    307330 
     331        // calculate the ratio of change between the current resolution of the 
     332        // backBufferTile and the layer.  If several animations happen in a 
     333        // row, then the backBufferTile will scale itself appropriately for 
     334        // each request. 
     335        var ratio = 1; 
     336        if (this.backBufferTile.resolution) { 
     337            ratio = this.backBufferTile.resolution / this.layer.getResolution(); 
     338        } 
     339         
     340        // if the ratio is not the same as it was last time (i.e. we are 
     341        // zooming), then we need to adjust the backBuffer tile 
     342        if (ratio != this.lastRatio) { 
     343            if (this.layer.transitionEffect == 'resize') { 
     344                // In this case, we can just immediately resize the  
     345                // backBufferTile. 
     346                var upperLeft = new OpenLayers.LonLat( 
     347                    this.backBufferTile.bounds.left,  
     348                    this.backBufferTile.bounds.top 
     349                ); 
     350                var size = new OpenLayers.Size( 
     351                    this.backBufferTile.size.w * ratio, 
     352                    this.backBufferTile.size.h * ratio 
     353                ); 
     354 
     355                var px = this.layer.map.getLayerPxFromLonLat(upperLeft); 
     356                OpenLayers.Util.modifyDOMElement(this.backBufferTile.frame,  
     357                                                 null, px, size); 
     358                var imageSize = this.backBufferTile.imageSize; 
     359                imageSize = new OpenLayers.Size(imageSize.w * ratio,  
     360                                                imageSize.h * ratio); 
     361                var imageOffset = this.backBufferTile.imageOffset; 
     362                if(imageOffset) { 
     363                    imageOffset = new OpenLayers.Pixel( 
     364                        imageOffset.x * ratio, imageOffset.y * ratio 
     365                    ); 
     366                } 
     367 
     368                OpenLayers.Util.modifyDOMElement( 
     369                    this.backBufferTile.imgDiv, null, imageOffset, imageSize 
     370                ) ; 
     371 
     372                this.backBufferTile.show(); 
     373            } 
     374        } else { 
     375            // default effect is just to leave the existing tile 
     376            // until the new one loads if this is a singleTile and 
     377            // there was no change in resolution.  Otherwise we 
     378            // don't bother to show the backBufferTile at all 
     379            if (this.layer.singleTile) { 
     380                this.backBufferTile.show(); 
     381            } else { 
     382                this.backBufferTile.hide(); 
     383            } 
     384        } 
     385        this.lastRatio = ratio; 
     386 
     387    }, 
     388     
     389    /**  
     390     * Method: show 
     391     * Show the tile by showing its frame. 
     392     */ 
     393    show: function() { 
     394        this.frame.style.display = ''; 
     395        // Force a reflow on gecko based browsers to actually show the element 
     396        // before continuing execution. 
     397        if (navigator.userAgent.toLowerCase().indexOf("gecko") != -1) {  
     398            this.frame.scrollLeft = this.frame.scrollLeft;  
     399        }  
     400    }, 
     401     
     402    /**  
     403     * Method: hide 
     404     * Hide the tile by hiding its frame. 
     405     */ 
     406    hide: function() { 
     407        this.frame.style.display = 'none'; 
     408    }, 
     409     
    308410    CLASS_NAME: "OpenLayers.Tile.Image" 
    309411  } 
    310412); 
  • lib/OpenLayers/Tile.js

    old new  
    8080     */ 
    8181    isLoading: false, 
    8282     
     83    /** 
     84     * Property: isBackBuffer 
     85     * {Boolean} Is this tile a back buffer tile? 
     86     */ 
     87    isBackBuffer: false, 
     88     
     89    /** 
     90     * Property: lastRatio 
     91     * {Float} Used in transition code only.  This is the previous ratio 
     92     *     of the back buffer tile resolution to the map resolution.  Compared 
     93     *     with the current ratio to determine if zooming occurred. 
     94     */ 
     95    lastRatio: 1, 
     96 
     97    /** 
     98     * Property: isFirstDraw 
     99     * {Boolean} Is this the first time the tile is being drawn? 
     100     *     This is used to force resetBackBuffer to synchronize 
     101     *     the backBufferTile with the foreground tile the first time 
     102     *     the foreground tile loads so that if the user zooms 
     103     *     before the layer has fully loaded, the backBufferTile for 
     104     *     tiles that have been loaded can be used. 
     105     */ 
     106    isFirstDraw: true, 
     107         
     108    /** 
     109     * Property: backBufferTile 
     110     * {<OpenLayers.Tile>} A clone of the tile used to create transition 
     111     *     effects when the tile is moved or changes resolution. 
     112     */ 
     113    backBufferTile: null, 
     114         
    83115    /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor. 
    84116     *             there is no need for the base tile class to have a url. 
    85117     *  
     
    111143     * Nullify references to prevent circular references and memory leaks. 
    112144     */ 
    113145    destroy:function() { 
     146        if (this.layer.transitionEffect) { 
     147            this.layer.events.unregister("loadend", this, this.resetBackBuffer); 
     148            this.events.unregister('loadend', this, this.resetBackBuffer);             
     149        } else { 
     150            this.events.unregister('loadend', this, this.showTile); 
     151        } 
    114152        this.layer  = null; 
    115153        this.bounds = null; 
    116154        this.size = null; 
     
    118156         
    119157        this.events.destroy(); 
    120158        this.events = null; 
     159         
     160        /* clean up the backBufferTile if it exists */ 
     161        if (this.backBufferTile) { 
     162            this.backBufferTile.destroy(); 
     163            this.backBufferTile = null; 
     164        } 
    121165    }, 
    122166     
    123167    /** 
     
    156200     *     depend on the return to know if they should draw or not. 
    157201     */ 
    158202    draw: function() { 
    159          
    160         //clear tile's contents and mark as not drawn 
    161         this.clear(); 
    162          
    163203        var maxExtent = this.layer.maxExtent; 
    164204        var withinMaxExtent = (maxExtent && 
    165205                               this.bounds.intersectsBounds(maxExtent, false)); 
    166206  
    167207        // The only case where we *wouldn't* want to draw the tile is if the  
    168208        // tile is outside its layer's maxExtent. 
    169         return (withinMaxExtent || this.layer.displayOutsideMaxExtent); 
     209        var drawTile = (withinMaxExtent || this.layer.displayOutsideMaxExtent); 
     210 
     211        if (this.layer.transitionEffect != null) { 
     212            if (drawTile) { 
     213                //we use a clone of this tile to create a double buffer for visual 
     214                //continuity.  The backBufferTile is used to create transition 
     215                //effects while the tile in the grid is repositioned and redrawn 
     216                if (!this.backBufferTile) { 
     217                    this.backBufferTile = this.clone(); 
     218                    this.backBufferTile.hide(); 
     219                    // this is important.  It allows the backBuffer to place itself 
     220                    // appropriately in the DOM.  The Image subclass needs to put 
     221                    // the backBufferTile behind the main tile so the tiles can 
     222                    // load over top and display as soon as they are loaded. 
     223                    this.backBufferTile.isBackBuffer = true; 
     224                     
     225                    // potentially end any transition effects when the tile loads 
     226                    this.events.register('loadend', this, this.resetBackBuffer); 
     227                     
     228                    // clear transition back buffer tile only after all tiles in 
     229                    // this layer have loaded to avoid visual glitches 
     230                    this.layer.events.register("loadend", this, this.resetBackBuffer); 
     231                } 
     232                // run any transition effects 
     233                this.startTransition(); 
     234            } else { 
     235                // if we aren't going to draw the tile, then the backBuffer should 
     236                // be hidden too! 
     237                if (this.backBufferTile) { 
     238                    this.backBufferTile.clear(); 
     239                } 
     240            } 
     241        } else { 
     242            if (drawTile && this.isFirstDraw) { 
     243                this.events.register('loadend', this, this.showTile); 
     244                this.isFirstDraw = false; 
     245            }    
     246        }     
     247        this.shouldDraw = drawTile; 
     248         
     249        //clear tile's contents and mark as not drawn 
     250        this.clear(); 
     251         
     252        return drawTile; 
    170253    }, 
    171254     
    172255    /**  
     
    237320                                       topLeft.lat);   
    238321        return bounds; 
    239322    },         
    240  
     323     
     324    /**  
     325     * Method: startTransition 
     326     * Prepare the tile for a transition effect.  To be 
     327     *     implemented by subclasses. 
     328     */ 
     329    startTransition: function() {}, 
     330     
     331    /**  
     332     * Method: resetBackBuffer 
     333     * Triggered by two different events, layer loadend, and tile loadend. 
     334     *     In any of these cases, we check to see if we can hide the  
     335     *     backBufferTile yet and update its parameters to match the  
     336     *     foreground tile. 
     337     * 
     338     * Basic logic: 
     339     *  - If the backBufferTile hasn't been drawn yet, reset it 
     340     *  - If layer is still loading, show foreground tile but don't hide 
     341     *    the backBufferTile yet 
     342     *  - If layer is done loading, reset backBuffer tile and show  
     343     *    foreground tile 
     344     */ 
     345    resetBackBuffer: function() { 
     346        this.showTile(); 
     347        if (this.backBufferTile &&  
     348            (this.isFirstDraw || !this.layer.numLoadingTiles)) { 
     349            this.isFirstDraw = false; 
     350            // check to see if the backBufferTile is within the max extents 
     351            // before rendering it  
     352            var maxExtent = this.layer.maxExtent; 
     353            var withinMaxExtent = (maxExtent && 
     354                                   this.bounds.intersectsBounds(maxExtent, false)); 
     355            if (withinMaxExtent) { 
     356                this.backBufferTile.position = this.position; 
     357                this.backBufferTile.bounds = this.bounds; 
     358                this.backBufferTile.size = this.size; 
     359                this.backBufferTile.imageSize = this.layer.imageSize || this.size; 
     360                this.backBufferTile.imageOffset = this.layer.imageOffset; 
     361                this.backBufferTile.resolution = this.layer.getResolution(); 
     362                this.backBufferTile.renderTile(); 
     363            } 
     364        } 
     365    }, 
     366         
     367    /**  
     368     * Method: showTile 
     369     * Show the tile only if it should be drawn. 
     370     */ 
     371    showTile: function() {  
     372        if (this.shouldDraw) { 
     373            this.show(); 
     374        } 
     375    }, 
     376     
     377    /**  
     378     * Method: show 
     379     * Show the tile.  To be implemented by subclasses. 
     380     */ 
     381    show: function() { }, 
     382     
     383    /**  
     384     * Method: hide 
     385     * Hide the tile.  To be implemented by subclasses. 
     386     */ 
     387    hide: function() { }, 
     388     
    241389    CLASS_NAME: "OpenLayers.Tile" 
    242390}); 
  • lib/OpenLayers/Layer.js

    old new  
    257257     */ 
    258258    wrapDateLine: false, 
    259259     
     260    /** 
     261     * APIProperty: transitionEffect 
     262     * {String} The transition effect to use when the map is panned or 
     263     *     zoomed.   
     264     * 
     265     * There are currently two supported values: 
     266     *  - *null* No transition effect (the default). 
     267     *  - *resize*  Existing tiles are resized on zoom to provide a visual 
     268     *    effect of the zoom having taken place immediately.  As the 
     269     *    new tiles become available, they are drawn over top of the 
     270     *    resized tiles. 
     271     */ 
     272    transitionEffect: null, 
    260273     
    261274    /** 
    262275     * Constructor: OpenLayers.Layer 
  • examples/transition.html

    old new  
     1<html xmlns="http://www.w3.org/1999/xhtml"> 
     2  <head> 
     3    <title>OpenLayers Transitions Example</title> 
     4    <style type="text/css"> 
     5        #mapDiv { 
     6            width: 400px; 
     7            height: 400px; 
     8            border: 1px solid black; 
     9        } 
     10    </style> 
     11    <script src="../lib/OpenLayers.js"></script> 
     12    <script type="text/javascript"> 
     13        var map; 
     14        function init(){ 
     15            map = new OpenLayers.Map('mapDiv', {maxResolution: 'auto'}); 
     16 
     17            var single_default_effect = new OpenLayers.Layer.WMS( 
     18                "WMS untiled default",  
     19                "http://labs.metacarta.com/wms/vmap0?", 
     20                {layers: 'basic'},  
     21                {singleTile: true} 
     22            ); 
     23            var single_resize_effect = new OpenLayers.Layer.WMS( 
     24                "WMS untiled resize",  
     25                "http://labs.metacarta.com/wms/vmap0?", 
     26                {layers: 'basic'},  
     27                {singleTile: true, transitionEffect: 'resize'} 
     28            ); 
     29            var tiled_default_effect = new OpenLayers.Layer.WMS( 
     30                "WMS tiled default ",  
     31                "http://labs.metacarta.com/wms/vmap0?", 
     32                {layers: 'basic'} 
     33            ); 
     34            var tiled_resize_effect = new OpenLayers.Layer.WMS( 
     35                "WMS tiled resize",  
     36                "http://labs.metacarta.com/wms/vmap0?", 
     37                {layers: 'basic'},  
     38                {transitionEffect: 'resize'} 
     39            ); 
     40 
     41            map.addLayers([single_default_effect, single_resize_effect, 
     42                           tiled_default_effect, tiled_resize_effect]); 
     43            map.addControl(new OpenLayers.Control.LayerSwitcher()); 
     44            map.setCenter(new OpenLayers.LonLat(6.5, 40.5), 4); 
     45        } 
     46    </script> 
     47  </head> 
     48  <body onload="init()"> 
     49    <h1 id="title">Transition Example</h1> 
     50    <p id="shortdesc"> 
     51        Demonstrates the use of transition effects in tiled and untiled layers. 
     52    </p> 
     53    <div id="mapDiv"></div> 
     54    <div id="docs"> 
     55        There are two transitions that are currently implemented: null (the 
     56        default) and 'resize'.  The default transition effect is used when no 
     57        transition is specified and is implemented as no transition effect except 
     58        for panning singleTile layers.  The 'resize' effect resamples the current 
     59        tile and displays it stretched or compressed until the new tile is available. 
     60    <ul> 
     61        <li>The first layer is an untiled WMS layer with no transition effect.</li> 
     62        <li>The second layer is an untiled WMS layer with a 'resize' effect. </li> 
     63        <li>The third layer is a tiled WMS layer with no transition effect. </li> 
     64        <li>The fourth layer is a tiled WMS layer with a 'resize' effect. </li> 
     65    </ul> 
     66    </div> 
     67  </body> 
     68  </body> 
     69</html>