Making an animation with image layers

When working with geographic information, its geometrical representation within the space is not the only important thing. Day by day time is becoming a new and important dimension to take into account.

This way, visualizations must show how data changes over time: city population, country frontiers, roads built, and so on.

There are many solutions to animate the data evolution through time but, as always, we work with web technologies, there are two groups: the solutions based on the server side and those based on the client side.

For server-side solutions, we can find the TIME parameter in the WMS and WFS standards. It allows us to request for raster or vector data in a specific time or within a range.

Server solutions means the client must request the server every time we want to show the data for a different interval.

For the client side, a simple solution is to have in the memory all the data, and only show those that correspond to the interval we are interested in.

In this recipe we are going to show how easily we can create an animation on the client side.

We are going to load some images from NEXTRAD (http://en.wikipedia.org/wiki/NEXRAD), showing the rain evolution at different time instants (as shown in the following screenshot), and we will create an animation by simply showing or hiding images:

Making an animation with image layers

How to do it...

  1. Create a new HTML file and add the OpenLayers dependencies. In the body section start adding the elements necessary for the play button and the slider:
    <table>
        <tr>
            <td>
                Animation:
            </td>
            <td>
                <div id="animSlider" dojoType="dijit.form.HorizontalSlider" value="0" minimum="0" maximum="100" intermediateChanges="true"
                     showButtons="false" style="width:300px;" onChange="animation">
                    <div dojoType="dijit.form.HorizontalRule" container="bottomDecoration" count=11 style="height:5px;"></div>
                </div> 
            </td>
            <td>
                <div dojoType="dijit.form.ToggleButton" iconClass="dijitCheckBoxIcon" onChange="animateAction">Play</div>
            </td>
        </tr>
    </table>

    Tip

    In addition to OpenLayers, we are using the Dojo Toolkit framework (http://dojotoolkit.org) to create more attractive user interfaces. Learning Dojo is out of the scope of this book and also, it is not necessary to know it to understand this recipe.

  2. Next, add the div element to hold the map:
    <div id="ch08_animating_raster" style="width: 100%; height: 100%;"></div>
  3. Now, in the header section add the next JavaScript code. Start initializing the map instance, add a base layer, and center the viewport:
    <script type="text/javascript">
        var map = new OpenLayers.Map("ch08_animating_raster");
        var wms = new OpenLayers.Layer.WMS("OpenLayers WMS Basic", "http://labs.metacarta.com/wms/vmap0",
        {
            layers: 'basic'
        });
        map.addLayer(wms);
        
        // Center the view
        map.setCenter(new OpenLayers.LonLat(-85, 40), 4);
  4. Now, we are going to create some raster image layers, add them to the map, and also store them on an array to control their visibility:
        var img_extent = new OpenLayers.Bounds(-131.0888671875, 30.5419921875, -78.3544921875, 53.7451171875);
        var img_size = new OpenLayers.Size(780, 480);
            
        var img_ulr = image = null;
        var imgArray = [];
        for(var i=1; i<=32; i++) {
            index = (i<10) ? "0"+i : i;
            img_url = "http://localhost:8080/openlayers-cookbook/recipes/data/radar/nexrad"+index+".png";        
            image = new OpenLayers.Layer.Image("Image Layer", img_url, img_extent, img_size, {
                isBaseLayer: false,
                alwaysInRange: true, // Necessary to always draw the image
                visibility: false
            });
            imgArray.push(image);
            map.addLayer(image);
        }
        imgArray[0].setVisibility(true);
  5. The following code controls the changes in the slider widget:
        var currentIndex = 0;
        function animation(value){
            imgArray[currentIndex].setVisibility(false);
            currentIndex = Math.floor(value * 31 / 100);
            imgArray[currentIndex].setVisibility(true);
        }
  6. Finally, the following code controls the automatic animation when the Play button is clicked:
        var interval = null;
        function animateAction(checked) {
            if(checked) {
                interval = setInterval(function() {
                    var v = dijit.byId('animSlider').get('value'),
                    v = (v>=100) ? 0 : (v+1);
                    dijit.byId('animSlider').set('value', v);
                    animation(v);
                },50);
            } else {
                clearInterval(interval);
            }
        }
    </script>

How it works...

As mentioned at the beginning of this recipe, the idea is to animate some weather radar on the client side. For this reason, we load a set of images from the server, creating a layer for each one.

The OpenLayers.Layer.Image class is used here to hold each image as a single layer. Its constructor requires five parameters:

  • name: The name of the layer
  • url: The URL where the image must be taken
  • extent: The bounds of the image within the map
  • size: The size in pixels
  • options: A set of options to be passed to the layer

Note

The extent and the size arguments are used to compute the resolution of the image.

In our case, all the image layers will have the same extent and size:

    var img_extent = new OpenLayers.Bounds(-131.0888671875, 30.5419921875, -78.3544921875, 53.7451171875);
    var img_size = new OpenLayers.Size(780, 480);

Later, within the loop to create all the image layers, we are setting dynamically the value of the img_url variable and passing some options to the OpenLayers.Layer.Image constructor:

        image = new OpenLayers.Layer.Image("Image Layer", img_url, img_extent, img_size, {
            isBaseLayer: false,
            alwaysInRange: true, // Necessary to always draw the image
            visibility: false
        });
        imgArray.push(image);

By setting the isBaseLayer property to false we are specifying that our layer is not a base layer, it will act as an overlay. In addition, we set the visibility property to false to initially hide the layer. Later, we will set the first image layer as the visible one:

    imgArray[0].setVisibility(true);

The alwaysInRange property is inherited from the superclass OpenLayers.Layer and specially useful in this case. We want our image layer to be visible at any zoom level. We do not want OpenLayers to compute the right resolution; given the image layer extent and size, the layer must be shown. So, setting alwaysInRange to true makes the layer always visible.

Next, we are going to see how to automatically automate the animation. The animateAction is executed when the play button is pressed. It is a toggle button, so depending on its state, the boolean checked parameter will be true if checked or false if not:

    function animateAction(checked) {
        if(checked) {

If the play button is checked, then we create an interval to execute the given anonymous function every 50 milliseconds which, in fact, increases the value of the slider and calls the animation function:

            interval = setInterval(function() {
                var v = dijit.byId('animSlider').get('value'),
                v = (v>=100) ? 0 : (v+1);
                dijit.byId('animSlider').set('value', v);
                animation(v);
            },50);
        } else {

If the button is unchecked, then we remove the interval reference to stop the execution:

            clearInterval(interval);
        }

Note

Intervals and timeouts are used in JavaScript to create animations and delays.

A good explanation of intervals in JavaScript can be found at http://www.w3schools.com/jsref/met_win_setinterval.asp.

Finally, let's take a look at the animation function, which is responsible to change the layer visibilities and create the animation effect.

All the layers are added to the map and also stored in the imgArray. The global currentIndex variable is used to hold the current visible layer. The animation function does three things:

  • Hides the current visible layer
  • Given the numeric value, that varies from 0 to 100, computes the layer array index, that goes from 0 to 31
  • Shows the new current layer
        var currentIndex = 0;
        function animation(value){
            imgArray[currentIndex].setVisibility(false);
            currentIndex = Math.floor(value * 31 / 100);
            imgArray[currentIndex].setVisibility(true);
        }

That's all! Once the set of layers is loaded in the client side, using any modern browser, the performance of the animation is good enough.

This is only a sample, so there are tons of things to improve. For example, think on how to implement a situation where we have a remote server with hundreds of images to load sequentially. In this case we cannot load all images at once because that can cause an out-of-memory problem in the browser.

Supposing we have thousands of images to animate, we could implement some buffer strategy. Given a buffer of ten images, we can load the first ten images from the server, then animate them and when the animation arrives to the last loaded image, load the next ten images from the server.

As we can see, these situations are out of the scope of this book and not only related to OpenLayers but with software architecture and design.

See also

  • The Creating an Image layer recipe in Chapter 2, Adding Raster Layers
  • The Changing layer opacity recipe in Chapter 2, Adding Raster Layers
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.201.217