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:
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>
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.
div
element to hold the map:<div id="ch08_animating_raster" style="width: 100%; height: 100%;"></div>
<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);
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);
var currentIndex = 0; function animation(value){ imgArray[currentIndex].setVisibility(false); currentIndex = Math.floor(value * 31 / 100); imgArray[currentIndex].setVisibility(true); }
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>
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 layerurl
: The URL where the image must be takenextent
: The bounds of the image within the mapsize
: The size in pixelsoptions
: A set of options to be passed to the layerIn 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); }
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:
value
, that varies from 0 to 100, computes the layer array index, that goes from 0 to 31var 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.
3.135.201.217