Table of Contents for
Mastering OpenLayers 3

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Mastering OpenLayers 3 by Gábor Farkas Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. Mastering OpenLayers 3
  4. Mastering OpenLayers 3
  5. Credits
  6. About the Author
  7. About the Reviewer
  8. www.PacktPub.com
  9. Preface
  10. What you need for this book
  11. Who this book is for
  12. Conventions
  13. Reader feedback
  14. Customer support
  15. 1. Creating Simple Maps with OpenLayers 3
  16. Structure of OpenLayers 3
  17. Building the layout
  18. Using the API documentation
  19. Debugging the code
  20. Summary
  21. 2. Applying Custom Styles
  22. Customizing the default appearance
  23. Styling vector layers
  24. Customizing the appearance with JavaScript
  25. Creating a WebGIS client layout
  26. Summary
  27. 3. Working with Layers
  28. Building a layer tree
  29. Adding layers dynamically
  30. Adding vector layers with the File API
  31. Adding vector layers with a library
  32. Removing layers dynamically
  33. Changing layer attributes
  34. Changing the layer order with the Drag and Drop API
  35. Clearing the message bar
  36. Summary
  37. 4. Using Vector Data
  38. Accessing attributes
  39. Setting attributes
  40. Validating attributes
  41. Creating thematic layers
  42. Saving vector data
  43. Saving with WFS-T
  44. Modifying the geometry
  45. Summary
  46. 5. Creating Responsive Applications with Interactions and Controls
  47. Building the toolbar
  48. Mapping interactions to controls
  49. Building a set of feature selection controls
  50. Adding new vector layers
  51. Building a set of drawing tools
  52. Modifying and snapping to features
  53. Creating new interactions
  54. Building a measuring control
  55. Summary
  56. 6. Controlling the Map – View and Projection
  57. Customizing a view
  58. Constraining a view
  59. Creating a navigation history
  60. Working with extents
  61. Rotating a view
  62. Changing the map's projection
  63. Creating custom animations
  64. Summary
  65. 7. Mastering Renderers
  66. Using different renderers
  67. Creating a WebGL map
  68. Drawing lines and polygons with WebGL
  69. Blending layers
  70. Clipping layers
  71. Exporting a map
  72. Creating a raster calculator
  73. Creating a convolution matrix
  74. Clipping a layer with WebGL
  75. Summary
  76. 8. OpenLayers 3 for Mobile
  77. Responsive styling with CSS
  78. Generating geocaches
  79. Adding device-dependent controls
  80. Vectorizing the mobile version
  81. Making the mobile application interactive
  82. Summary
  83. 9. Tools of the Trade – Integrating Third-Party Applications
  84. Exporting a QGIS project
  85. Importing shapefiles
  86. Spatial analysis with Turf
  87. Spatial analysis with JSTS
  88. 3D rendering with Cesium
  89. Summary
  90. 10. Compiling Custom Builds with Closure
  91. Configuring Node JS
  92. Compiling OpenLayers 3
  93. Bundling an application with OpenLayers 3
  94. Extending OpenLayers 3
  95. Creating rich documentation with JSDoc
  96. Summary
  97. Index

Creating custom animations

In a WebGIS application, animations do not play serious roles; however, in some web mapping applications that are focused on user experience, they can really come in handy. In the last example, called ch06_animation, we will go through the process of making our own special camera effects. The theory behind animations is very important but quite mathematical, so try to keep up with me. First, we declare some CSS rules for our custom animation control:

.ol-rocket {
    top: 20px;
    right: 20px;
}
.ol-rocket button {
    background-image: url(../../res/button_rocket.png);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: 50%;
}

Building the control

Next, we create a simple control, which will trigger the animation process. Let's make a little revision first. Animations can be triggered by calling the map object's beforeRender method with one or more animation functions. We can provide a start time and, occasionally, source, resolution, or rotation. These functions make sure that our view is animated before it switches to the next destination. After the first change in the view, the list of animations gets cleared and changes take place immediately later without animations. In this example, we make two custom animations: one for zooming into the maximum resolution of an OpenSteetMap layer (rocketTakeOff) and the other to zoom into my campus (rocketLanding):

