Udostępnij za pośrednictwem


Creating animations with Bing Maps (JavaScript)

Bing Maps is a very powerful mapping platform that is often used for creating engaging user experiences. The fluid interactive maps make for a great canvas when visualizing location based data. In this blog post we are going to take a look at how to make the user experience a little more engaging by adding custom animations that can be used in both web and Windows Store apps.

Full source code for this blog post can be found in the MSDN Code Samples here.

Setting up the base project

In this blog you have the choice of creating a web or a Windows Store app. The web based app will make use of the Bing Maps V7 AJAX control while the Windows Store app will make use of the Bing Maps Windows Store SDK. Both API’s are nearly identical but there are a couple of minor differences. The main difference is that we will be using different JavaScript references to load the map control into the page. The Windows Store app will reference a local copy of the Bing Maps API while the web app will be loading in a cloud hosted version of the Bing Maps API. Since the API’s are nearly identical we will be able to use the only differences between the Windows Store and web app will be the main HTML page that loads in the script references needed for the app.

Creating a Web based Project

If you would like to create a web based app open Visual Studio and create a new ASP.NET Web Application called AnimatedMaps.

clip_image002

Next, select the Empty template. We will be creating a basic web app to keep things simple.

clip_image004

Next create a new HTML page called default.html by right clicking on the project and selecting Add → New Item. Create two folders called js and css. In the css folder create a style sheet called default.css. We will use this file to store all our styles for laying out the web app. In the js folder create two JavaScript files. The first JavaScript file will be called AnimatedMap.js and will be used to store our application logic. The second JavaScript file will be called AnimationModule.js and will be used to store the code for an animation module that can be used with Bing Maps. At this point your project should look like this:

clip_image005

Creating a Windows Store Project

If you would like to create a Windows Store app then create a new JavaScript Windows Store project. Select the Blank Template and call the project AnimatedMaps.

clip_image007

Add a reference to the Bing Maps SDK. To do this, right click on the References folder and press Add Reference. Select Windows -> Extensions and then select Bing Maps for JavaScript. If you do not see this option ensure that you have installed the Bing Maps SDK for Windows Store apps.

clip_image009

Next right click on the js folder and select Add -> New Item. Create two new JavaScript files called AnimatedMaps.js and AnimationModule.js. At this point your project should look like this:

clip_image011

Setting up the UI

The JavaScript and CSS styles that we will use in this app will be identical for both the web and Windows Store app. The defaut.html file however will have some differences. The main difference will be with the JavaScript script files being referenced in the header. We will also make use of the onload event on the body of the page in the web app to load the map. The page itself will consist of a map and a panel above it that contains a number of buttons for testing out the animations.

If you are creating a web based application update the default.html file with the following HTML:

 <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    
    <link href="/css/default.css" rel="stylesheet" />

    <!-- Reference To Bing Maps API -->
    <script type="text/javascript" src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>

    <!-- Our Bing Maps JavaScript Code -->
    <script src="/js/AnimatedMaps.js"></script>

    <!-- Use script tag to register the Animation module -->
    <script>Microsoft.Maps.registerModule('AnimationModule');</script>
    <script type="text/javascript" src="/js/AnimationModule.js"></script>
</head>
<body onload="GetMap();">
    <div id='myMap'></div>

    <div class="sidePanel">
        <input type="button" value="Clear Map" onclick="ClearMap();" /><br /><br />

        <span>CSS Pushpin Animations</span><br />
        <input type="button" value="Scale on Hover" onclick="ScalingPin();" /><br /><br />

        <span>Pushpin Animations</span><br/>
        <input type="button" value="Drop Pin" onclick="DropPin();" /><br />
        <input type="button" value="Bounce Pin" onclick="BouncePin();" /><br />
        <input type="button" value="Bounce 4 Pins After Each Other" onclick="Bounce4Pins();" /><br /><br />
        
        <span>Path Animations</span><br />
        <input type="button" value="Move Pin Along Path" onclick="MovePinOnPath();" /><br />
        <input type="button" value="Move Pin Along Geodesic Path" onclick="MovePinOnPath(true);" /><br />
        <input type="button" value="Move Map Along Path" onclick="MoveMapOnPath();" /><br />
        <input type="button" value="Move Map Along Geodesic Path" onclick="MoveMapOnPath(true);" /><br />
        <input type="button" value="Draw Path" onclick="DrawPath();" /><br />
        <input type="button" value="Draw Geodesic Path" onclick="DrawPath(true);" />
    </div>
</body>
</html>

If you are creating a Windows Store app update the default.html file with the following HTML:

 <!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>AnimatedMaps</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

    <!-- AnimatedMaps references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>

    <!-- Bing Maps references -->
    <script type="text/javascript" src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>
    <script type="text/javascript" src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

    <!-- Our Bing Maps JavaScript Code -->
    <script src="/js/AnimatedMaps.js"></script>

    <!-- Use script tag to register the Animation module -->
    <script>Microsoft.Maps.registerModule('AnimationModule');</script>
    <script type="text/javascript" src="/js/AnimationModule.js"></script>
</head>
<body>
    <div id='myMap'></div>

    <div class="sidePanel">
        <input type="button" value="Clear Map" onclick="ClearMap();" /><br /><br />

        <span>CSS Pushpin Animations</span><br />
        <input type="button" value="Scale on Hover" onclick="ScalingPin();" /><br /><br />

        <span>Pushpin Animations</span><br />
        <input type="button" value="Drop Pin" onclick="DropPin();" /><br />
        <input type="button" value="Bounce Pin" onclick="BouncePin();" /><br />
        <input type="button" value="Bounce 4 Pins After Each Other" onclick="Bounce4Pins();" /><br /><br />

        <span>Path Animations</span><br />
        <input type="button" value="Move Pin Along Path" onclick="MovePinOnPath();" /><br />
        <input type="button" value="Move Pin Along Geodesic Path" onclick="MovePinOnPath(true);" /><br />
        <input type="button" value="Move Map Along Path" onclick="MoveMapOnPath();" /><br />
        <input type="button" value="Move Map Along Geodesic Path" onclick="MoveMapOnPath(true);" /><br />
        <input type="button" value="Draw Path" onclick="DrawPath();" /><br />
        <input type="button" value="Draw Geodesic Path" onclick="DrawPath(true);" />
    </div>
</body>
</html>

Next open the default.css file and update it with the following styles:

 html, body {
            width:100%;
            height:100%;
            margin:0;
            padding:0;
        }

#myMap {
    position:relative;
    width:100%;
    height:100%;
}

.sidePanel {
    position:absolute;
    right:10px;
    top:200px;
    top:calc(50% - 250px);
    margin:10px;
    width:250px;
    height:500px;
    border-radius:10px;
    background-color:#000;
    background-color:rgba(0,0,0,0.8);
    color:#fff;
    padding:10px;
}

    .sidePanel input {
        margin:5px 0;
    }

If you try to run the application at this point you won’t see too much as the map hasn’t been loaded. We will add the code to load the map to the AnimatedMap.js file. In addition to this we will also include a couple of global variables that we will use later and event handlers for all of the buttons. We will add the logic for the clearing the map as well. When we clear the map we will also stop any current animations that might be running. Update the AnimatedMap.js file with the following code:

 var map, currentAnimation;

var path = [
    new Microsoft.Maps.Location(42.8, 12.49),   //Italy
    new Microsoft.Maps.Location(51.5, 0),       //London
    new Microsoft.Maps.Location(40.8, -73.8),   //New York
    new Microsoft.Maps.Location(47.6, -122.3)   //Seattle
];

function GetMap() {
    map = new Microsoft.Maps.Map(document.getElementById("myMap"), {
        credentials: "YOUR_BING_MAPS_KEY"
    });

    //Load the Animation Module
    Microsoft.Maps.loadModule("AnimationModule");
}

function ClearMap() {
    map.entities.clear();

    if (currentAnimation != null) {
        currentAnimation.stop();
        currentAnimation = null;
    }
}

function ScalingPin() {
}

function DropPin() {
}

function BouncePin() {
}

function Bounce4Pins() {
}

function MovePinOnPath(isGeodesic) {
}

function MoveMapOnPath(isGeodesic) {
}

function DrawPath(isGeodesic) {
}

//Initialization logic for loading the map control
(function () {
    function initialize() {
        Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: GetMap });
    }

    document.addEventListener("DOMContentLoaded", initialize, false);
})();

If you run the application you will see the map load up and a bunch of buttons appearing in a panel like this:

clip_image013

Creating Animations using CSS

