Blog

Creating an Animated Javascript Heat Map with Polymap

Creating a Javascript Animated Heat Map With Polymap


WhereShouldISki.com Powder Rating Visualization

As you may or may not know, I run a website WhereShouldISki.com – weather data-driven recommendations for skiing and snowboarding. One of the items on my to-do list was to create a season-long visualization that shows where the storms occurred in North America.

Attending conferences last year, like Strata NY, there would often be some very beautiful visuals that tell excellent stories. For work I had manually created some rough animated heat maps using HeatMap Lib and iMovie, but surely there has to be a better way to do this within a web browser, right?

After a few hours of research, no obvious answer came up – or I just missed it. There are a number of libraries that will render maps, render points on a map, etc. D3JS, Polymap, Leaflet, and others all fit the bill on this, but what about animation? Not many examples (at least that were obvious). After some experimentation with Polymap, I figured out a way to dynamically generate a “layer” of coordinates. Well – what happens if I have an array of my data points, and iterate through dynamically creating new layers of coordinates and deleting old ones? Bingo.

Below is the strategy and some walkthrough of how I used the Polymap framework to create the animation at http://whereshouldiski.com/viz/2012-2013-viz.php

Strategy

Overall, it’s not that complicated. The strategy is comprised of the four steps

1. Have the ability to create your map with a single date of data loaded dynamically

2. Set up the data as an array of single dates of data

3. Utilize the Javascript timer function to iterate through the array of data, destroying the old data and dynamically loading the new data

4. Add user controls to identify and/or change the date of the data displayed

Step 1 – Collect and Parse Data

WhereShouldISki has a rating system of 1-5 for powder, bluebird, and freezing level, stored as one JSON document per resort per day, for example:

These documents are accessed via ElasticSearch. Polymap uses the GeoJSON format, so after writing a Python script to parse through my relevant dates and data, I have a JSON object that looks something like:

{
type: "FeatureCollection",
features: [
{
  geometry: {
    type: "Point",
  coordinates: [
   -114.36,
  48.02
]
},
 type: "Feature",
id: "blacktailmountain_2012-12-20",
properties: {
count: 2
}
},

Within the array of each FeatureCollection, there is one entry for each coordinate and the weight (count in this case). So let’s build up a pretty large JSON structure to store this – an array of FeatureCollections, one per date.

Step 2 – Set up Polymap

Download Polymap and set up on your web server, whether locally or on the internet/cloud. I have had decent success with using MAMP and updating the Apache web server to my development directory.

I use the tiles at cloudmade, make sure you sign up for your own key. I thought the blue map was rather dramatic, so first let’s set up a blank map centered on the US.

var po = org.polymaps;
var map = po.map()
.container(document.getElementById("map").appendChild(po.svg("svg"))) .add(po.interact())
.add(po.hash())
.center({lat: 46.14, lon: -101.26})
.zoom(4);
map.add(po.image()
.url(po.url("http://{S}tile.cloudmade.com"
 + "YOUR KEY HERE" // http://cloudmade.com/register
 + "/999/256/{Z}/{X}/{Y}.png")
.hosts(["a.", "b.", "c.", ""])));
map.add(po.compass().pan("none"));

With this code, you should have a blank map centered on the US.

Step 3 - Add the Data

Since I am storing the data as a large JSON file, we can use JQuery’s ajax call to retrieve the data while letting the user know the data is loading.

var allDatesData = (function () {
   var allDatesData = null;
   $.ajax({
      'async': false,
      'global': false,
      'url': "allDatesData.json",
      'dataType': "json",
      'success': function (data) {
         allDatesData = data;
      }
   });
   return allDatesData;
})();

Now we have all of the JSON data stored in a variable. In order to add the points for a date onto the map, follow the example code to look something like:

jsonData = po.geoJson()
   .features(allDatesData[i]['features'])
   .on("load", load)
   .clip(false);
map.add(jsonData);

Since allDatesData is an array above, I have to specify which date to populate. Cool, so now we can populate a single date. But how do we animate?

Step 4 – Animate

Since we know Polymap deals with layers, and we have an array of all of our data, seems we just need a way to delete the old layer and add a new layer of the new date’s data. Then, we can make use of the Javascript timer to iterate through the array of data.

var dayIndex = 0;
function startAnimation() {
   myVar=setInterval(function(){refreshData(dayIndex++)},500);
}
function refreshData(i) {
   if (i < allDatesData.length) {
      if (jsonData) {
         map.remove(jsonData);
         unhighlightDate(i-1);
      }

      jsonData = po.geoJson()
         .features(allDatesData[i]['features'])
         .on("load", load)
         .clip(false);
      map.add(jsonData);

      $('#date' + i).addClass('selected');
   } else {
         stopTimer();
   }
}

For some added flair, I added an on-display calendar with a few divs and and then some Javascript to make these clickable:


//set up click actions
for (i=0; i<allDatesData.length; i++) {
   $('#date' + i).click(selectDate);
   $('#date' + i).addClass('clickable');

Now we just need some HTML elements to Start and Stop the animation:

 

 

 

 

 

 

<div id="start" onclick="startAnimation();"> START</div></p> <p><div id="stop" onclick="stopAnimation();">STOP</div></pre> <p> </p>"</p>"</p> <p>(Apologies for the weird formatting on the blog).  In theory, you'll have an animated heatmap!  Of course, many details have been left out because so much is particular to the specific dataset, which is likely where one will spend most of their time - formatting the GeoJSON object.  But if you follow the principles and after much debugging it might turn out nicely!</p>"</p>"</p>"</p>"</p>"