ol.control.RocketFlight = function () {
    var _this = this;
    var controlDiv = document.createElement('div');
    controlDiv.className = 'ol-rocket ol-unselectable ol-control';
    var controlButton = document.createElement('button');
    controlButton.title = 'Launch me';
    controlButton.addEventListener('click', function () {
        var view = _this.getMap().getView();
        _this.getMap().beforeRender(
            ol.animation.rocketTakeoff({
                resolution: view.getResolution(),
                rotation: view.getRotation()
            })
        );
        view.setResolution(39135.75848201024);

The first part, as mentioned previously, zooms into the maximum zoom level from the current resolution in an imaginary rocket. Next, we call an animation, which is designated to zoom into the final destination:

        setTimeout(function () {
            _this.getMap().beforeRender(
                ol.animation.pan({
                    duration: 2000,
                    source: view.getCenter(),
                    easing: ol.easing.linear
                }),
                ol.animation.rocketLanding({
                    resolution: view.getResolution(),
                    rotation: view.getRotation()
                })
            );
            view.setProperties({
                center: [2026883.0676951527, 5792745.55306364],
                resolution: 0.5971642834779395
            });
        }, 5100);
    });
    controlDiv.appendChild(controlButton);
    ol.control.Control.call(this, {
        element: controlDiv
    });
};
ol.inherits(ol.control.RocketFlight, ol.control.Control);

As we cannot trigger an animation before the previous one is finished, we delay it by a little more than five seconds. Our first animation will take exactly five seconds to complete, but we do not want the two animations to hook up.

Creating animations

Before we create our first custom animation, let's discuss how animations work in OpenLayers 3. Every animation function returns a function. The returned function receives two arguments. The first is the map object, which is negligible in most of the cases. The second one is the frame state. The returned function gets called in every frame until it returns false. From the frame state object, the most important properties that we can decipher is the current time and the view's updated state.

If you are a visual type, this might help you understand animations in OpenLayers 3 better. Imagine a spring. When we start an animation, we fixate one end in the new view. We hold the other end in the previous view where we start animating from. The spring will be in stationary state when both of its ends are in the new view. If we stop animating, we release the spring, which instantly jumps to the new view, as it wants to be in its stationary state. However, until we are animating, we move the end that we hold continuously to the fixated end for a fluent result. But how do we know how much it needs to be moved in a given time? We calculate it from the initial distance and the passed time. Finally, we move closer to the fixed end by the value it needs to be moved. Of course, every spring can be moved in two directions; thus, we can pull the spring in the opposite direction. If we do this, however, we have to calculate with an inverse distance.

There is a very important function, which we will call frequently, called easing. An easing function in the library expects a number between 0 and 1, representing the percentage value for which a movement in the animation is completed. It returns a scaling factor that's based on a function curve, with which a linear movement should be multiplied by in order to result in a tween. Now, we create our first custom animation function to zoom out of the map in the application:

ol.animation.rocketTakeoff = function (options) {
    var now = +new Date();
    return function (map, frameState) {
        if (frameState.time < now + 5000) {
            var delta = 1 - ol.easing.easeIn((frameState.time - now) / 5000);
            var deltaResolution = options.resolution - frameState.viewState.resolution;
            frameState.animate = true;
            frameState.viewState.resolution += delta * deltaResolution;
            if (frameState.time > now + 2000 && frameState.time < now + 3000) {
                var rotateDelta = ol.easing.linear((frameState.time - now - 2000) / 1000);
                var deltaRotation = options.rotation - 0.5;
                frameState.viewState.rotation += deltaRotation * rotateDelta;
            } else if (frameState.time >= now + 3000 && frameState.time < now + 4000) {
                var rotateDelta = 1 - ol.easing.linear((frameState.time - now - 3000) / 1000);
                var deltaRotation = options.rotation - 0.5;
                frameState.viewState.rotation += deltaRotation * rotateDelta;
            }
            frameState.viewHints[0] += 1;
            return true;
        }
        return false;
    };
};

As mentioned previously, every frame state comes with a timestamp. There is a twist in the timestamps, though, as it represents the current date in milliseconds.

Note

In most programming languages, such as JavaScript, the current date represents the elapsed time between 1970, January 1, 00:00:00 and the operating system's current time.

This way, we have to adjust our timing with a new Date object, which we can cast to milliseconds by preceding it with + on construction. We save the starting time of our animation with this method and compare every frame state's timestamp to it. As we want our first animation to take 5 seconds, we update our frame states for this time, then return false, terminating the animation.

For the resolution change, we use the ol.easing.easeIn function, which starts fast and slows down at the end. We must provide the inverse value that's returned by the easing function, though, as we move toward our destination.

Note

In every function call, our animation function gets the updated properties of the view object. While the animation exists, we can overwrite the view on every frame by modifying the frameState object's viewState properties. This is how animations work in OpenLayers 3.

We increase our view state's resolution with the delta resolution between the initial and target value multiplied by the value that's returned by our easing function. This will make sure that our animation is fluent for the time it takes place.

If the animation is between 2 and 3 seconds, we rotate the view with a linear easing. We do not have to invert the returned value as we are moving from our destination (the updated rotation value). To turn back the rotation between the fourth and the fifth seconds, we take the same procedure with an inverse easing value.

Tip

If you want to use a linear easing, you do not have to use an easing function at all. The ol.easing.linear function returns the provided value without any modifications. I'm just using it now for consistency.

As you may have noticed, we changed two values in the frame state's besides its view state's properties. Setting its animate property to true is mandatory; otherwise, the animation stops. Updating the 0 property of its viewHints object is optional, though. That property shows, how many animations are modifying the view at a current frame.

Next, we create our second animation to land in our imaginary rocket's shuttle:

ol.animation.rocketLanding = function (options) {
    var now = +new Date();
    var direction = Math.round(Math.random());
    return function (map, frameState) {
        if (frameState.time < now + 15000) {
            var delta = 1 - ol.easing.parachuate((frameState.time - now) / 15000);
            var deltaResolution = options.resolution - frameState.viewState.resolution;
            frameState.animate = true;
            frameState.viewState.resolution += delta * deltaResolution;
            if (frameState.time > now + 5000 && frameState.time < now + 7000) {
                var rotateDelta = ol.easing.linear((frameState.time - now - 5000) / 2000);
                var deltaRotation = options.rotation + 2 * Math.PI;
                frameState.viewState.rotation += deltaRotation * rotateDelta;
            } else if (frameState.time > now + 7000 && frameState.time < now + 10000) {
                var panDelta = ol.easing.linear((frameState.time - now - 7000) / 3000);
                frameState.viewState.center[direction] += 500 * panDelta;
            } else if (frameState.time >= now + 10000 && frameState.time < now + 12000) {
                var panDelta = 1 - ol.easing.linear((frameState.time - now - 10000) / 2000);
                frameState.viewState.center[direction] += 500 * panDelta;
            }
            frameState.viewHints[0] += 1;
            return true;
        }
        return false;
    };
};

In this function, at the end of the landing, we swing our capsule on a random axis. The landing takes 15 seconds. For the duration of this period, we decrease the resolution with a custom easing function, which we will soon discuss. After 5 seconds, we make a 360 degree turn, which is exactly 2π radians. Between 7 and 10 seconds, we pan our view by 500 meters on the randomly generated axis, while between 10 and 12 seconds, we pan it back to its original place.

Next, we define our custom easing function:

ol.easing.parachuate = function (t) {
    return 1 - Math.pow(1 - t, 7);
};

The basic ol.easing.easeOut function is similar to custom easing function; however, it brings the inverted argument to its third power, making the slowdown faster. We slow down the effect drastically by bringing the inverted argument to its seventh power.

Note

As you may have noticed already, we didn't make an effort to pan our view from our imaginary rocket's launch site to its landing place. This is why we included a built-in pan animation along with a custom landing animation in our control.

Finally, we include the control to the map and change the base layer back to OpenStreetMap:

var map = new ol.Map({
    target: 'map',
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM({
                wrapX: false
            }),
            name: 'OpenStreetMap'
        }),
        […]
    controls: [
        […]
        new ol.control.RocketFlight()
    […]
});

Now, if you save the code and open it, you can fly to my campus from any location.

Creating animations

Tip

For the best experience, start it from a high zoom level!