You can create fairly complex animations in web based apps using CSS3 animations and transitions. However there are some limitations. The first is that only modern browsers support CSS3. Older browsers will ignore these CSS styles. The second one limitation is that we can only modify CSS properties.

There are two ways you can make use of CSS3 to animate pushpins in Bing Maps. The first method consists of creating a standard pushpin and setting the typeName property to the name of a CSS class. By doing this Bing Maps will use the typeName value to set the class property of the generated pushpin HTML. For example, if we had a CSS style called “scaleStyle” we could assign this to a pushpin like so:

 var pin = new Microsoft.Maps.Pushpin(map.getCenter(), { typeName: 'scaleStyle' });

The second method is to create a custom HTML pushpin and set the CSS class on one f the elements way. For example:

 var pin = new Microsoft.Maps.Pushpin(map.getCenter(), { 
    htmlContent: "<div class='scaleStyle'>Custom Pushpin</div>" 
});

To try this out default.css file and add the following CSS style. This CSS style is designed to scale an HTML element to twice its size when the mouse hovers over it.

 .scaleStyle:hover {
    -webkit-transition: 0.2s ease-in-out;
    -moz-transition: 0.2s ease-in-out;
    -o-transition: 0.2s ease-in-out;
    transition: 0.2s ease-in-out;

    -webkit-transform: scale(2);
    -moz-transform: scale(2);
    -o-transform: scale(2);
    -ms-transform: scale(2);
    transform: scale(2);
}

Next open the AnimatedMaps.js file and update the ScalingPin function with the following code:

 function ScalingPin() {
    ClearMap();

    var pin = new Microsoft.Maps.Pushpin(map.getCenter(), { typeName: 'scaleStyle' });
    map.entities.push(pin);
}

If you run the app and press the “Scale on Hover” button a pushpin will appear in the center of the map. If you then hover your mouse over the pushpin you will notice that it grows to be twice its size. When you hover off the pushpin it goes back to its original size. Here is an animated gif that demonstrates this animation:

clip_image014

As we will see later in this blog post, CSS3 is only one way we can animate data on our map. We can create even more powerful animations using JavaScript which will work on nearly any web browsers.

Creating an Animation Module

Bing Maps has a large set of features and functionalities. So many in fact that it’s unusual for a single application to make use of all of them. With this in mind, if your app doesn’t require certain features then why download all that extra code. The Bing Maps JavaScript API’s use what is called a modular framework. This framework consists of a main API which contains the core set of features, such as the interactive map and pushpins, along with several modules which contain additional features such as directions, and venue maps. One benefit of using modules is that it allows you to pick and choose which features you want to load into your application. It also allows you to delay the loading of certain features until they are need. Take for example directions, you really don’t need to download that module until the user has pressed a button to calculate directions. In addition to the modules that are built into the Bing Maps platform you can create your own reusable modules as well. In fact several developers have created modules for Bing Maps and made them available through the open source Bing Maps V7 Modules CodePlex project.

In this section of the blog we are going to create the base of a module that will contain all our animation functionality. Since animations are common and there are a lot of different animation libraries out there we will give this module a namespace of Bing.Maps.Animations so as to limit the chances of it clashing with other animation libraries that you might want to use in your app. A Bing Maps module at its core is nothing more than a self-contained JavaScript file that make use of Bing Maps and triggers the Microsoft.Maps.moduleLoaded event on the last line of the file. While we are creating the base module we will also add two local variables to it. The first one will be called _delay and is the amount of time in milliseconds between each frame of the animation. A delay of 30ms is roughly equivalent to 33 frames per second. The second variable will be the radius of the earth in kilometers. We will make use of this constant later in this blog when we look at animations along a path. To create the base module open the AnimationModule.js file and add the following code:

 window.Bing = window.Bing || {};
window.Bing.Maps = window.Bing.Maps || {};
window.Bing.Maps.Animations = new function () {
    var _delay = 30,    //Time in ms between each frame of the animation
        EARTH_RADIUS_KM = 6378.1;

};

// Call the Module Loaded method
Microsoft.Maps.moduleLoaded('AnimationModule');

Now if you looked through all the HTML in the default.html file we created earlier you may have noticed that we registered the module and loaded in the code using a script reference like this:

 <!-- Use script tag to register the Animation module -->
<script>Microsoft.Maps.registerModule('AnimationModule');</script>
<script type="text/javascript" src="/js/AnimationModule.js"></script>

You may have also noticed in the GetMap function in the AnimatedMap.js file the following code:

 //Load the Animation Module
Microsoft.Maps.loadModule("AnimationModule");

This code loads the module if it wasn’t already loaded. We can also add a callback as an option to this function that will be fired after the module is loaded. Since we don’t need to worry about code possibly running before the module is loaded we don’t need to worry about using a callback. At this point we have the base module created, however it’s doesn’t do much at the moment as we haven’t exposed any public functions yet. We will add a number of different functions to this module as we take a look at different types of animations.

Simple Animations using JavaScript

As I mentioned earlier there are a lot of animation libraries out there that we could make use of. For example the jQuery Effects and WinJS.UI.Animation libraries. However these are not really related to spatial data and don’t give us all the functionality we will need for some of these animations. For our animations we are going to keep things simple and make use of the setInterval JavaScript functions. The setInterval function repeated calls a callback function on a set interval specified in milliseconds. In our case we will have an interval time of 30ms. The setInterval function will continue to run for as long as the app is running unless we stop it. For simple animations we will want to run the animation for a specified duration. Since we will have a specified duration and a constant delay between each frame we can easily calculate how many frames will be in the animation. By keeping track of how many frames that have been rendered we can calculate the progress of the animation by multiplying the current frame count by the delay and then dividing it by the duration. This will give us a decimal number between 0 and 1 for the progress. When the progress is equal to 1 the animation has completed and we can then stop it by calling the clearInterval function in JavaScript. Since this is likely to be a common task we can create the following reusable function for simple animations. Add this to the animation module.

 function simpleAnimation(renderFrameCallback, duration) {        
    var _timerId,
        _frame = 0;

    duration = (duration && duration > 0) ? duration : 150;

    _timerId = setInterval(function () {
        var progress = (_frame * _delay) / duration;

        if (progress > 1) {
            progress = 1;
        }

        renderFrameCallback(progress);

        if (progress == 1) {
            clearInterval(_timerId);
        }

        _frame++;
    }, _delay);
}

Now that we have a nice reusable function to help us create simple animations it’s time to create an actual animations. To start off with we will look at simple pushpin animations. The first animation we will create will drop the pushpin from a specified pixel height above the map to its location on the map. Here is an animated gif of how this animation will look.

clip_image015

When using Bing Maps the HTML that is generated for the map and pushpins is not directly accessible through the API. It might be tempting to us a trick or two to grab the pushpin DOM element but it’s not needed. The Pushpin class has an anchor property which is used to specify the offset used to align the point of the pushpin to the proper location on the map. To create a drop animation we simply need to animate the y value of the anchor. Since this is a linear animation we can easily decrease the y value as progress of the animation increases.

The second animation we will be very similar to the first but instead of just dropping the pushpin we will have it bounce a couple of times to rest. To accomplish this we need to calculate different values for the height as the progress increases to create this bounce effect. After a bit of playing around with a graphing calculator I came up with the following formula:

clip_image017

This then generates a graph that looks like this:

clip_image019

Using this formula to animate the pushpins position results in a nice bounce effect as demonstrated in this animated gif:

clip_image020

To help keep things clean we will create the Bing.Maps.Animations.PushpinAnimations namespace for these animations. To do this, add the following code to the animation module.

 this.PushpinAnimations = {
    Drop:  function (pin, height, duration) {
        height = (height && height > 0) ? height : 150;
        duration = (duration && duration > 0) ? duration : 150;

        var anchor = pin.getAnchor();
        var from = anchor.y + height;

        pin.setOptions({ anchor: new Microsoft.Maps.Point(anchor.x, anchor.y + height) });

        simpleAnimation(
            function (progress) {
                var y = from - height * progress;
                pin.setOptions({ anchor: new Microsoft.Maps.Point(anchor.x, y) });
            },
            duration
        );
    },

    Bounce: function (pin, height, duration) {
        height = (height && height > 0) ? height : 150;
        duration = (duration && duration > 0) ? duration : 1000;

        var anchor = pin.getAnchor();
        var from = anchor.y + height;

        pin.setOptions({ anchor: new Microsoft.Maps.Point(anchor.x, anchor.y + height) });

        simpleAnimation(
            function (progress) {
                var delta = Math.abs(Math.cos(progress * 2.5 * Math.PI)) / Math.exp(3 * progress);
                var y = from - height * (1 - delta);
                pin.setOptions({ anchor: new Microsoft.Maps.Point(anchor.x, y) });
            },
            duration
        );
    }
};

We can now update our button handlers to make use of these new animations. Open the AnimatedMap.js file and update the DropPin, BouncePin, and Bounce4Pins functions with the following code.

 function DropPin() {
    ClearMap();

    var pin = new Microsoft.Maps.Pushpin(map.getCenter());
    map.entities.push(pin);

    Bing.Maps.Animations.PushpinAnimations.Drop(pin);
}

function BouncePin() {
    ClearMap();

    var pin = new Microsoft.Maps.Pushpin(map.getCenter());
    map.entities.push(pin);

    Bing.Maps.Animations.PushpinAnimations.Bounce(pin);
}

function Bounce4Pins() {
    ClearMap();

    var idx = 0;

    for (var i = 0; i < path.length; i++) {
        setTimeout(function () {
            var pin = new Microsoft.Maps.Pushpin(path[idx]);
            map.entities.push(pin);

            Bing.Maps.Animations.PushpinAnimations.Bounce(pin);
            idx++;
        }, i * 500);
    }
}

If you run the application and press the buttons to drop or bounce a pushpin you will see a pushpin that falls to the center of the map just like the animated gif’s we saw before. If you press the button to animate 4 pushpins you will see 4 pushpins added to the map, one after another with a 500ms delay between them. This will look like this animated gif.

clip_image021

Creating Path Animations

The animations we have seen so far have been fairly simple and only run once. Before we dive into path animations it would be useful if we could not only play the animation but also pause or stop it. With a little work we can create a modified version of our simpleAnimation function that supports for play, pause and stop. The following code shows how to do this. Add this to the animation module.

 this.BaseAnimation = function (renderFrameCallback, duration) {
    var _timerId,
        frameIdx = 0,
        _isPaused = false;

    //Varify value 
    duration = (duration && duration > 0) ? duration : 1000;

    this.play = function () {
        if (renderFrameCallback) {
            if (_timerId) {
                _isPaused = false;
            } else {
                _timerId = setInterval(function () {
                    if (!_isPaused) {                            
                        var progress = (frameIdx * _delay) / duration;

                        renderFrameCallback(progress, frameIdx);

                        if (progress >= 1) {
                            reset();
                        }

                        frameIdx++;
                    }
                });
            }
        }
    };

    this.pause = function () {
        _isPaused = true;
    };

    this.stop = function () {
        reset();
    };

    function reset() {
        if (_timerId != null) {
            clearInterval(_timerId);
        }

        frameIdx = 0;
        _isPaused = false;
    }
};

We can now use this BaseAnimation class to power our more complex animations. One common type of animation I see developers struggle with when working with maps is animating along a path. To get a sense of the complexity involved consider the path between two locations on the map. If asked you to draw the shortest path between these two locations your first instinct might be to draw straight line, and visually you would be correct. However, the world is not flat and is actually an ellipsoid, yet most online maps show the world as a flat 2d rectangle. In order to accomplish this the map projects the 3D ellipsoid to this 2D map using what is called a Mercator projection. This ends up stretching the map out at the poles. So what does this all mean, well it means that the shortest distance between two locations on the map is rarely a straight line and is actually a curved path, commonly referred to as a geodesic path. Here is an image with a path connecting Seattle, New York, London and Italy. The red line connects these locations using straight lines while the purple line shows the equivalent geodesic path.

clip_image023

So which type of line do you want to animate with? Straight line paths are great for generic animations where you want to move things across the screen and only really care about the start and end point. Whereas geodesic lines are great for when you want the path to be spatially accurate, such as when animating the path of an airplane. It’s worth noting that when you are working with short distances the differences between are very minor.

Animating along a straight path is fairly easy. One method is to calculate the latitude and longitude differences between two locations and then divide these values by the number of frames in the animation to get a single frame offset values for latitude and longitude. Then when each frame is animated we take the last calculate coordinate and add these offsets to the latitude and longitude properties to get the new coordinate to advance the animation to.

Animating along a geodesic path is a bit more difficult. One of our Bing Maps MVP’s, Alastair Aitchison, wrote a great blog post on creating geodesic lines in Bing Maps. The process of creating a geodesic line consists of calculating a several midpoint locations that are between two points. This can be done by calculating the distance and bearing between the two locations. Once you have this you can divide the distance by the number of mid-points you want to have and then use the distance to each midpoint and the bearing between the two end points to calculate the coordinate of the mid-point location. To help us with this type of animation we will create some helper functions to do these calculations. Add the following code to the animation module. These functions allow you to calculate the Haversine distance between two locations (distance along curvature of the earth), bearing and a destination coordinate.

 function degToRad(x) {
    return x * Math.PI / 180;
}

function radToDeg(x) {
    return x * 180 / Math.PI;
}

function haversineDistance(origin, dest) {
    var lat1 = degToRad(origin.latitude),
        lon1 = degToRad(origin.longitude),
        lat2 = degToRad(dest.latitude),
        lon2 = degToRad(dest.longitude);

    var dLat = lat2 - lat1,
    dLon = lon2 - lon1,
    cordLength = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dLon / 2), 2),
    centralAngle = 2 * Math.atan2(Math.sqrt(cordLength), Math.sqrt(1 - cordLength));

    return EARTH_RADIUS_KM * centralAngle;
}

function calculateBearing(origin, dest) {
    var lat1 = degToRad(origin.latitude);
    var lon1 = origin.longitude;
    var lat2 = degToRad(dest.latitude);
    var lon2 = dest.longitude;
    var dLon = degToRad(lon2 - lon1);
    var y = Math.sin(dLon) * Math.cos(lat2);
    var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
    return (radToDeg(Math.atan2(y, x)) + 360) % 360;
}

function calculateCoord(origin, brng, arcLength) {
    var lat1 = degToRad(origin.latitude),
    lon1 = degToRad(origin.longitude),
    centralAngle = arcLength / EARTH_RADIUS_KM;

    var lat2 = Math.asin(Math.sin(lat1) * Math.cos(centralAngle) + Math.cos(lat1) * Math.sin(centralAngle) * Math.cos(degToRad(brng)));
    var lon2 = lon1 + Math.atan2(Math.sin(degToRad(brng)) * Math.sin(centralAngle) * Math.cos(lat1), Math.cos(centralAngle) - Math.sin(lat1) * Math.sin(lat2));

    return new Microsoft.Maps.Location(radToDeg(lat2), radToDeg(lon2));
}

For the path animation we will create a class that extends from the base animation class we created earlier. When creating the path animation we will have this class take in four parameters;

  • path - The path and consist of an array of Microsoft.Maps.Location objects.
  • intervalCallback - A callback function that will be triggered on each frame interval. The interval callback function will receive three parameters; a midpoint location, the last path location that has been passed, and the frame index of the animation.
  • isGeodesic - A Boolean value that indicates if the path animation should follow the geodesic path or a straight path.
  • duration – The length of time the animation should take to complete.

When the path animation is created it will pre-calculate all the midpoint locations that the animation passes through. As a result little to no calculations needing to be performed when the animation advances a frame and thus should create a smooth animation. Add the following code for the path animation class to the animation module.

 this.PathAnimation = function (path, intervalCallback, isGeodesic, duration) {
    var _totalDistance = 0, 
        _intervalLocs = [path[0]],
        _intervalIdx = [0],
        _frameCount = Math.ceil(duration / _delay), idx;

    var progress, dlat, dlon;

    if (isGeodesic) {
        //Calcualte the total distance along the path in KM's.
        for (var i = 0; i < path.length - 1; i++) {
            _totalDistance += haversineDistance(path[i], path[i + 1]);
        }
    }else{
        //Calcualte the total distance along the path in degrees.
        for (var i = 0; i < path.length - 1; i++) {
            dlat = (path[i + 1].latitude - path[i].latitude);
            dlon = (path[i + 1].longitude - path[i].longitude);

            _totalDistance += Math.sqrt(dlat*dlat + dlon*dlon);
        }
    }

    //Pre-calculate midpoint locations for smoother rendering.
    for (var f = 0; f < _frameCount; f++) {
        progress = (f * _delay) / duration;

        var travel = progress * _totalDistance;
        var alpha;
        var dist = 0;
        var dx = travel;

        for (var i = 0; i < path.length - 1; i++) {

            if(isGeodesic){
                dist += haversineDistance(path[i], path[i + 1]);
            }else {
                dlat = (path[i + 1].latitude - path[i].latitude);
                dlon = (path[i + 1].longitude - path[i].longitude);
                alpha = Math.atan2(dlat * Math.PI / 180, dlon * Math.PI / 180);
                dist += Math.sqrt(dlat * dlat + dlon * dlon);
            }

            if (dist >= travel) {
                idx = i;
                break;
            }

            dx = travel - dist;
        }

        if (dx != 0 && idx < path.length - 1) {
            if (isGeodesic) {
                var bearing = calculateBearing(path[idx], path[idx + 1]);
                _intervalLocs.push(calculateCoord(path[idx], bearing, dx));
            }else{
                dlat = dx * Math.sin(alpha);
                dlon = dx * Math.cos(alpha);

                _intervalLocs.push(new Microsoft.Maps.Location(path[idx].latitude + dlat, path[idx].longitude + dlon));
            }

            _intervalIdx.push(idx);
        }
    }

    //Ensure the last location is the last coordinate in the path.
    _intervalLocs.push(path[path.length - 1]);
    _intervalIdx.push(path.length - 1);

    return new Bing.Maps.Animations.BaseAnimation(
        function (progress, frameIdx) {

            if (intervalCallback) {
                intervalCallback(_intervalLocs[frameIdx], _intervalIdx[frameIdx], frameIdx);
            }
        }, duration);
};

Now that the path animation class is created we can start implementing it. The first animation will move a pushpin along either a straight or geodesic the path. Update the MovePinOnPath function in the AnimatedMap.js file with the following code.

 function MovePinOnPath(isGeodesic) {
    ClearMap();

    var pin = new Microsoft.Maps.Pushpin(path[0]);
    map.entities.push(pin);

    currentAnimation = new Bing.Maps.Animations.PathAnimation(path, function (coord) {
        pin.setLocation(coord);
    }, isGeodesic, 40000);

    currentAnimation.play();
}

If you run the application and press the "Move Pin Along Path" button you will see a pushpin follow a straight line between the path locations. The following animated gif shows what this animation will look like. I’ve added in a red line as a reference of the straight line path.

clip_image024

If you press the "Move Pin Along Geodesic Path" button you will see a pushpin follow a geodesic path between the locations as you can see in the following animated gif. I’ve also included the straight line between the locations as a reference.

clip_image025

The next path animation we will implement will move the map along either a straight or geodesic path. Update the MovePinOnPath function in the AnimatedMap.js file with the following code.

 function MoveMapOnPath(isGeodesic) {
    ClearMap();

    //Change zooms levels as map reaches points along path.
    var zooms = [5, 4, 6, 5];

    map.setView({ center: path[0], zoom: zooms[0] });

    currentAnimation = new Bing.Maps.Animations.PathAnimation(path, function (coord, idx) {
        map.setView({ center: coord, zoom: zooms[idx] });
    }, isGeodesic, 100000);

    currentAnimation.play();
}

Pressing the "Move Map Along Path" or "Move Map Along Geodesic Path" buttons you will see the map pan from one location to another, while changing zoom levels when it passes one of the path points. I have not included animated gif’s for this animation as they ended up being several megabytes in size.

The final path animation we will implement will animate the drawing of the path line. Update the DrawPath function in the AnimatedMap.js file with the following code.

 function DrawPath(isGeodesic) {
    ClearMap();

    var line;

    currentAnimation = new Bing.Maps.Animations.PathAnimation(path, function (coord, idx, frameIdx) {
        if (frameIdx == 1) {
            //Create the line the line after the first frame so that we have two points to work with.
            line = new Microsoft.Maps.Polyline([path[0], coord]);
            map.entities.push(line);
        }
        else if (frameIdx > 1) {
            var points = line.getLocations();
            points.push(coord);
            line.setLocations(points);
        }
    }, isGeodesic, 40000);

    currentAnimation.play();
}

If you run the application and press the "Draw Path" button you will see a pushpin follow a straight line between the path locations. The following animated gif shows what this animation will look like.

clip_image026

If you run the application and press the "Draw Geodesic Path" button you will see a pushpin follow a straight line between the path locations. The following animated gif shows what this animation will look like.

clip_image027

Wrapping Up

In this blog we have seen a number of different ways to animate data on Bing Maps. Let your imagination go wild and create some cool animations. As mentioned at the beginning of this blog post the full source code can be found in the MSDN Code Samples here.

Comments