Duncan's blog

January 22, 2015

Animated paths with Google Maps

Filed under: Google Maps,Javascript — duncan @ 6:39 pm
Tags: , ,

I saw this question on StackOverflow a few days ago.  There was a map with animated polylines being drawn along a set of coordinates, with a minor bug in it.  The very next day I saw another question asking how to animate a route on a map.  I thought the approach I’d seen the day before was ideal, and posted a slightly re-written version of it as an answer. I thought there was enough merit in the technique used to turn it into a blog post.  Thanks to the OP Alex Man for the original version of the code.

Version 1

Animated_route_-_2015-01-22_18.35.41

function initialize() {
	var map = new google.maps.Map(document.getElementById("map"), {
	  center: {lat: pathCoords[0].lat, lng: pathCoords[0].lng},
	  zoom: 11,
	  mapTypeId: google.maps.MapTypeId.ROADMAP
	});
	
	autoRefresh(map);
}

function moveMarker(map, marker, latlng) {
	marker.setPosition(latlng);
	map.panTo(latlng);
}

function autoRefresh(map) {
	var i, route, marker;
	
	route = new google.maps.Polyline({
		path: [],
		geodesic : true,
		strokeColor: '#FF0000',
		strokeOpacity: 1.0,
		strokeWeight: 2,
		editable: false,
		map:map
	});
	
	marker=new google.maps.Marker({map:map,icon:"http://maps.google.com/mapfiles/ms/micons/blue.png"});

	for (i = 0; i < pathCoords.length; i++) {
		setTimeout(function (coords)
		{
			var latlng = new google.maps.LatLng(coords.lat, coords.lng);
			route.getPath().push(latlng);
			moveMarker(map, marker, latlng);
		}, 200 * i, pathCoords[i]);
	}
}

google.maps.event.addDomListener(window, 'load', initialize);

