upbeatdesignsnet upbeatdesignsnet - 3 months ago 5
jQuery Question

How do I restart this animated map route?

I am using 'Gmaps Animted Route (found here https://github.com/henriquea/gmaps-animated-route), and right now I have everything working fine -- however I would like for the animation to reset when the user clicks on 'Animate Route' again. I have no idea where to start or what to edit in the files so that the map will reset. Any help is greatly appreciated.

please let me know if there is more information or code that you need from me.

route.js

define([
'gmaps',
'../underscore',
'./points',
'./filters',
'./styles',
'./animation'],
function(gmaps, _, points, filters, styles, animateRoute){

function Route(options) {
this.options = this.extend(this._options, options);
this.init();
}

Route.prototype = {

// default options
_options: {
initializeFilters: true,
animate: true
},

map: {},

mapTileListener: null,

coordinates: [],

line: {},

enabledFilters: {},

init: function(){
this.enabledFilters = (this.options.initializeFilters ? filters : {});
this.parseJSON(points);
},

parseJSON: function(data){

this.coordinates = data.map(function(item){
return {
lat: item.latitude,
lng: item.longitude,
timestamp: item.timestamp,
googLatLng: new gmaps.LatLng(item.latitude, item.longitude)
}
});

this.drawMap();
},

drawMap: function() {

var self = this,
forEach = Array.prototype.forEach;

self.map = new gmaps.Map(document.querySelector(".map"), {
center: new gmaps.LatLng(41.712167, -90.689098),
zoom: 14,
mapTypeId: gmaps.MapTypeId.ROADMAP,
styles: styles,
panControl: false,
zoomControl: false,
mapTypeControl: false,
streetViewControl : false,
scrollwheel: false,
zoomControlOptions : {
position: gmaps.ControlPosition.LEFT_BOTTOM,
style: gmaps.ZoomControlStyle.LARGE
}
});

// Wait map to be fully loaded before set the markers
self.mapTileListener = gmaps.event.addListener(self.map, 'tilesloaded', function(){
self.setMarkers();
gmaps.event.removeListener(self.tileListener);
});

},

setMarkers: function() {

var self = this,
startMarker, endMarker, pin;

pin = new gmaps.MarkerImage('images/pin.png', null, null, null, new gmaps.Size(38,45));

startMarker = new gmaps.Marker({
position: self.coordinates[0].googLatLng,
icon: pin,
map: self.map,
//animation: google.maps.Animation.DROP
});

endMarker = new google.maps.Marker({
position: self.coordinates[self.coordinates.length-1].googLatLng,
icon: pin,
map: self.map,
//animation: google.maps.Animation.DROP
});

//self.updateRoutes();
},

updateRoutes: function() {

var pathCoordinates = _.pluck(this.normalizeCoordinates(), "googLatLng");

if(this.options.animate) {
this.enabledFilters = filters;
pathCoordinates = _.pluck(this.normalizeCoordinates(), "googLatLng");
animateRoute(pathCoordinates, this.map);
return;
}

this.line = new gmaps.Polyline({
path: pathCoordinates,
geodesic: false,
strokeColor: '#fff000',
strokeOpacity: 1,
strokeWeight: 2
});

this.line.setMap(this.map);

},

// Remove potentially erroneous points
normalizeCoordinates: function() {

var self = this;
var filtersList = _.keys(self.enabledFilters);

return _.reduce(filtersList, function(memo, filter) {
return self.enabledFilters[filter](memo);
}, self.coordinates);

},

playAnimation: function() {

if (this.line.setMap) {
this.line.setMap(null);
}

this.options.animate = true;
this.updateRoutes();

},

extend: function(a, b) {

for (var key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;

}

}

return Route;

});


animation.js

define(['gmaps'],function(gmaps){
var animationIndex = 0;

function animateRoute(coords, map) {

var self = this,
step = 0,
numSteps = 100,
animationSpeed = 0.10,
offset = animationIndex,
nextOffset = animationIndex + 1,
departure, destination, nextStop, line, interval;

if (nextOffset >= coords.length) {
clearInterval(interval);
return false;
}

departure = coords[offset];
destination = coords[nextOffset];

line = new gmaps.Polyline({
path: [departure, departure],
geodesic: false,
strokeColor: '#fff000',
strokeOpacity: 0.5,
strokeWeight: 4,
map: map
});

interval = setInterval(function() {
step++;
if (step > numSteps) {
animationIndex++;
animateRoute(coords, map);
clearInterval(interval);
} else {
nextStop = gmaps.geometry.spherical.interpolate(departure,destination,step/numSteps);
line.setPath([departure, nextStop]);
}
}, animationSpeed);
}

return animateRoute;
});


points.js

// array of points (latitude, longitude and timestamp)
define([], function() {
return [
{
"latitude": "41.71312",
"longitude": " -90.68956",
"timestamp": "100"
},
{
"latitude": "41.70579",
"longitude": "-90.68943",
"timestamp": "200"
},
{
"latitude": "41.70566",
"longitude": "-90.69617",
"timestamp": "300"
},
{
"latitude": "41.70265",
"longitude": "-90.69411",
"timestamp": "400"
},
{
"latitude": "41.69951",
"longitude": "-90.70046",
"timestamp": "500"
},
{
"latitude": "41.70252",
"longitude": "-90.70638",
"timestamp": "600"
},
{
"latitude": "41.70598",
"longitude": "-90.70775",
"timestamp": "700"
},
{
"latitude": "41.70566",
"longitude": "-90.69248",
"timestamp": "800"
},
{
"latitude": "41.7136",
"longitude": "-90.69213",
"timestamp": "900"
},
{
"latitude": "41.71376",
"longitude": "-90.69574",
"timestamp": "1000"
},
{
"latitude": "41.71947",
"longitude": "-90.696",
"timestamp": "1100"
},
{
"latitude": "41.7169",
"longitude": "-90.69016",
"timestamp": "1200"
}
]
})


filters.js

define(['gmaps', '../GDouglasPeuker'], function(gmaps, GDouglasPeuker) {

return {
/*
* Douglas Peucker line simplification routine
* http://www.bdcc.co.uk/Gmaps/GDouglasPeuker.js
*/
GDPeuker: function(data) {

var gdp = GDouglasPeuker(_.pluck(data, "googLatLng"), 23),
result = _.filter(data, function(c) {
return _.contains(gdp, c.googLatLng);
});

return result;
},

/*
* Calculate a maxium possible distance between the coordinates
* http://thinkmetric.org.uk/speed.html
*/
maxDistanceTravelled: function(data) {

var maxMetersPerSec = 13, // 50km/h
i, curr, last, result = [];

for(i=0;i<data.length;i++) {

curr = data[i];

if (last) {

// seconds between current and last coord
var diff = curr.timestamp - last.timestamp;
var maxDistance = diff * maxMetersPerSec;
var traveledDistance = gmaps.geometry.spherical.computeDistanceBetween(last.googLatLng, curr.googLatLng);

if (traveledDistance > maxDistance) {
continue;
} else {
result.push(curr);
}

} else {
result.push(curr);
}

last = curr;

}

return result;
}
}

});

Answer

Sorry I can't figure out how to make a demo. There's just too much stuff for me to piece together.

I tried the following changes locally. With the changes, the path redraws on clicking the button, but only after the current animation is complete.

I had a hard time getting the interval to clear correctly, so instead I invisibly block the button's action until the current animation is complete.

I rely on "global state", because I want only a single animation to occur at any time. I know it's not ideal, but I tried alternatives. The easy way won.

The changes I made are in route.js and animation.js. I assume you didn't change either file. I am including the entire files for ease of copying. The parts where I made changes are commented with // ADD.

route.js

define([
  'gmaps',
  '../underscore',
  './points',
  './filters',
  './styles',
  './animation'],
function(gmaps, _, points, filters, styles, animateRoute){

  function Route(options) {
    this.options = this.extend(this._options, options);
    this.init();
  }

  Route.prototype = {

    // default options
    _options: {
      initializeFilters: true,
      animate: false
    },

    map: {},

    mapTileListener: null,

    coordinates: [],

    // ADD references to later delete the line
    line: { segments: [] },

    // ADD, because cannot figure out how to manipulate interval
    globalState: { idle: true },


    enabledFilters: {},

    init: function(){
      this.enabledFilters = (this.options.initializeFilters ? filters : {});
      this.parseJSON(points);
    },

    parseJSON: function(data){

      this.coordinates = data.map(function(item){
        return {
          lat: item.latitude,
          lng: item.longitude,
          timestamp: item.timestamp,
          googLatLng: new gmaps.LatLng(item.latitude, item.longitude)
        }
      });

      this.drawMap();
    },

    drawMap: function() {

      var self = this,
          forEach = Array.prototype.forEach;

      self.map = new gmaps.Map(document.querySelector(".map"), {
        center: new gmaps.LatLng(51.512361, -0.1404834),
        zoom: 13,
        mapTypeId: gmaps.MapTypeId.ROADMAP,
        styles: styles,
        panControl: false,
        zoomControl: false,
        mapTypeControl: false,
        streetViewControl : false,
        scrollwheel: false,
        zoomControlOptions : {
          position: gmaps.ControlPosition.LEFT_BOTTOM,
          style: gmaps.ZoomControlStyle.LARGE
        }
      });

      // Wait map to be fully loaded before set the markers
      self.mapTileListener = gmaps.event.addListener(self.map, 'tilesloaded', function(){
        self.setMarkers();
        gmaps.event.removeListener(self.tileListener);
      });

    },

    setMarkers: function() {

      var self = this,
          startMarker, endMarker, pin;

      pin = new gmaps.MarkerImage('images/pin.png', null, null, null, new gmaps.Size(26,31));

      startMarker = new gmaps.Marker({
        position: self.coordinates[0].googLatLng,
        icon: pin,
        map: self.map,
        //animation: google.maps.Animation.DROP
      });

      endMarker = new google.maps.Marker({
        position: self.coordinates[self.coordinates.length-1].googLatLng,
        icon: pin,
        map: self.map,
        //animation: google.maps.Animation.DROP
      });

      //self.updateRoutes();
    },

    updateRoutes: function() {

      var pathCoordinates = _.pluck(this.normalizeCoordinates(), "googLatLng");

      if(this.options.animate) {
        this.enabledFilters = filters;
        pathCoordinates = _.pluck(this.normalizeCoordinates(), "googLatLng");
        // ADD globals
        animateRoute(pathCoordinates, this.map, this.line, 0, this.globalState);
        return;
      }

      this.line = new gmaps.Polyline({
        path: pathCoordinates,
        geodesic: false,
        strokeColor: '#f1d32e',
        strokeOpacity: 1,
        strokeWeight: 2
      });

      this.line.setMap(this.map);

    },

    // Remove potentially erroneous points
    normalizeCoordinates: function() {

      var self = this;
      var filtersList = _.keys(self.enabledFilters);

      return _.reduce(filtersList, function(memo, filter) {
        return self.enabledFilters[filter](memo);
      }, self.coordinates);

    },

    clearRoute: function() {
      this.line.segments.forEach(function(segment) {
        segment.setMap(null);
      }, null);
    },

    playAnimation: function() {

      // ADD
      if (this.globalState.idle) {
        // ADD
        console.log("Clear route and prepare to animate");
        this.clearRoute();

        if (this.line.setMap) {
          this.line.setMap(null);
        }

        this.options.animate = true;
        this.updateRoutes();
      }
    },

    extend: function(a, b) {

      for (var key in b) {
        if (b.hasOwnProperty(key)) {
          a[key] = b[key];
        }
      }
      return a;

    }

  }

  return Route;

});

animation.js

define(['gmaps'],function(gmaps){
  var animationIndex = 0;

  // ADD global parameters
  function animateRoute(coords, map, line, animationIndex, globalState) {

    // ADD, to make redraw possible
    animationIndex = animationIndex || 0;

    var self = this,
    step = 0,
    numSteps = 20,
    animationSpeed = 0.50,
    offset = animationIndex,
    nextOffset = animationIndex + 1,
    departure, destination, nextStop, line, interval;

    // ADD
    globalState.idle = false;

    console.log("animation offset " + nextOffset);

    if (nextOffset >= coords.length) {
      clearInterval(interval);
      globalState.idle = true;
      return false;
    }

    departure = coords[offset];
    destination = coords[nextOffset];

    // ADD
    line.segments.push(new gmaps.Polyline({
      path: [departure, departure],
      geodesic: false,
      strokeColor: '#f1d32e',
      strokeOpacity: 1,
      strokeWeight: 2,
      map: map

      // ADD RIGHT PARENTHESES
    }));

    interval = setInterval(function() {
      step++;
      if (step > numSteps) {
        animationIndex++;

        // ADD references to globals
        animateRoute(coords, map, line, animationIndex, globalState);
        clearInterval(interval);
      } else {
        nextStop = gmaps.geometry.spherical.interpolate(departure,destination,step/numSteps);
        // ADD segment reference
        line.segments[line.segments.length-1].setPath([departure, nextStop]);
      }
    }, animationSpeed);
  }

  return animateRoute;
});