var pathCoords = [
	{
	"lat": 8.893260000000001,
	"lng": 76.61427
	},
	{
	"lat": 8.894430000000002,
	"lng": 76.61418
	},
	// etc for hundreds more coordinates

View full code

The interesting bit here is the ‘autoRefresh‘ function.  That creates an empty polyline, then loops over the global pathCoords array.  For each set of coordinates, it creates a call to this anonymous function, at 200 millisecond intervals:

function (coords) {
	var latlng = new google.maps.LatLng(coords.lat, coords.lng);
	route.getPath().push(latlng);
	moveMarker(map, marker, latlng);
}

So every 200 milliseconds it calls that function.  When using setTimeout I pass in a third parameter, which is a single set of coordinates.  I’m not sure this is an ideal approach, for instance if something in that function took longer than 200ms to execute, things could get a bit disjointed looking.  But it seems to work for now.

For each set of coordinates, it adds it to the current polyline’s path.  You might have thought I’d have to use setPath, but just pushing it onto the array that getPath returns works.

Then I update the position of the marker to the current point, and pan the map.

One thing worth noting: Google Maps have started accepting coordinates in this format in a lot of places:

	{lat: 1.234, lng: 2.345}

instead of having to always create new LatLng objects each time:

	new google.maps.LatLng(1.234, 2.345)

In the example above, the coords object was fine when passed straight to the moveMarker function, i.e. for setting the marker and panning the map.  However it didn’t work when pushing it into the array of the polyline’s path.  So I still had to create a LatLng object for that.  Possibly if I’d saved the polyline path from getPath into a variable, appended the coords parameter, then done setPath with the full array, it would have been fine.

See the map working here

Version 2

Animated_route_-_2015-01-22_18.36.08

Instead of having to hardcode hundreds of coordinates in our javascript, we can use the DirectionsService, simply specifying a start and end point.  The data it returns includes a list of all the coordinates we’d need.  Then instead of rendering the directions, we can simply use those coordinates to draw our own animated path.

function initialize() {
    var map = new google.maps.Map(document.getElementById("map"), {
      center: {lat: 51.5087531, lng: -0.1281153},
      zoom: 7,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    });
    
    getDirections(map);
}

function moveMarker(map, marker, latlng) {
    marker.setPosition(latlng);
    map.panTo(latlng);
}

function autoRefresh(map, pathCoords) {
    var i, route, marker;
    
    route = new google.maps.Polyline({
        path: [],
        geodesic : true,
        strokeColor: '#FF0000',
        strokeOpacity: 1.0,
        strokeWeight: 2,
        editable: false,
        map:map
    });
    
    marker=new google.maps.Marker({map:map, icon:"http://maps.google.com/mapfiles/ms/micons/blue.png"});

    for (i = 0; i < pathCoords.length; i++) {                
        setTimeout(function(coords) {
            route.getPath().push(coords);
            moveMarker(map, marker, coords);
        }, 200 * i, pathCoords[i]);
    }
}

function getDirections(map) {
    var directionsService = new google.maps.DirectionsService();

    var request = {
        origin: new google.maps.LatLng(51.5087531, -0.1281153),
        destination: new google.maps.LatLng(48.8583694, 2.2944796),
        travelMode: google.maps.TravelMode.DRIVING
    };
    directionsService.route(request, function(result, status) {
        if (status == google.maps.DirectionsStatus.OK) {
            autoRefresh(map, result.routes[0].overview_path);
        }
    });
}

google.maps.event.addDomListener(window, 'load', initialize);

See the second map working here

Again, I wasn’t able to just use a coords struct, I had to create LatLng objects for passing into the DirectionsService request. The directions service happens asynchronously, so I have to wait for when it returns back an ‘OK’ status before creating the path.

Apart from replacing the array of coordinates with the value returned by the DirectionsService’s overview_path property, the code for drawing the path is basically the same as before.

Further ideas:

  • Instead of just creating a polyline from all the coordinates in the overview_path, we could loop over all the legs of the route.  This would give us more flexibility to amend the polyline, e.g. you could examine the duration property of each DirectionsStep and change the colour of the polyline.
  • We could use the bounds property of the DirectionsRoute to update the map’s bounds so the entire route is always visible.  Doing this you wouldn’t need to constantly pan the map centre.
Advertisements

39 Comments »

  1. Impressive work here… I am Duncan (straydog) from Stack Overflow and I also come from Clifton, Bristol but now I live in Cancun working as a contract programmer… nothing sinister here, just giving you a thumbs up for your highly respected codings

    Comment by Duncan — June 21, 2015 @ 11:22 am | Reply

  2. You mention setTimeout(function(coords) , yet I don’t see coords being passed in or any variable by that name… only see it as a param.

    Comment by Red Smith — December 1, 2015 @ 10:27 pm | Reply

    • With setTimeout you can pass optional additional parameters after the first two (the function and the milliseconds to wait). These will then be passed as parameters to your function. e.g. you could do

      function doStuff (name, age, gender)

      Then setTimeout(doStuff, 1000, “Joe”, 30, “Male”) and those last three values would correspond to the name, age and gender arguments in the doStuff function. So in this case I’m passing it pathCoords[i] and the argument is referred to as coords in the anonymous function.

      Comment by duncan — December 1, 2015 @ 10:42 pm | Reply

  3. Thanks 🙂 Great work!

    Comment by BA — March 15, 2016 @ 1:31 pm | Reply

  4. really i like this post , actually i’ve started working on a web application to map the bus position this start helps me to get the abc

    Comment by Bilel Khaled — March 22, 2016 @ 7:44 pm | Reply

  5. WOw. . . This is a good thing to explore. . , do you think, what’re we can make some pause or speed up feature ?

    Comment by Sepryadi — June 9, 2016 @ 4:48 am | Reply

  6. I opened the two map examples but no animation. Refresh many times, still no animation.

    Comment by Alex Alan — July 2, 2016 @ 11:09 am | Reply

    • Alex, I think you have to have apache or some kind of local web server so make the examples work.

      Comment by JT — July 2, 2016 @ 6:26 pm | Reply

  7. I am trying to animate 2 paths using your first example but only one is marker moves while the other stays still. May I send you the code to look at? Maybe submit a pull request on github?

    Comment by Jimmy Touma — July 5, 2016 @ 3:57 pm | Reply

    • Sure, although I can’t promise I’ll be able to fix it.

      Comment by duncan — July 5, 2016 @ 5:14 pm | Reply

      • Okay thanks. I tried running your original code by itself (1.html) but now I am getting an error on this line (on Chrome and Firefox):
        route.getPath().push(latlng);
        The error says “Uncaught TypeError: Cannot read property ‘push’ of undefined”
        it was running last week and now I get this error. Any ideas?

        Comment by Jimmy Touma — July 5, 2016 @ 5:37 pm

    • Hmm your code seemed to work to me, no JS errors. I made a change to make the 2nd marker animation slower, given they’re both on the same path it’s hard to see them both otherwise. Seemed fine in FF and Chrome; could it be something else causing the problem? Maybe you need to specify an API key; that’s become mandatory for new maps recently. https://github.com/duncancumming/maps/commit/d7c101f1b96e3bca2164aa2f2847c82a1813cf77#diff-f4858134641f9d4c25f7aac683db18cb

      Comment by duncan — July 5, 2016 @ 10:36 pm | Reply

      • You’re right, my original code was ok. may Apache server was causing the issues. Things are working now. Thanks again

        Comment by Jimmy Touma — July 6, 2016 @ 4:12 am

  8. Thanks. I may have to clear the cache in my browser or reboot the system. I’ll keep you updated.

    Comment by Jimmy Touma — July 5, 2016 @ 11:31 pm | Reply

  9. hi, will this script work with real-time tracking? the cords are passed to the script via js or websocket?

    Comment by Toby Benjamin — July 5, 2016 @ 11:53 pm | Reply

    • It could do. Instead of having an array of coordinates to loop over, get your coordinates on a timeout, pushing them onto the path.

      Comment by duncan — July 6, 2016 @ 8:19 am | Reply

  10. This is nicely done! I like the look when the cursor position is fixed like this. I’m using this to follow the path of ocean drifters (see example below). However, as the example below shows (be patient; it’s a couple minutes in), some of the drifters suddenly cover a lot of ground in a hurry, and you suddenly lose any perspective of where they are. Any thoughts on how one might modify the first code so that the scale changes on the fly (that is, at certain points in the animation)?

    Comment by Alan Harvey — August 9, 2016 @ 8:19 pm | Reply

    • Could you calculate the distance between each set of coordinates, and adjust the zoom based on that? Either using Google’s own computeDistanceBetween function, or it might be smarter to write your own Haversine formula function and calculate those distances in advance before you start the animation.

      Comment by duncan — August 9, 2016 @ 11:00 pm | Reply

  11. Thanks Duncan! It is quite interesting; I modified the code to my needs and works OK. However, when I tried to change the color of the marker after certain path, it will only have a single color. I have an array of colors and for some reason the icon is getting the last color in the array. Do you have any suggestion on how to change the color for certain path of the track?

    Comment by Senai — September 9, 2016 @ 3:24 am | Reply

    • It should be possible. Can you share your code on somewhere like jsfiddle?

      Comment by duncan — September 9, 2016 @ 8:16 am | Reply

      • I just figured it out. Thanks!

        Comment by Senai — September 13, 2016 @ 3:39 am

  12. How can i update pathCoords to Live drawing and updating a Polyline in google map?

    Comment by Pankaj S — September 19, 2016 @ 8:36 am | Reply

    • Can you share what code you’ve got so far? Possibly asking on StackOverflow too (if so, link your question here and I’ll take a look)

      Comment by duncan — September 19, 2016 @ 10:05 am | Reply

  13. Hi, great work and I am trying to modify it. I search for to find a solution that is halfway betwwen the first and the second solution.
    It have to contains the ” travelMode: google.maps.DirectionsTravelMode.DRIVING” but with the possibility of the array of path coords as in the first code….
    Is it possible? I did a lot of tests but without success….

    Comment by Maurizio Pro — November 17, 2016 @ 4:44 pm | Reply

    • Not that I know of. You could specify up to 8 positions as waypoints for the DirectionsRequest, but I’m guessing that’s not enough for your needs. Unless you could make multiple DirectionsRequests for groups of coordinates, and then display it as if it’s all just one journey (I’m not sure if there’s a limit on how many of these requests you can make at a time).

      Comment by duncan — November 17, 2016 @ 9:11 pm | Reply

      • It could be enough because I have to create on the same map 8 different paths composed by 5-6 or 7 steps…..
        Can you help me?

        Comment by Maurizio Pro — November 18, 2016 @ 12:34 am

  14. You just save my day. Thanks!
    One thing I found it interesting is that when you use the coords looped from result.routes[0].overview_path, it should be a new google.maps.LatLng(51.5087531, -0.1281153) object, right?
    However, if you console.log(coord.lat), it print undefined. And I cannot print its true lat and lng. How that comes?

    Comment by imink — November 18, 2016 @ 3:29 am | Reply

    • Looking at my code, if you were trying to reference the individual LatLng while looping over the array of coords in the overview_path, you should be able to do something like:

      for (i = 0; i < pathCoords.length; i++) {                
              setTimeout(function(coords) {
                  route.getPath().push(coords);
                  moveMarker(map, marker, coords);
      
                  console.log(coords.lat(), coords.lng());
      
              }, 200 * i, pathCoords[i]);
          }
      

      Can you share your code?

      Comment by duncan — November 18, 2016 @ 10:23 am | Reply

  15. If it can work in .aspx page?

    Comment by Steven — April 20, 2017 @ 5:50 am | Reply

    • Yes, it should be able to.

      Comment by duncan — April 20, 2017 @ 8:18 am | Reply

  16. Hi,

    This is a great code.

    However, I’m using python and would like to know how to replicate it there. Can somebody share that code with me?

    Thanks,
    Visaj

    Comment by Visaj Desai — May 17, 2017 @ 3:56 am | Reply

  17. Hello Duncan, thank you so much this code and explanation. You have explained so well! Really appreciate your effort and help here. This is excatly what I wanted. I am new to Google Maps and Javascript. I am working on a project in which I have to plot a moving path on the map using the coordinates. I have more than 76000 coordinates. So I tried your second example with source and destination. With that code I am getting the path, but it is taking the shorter route to reach the destination.
    Here is the link of my desired path-:https://www.google.com/maps/d/edit?mid=1_vw4yBFqNS9RfviY0y1tHtIQ2A8&ll=30.60137216756369%2C-96.35408464236258&z=19
    . In my project I need to show a particular moving path, but not the short route.
    Is there any way by which we can do that? Also, I have a .csv file for my coordinates. Can ypu show a code by which I can access the coordinates from a .csv file using your code? That will be very very helpful. Thanks once again. 🙂

    Comment by Shivani Arora — October 15, 2017 @ 5:06 am | Reply

  18. Hi Duncan, I have tried importing .csv file using your first explanation. I have posted the code on stack overflow. Here is the link-:
    https://stackoverflow.com/questions/46775439/getting-error-in-animation-of-polylines-in-google-map-using-javascript
    Can you please go throughit and provide some help. Thanks in advance.

    Comment by Shivani Arora — October 16, 2017 @ 8:22 pm | Reply

  19. @Duncan, Hi, when I run your code in windows I get the correct output, but when I run it on Mac I get this error-: Uncaught TypeError:Cannot read property ‘push’ of undefined, at line 47(in timeout function). This error I am getting for all the 300 coordinates. Any help in this will be appreciated.

    Comment by Shivani Arora — October 20, 2017 @ 5:22 pm | Reply

    • which browser?

      Comment by duncan — October 20, 2017 @ 6:05 pm | Reply

      • Chrome

        Comment by Shivani Arora — October 20, 2017 @ 6:16 pm

  20. I get this error if I run the code from MAPM OR XAMP, i.e whne i pass localhost:8888//foldername/filename.html. If I run directly from chrome then I do not get any error. It runs perfectly.

    Comment by Shivani Arora — October 20, 2017 @ 6:24 pm | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: