October 20, 2017 Viswesh Subramanian 0Comment

From prehistoric days to the modern day, we have been taking quantum leaps in mapping land and water masses. It’s amazing how technology has enabled the field of Cartography to reach higher levers each century.

The good ‘ol folks at Natural Earth are doing an exceptional job towards the cause. They curate geospatial data to help professional cartographers and hobbyists in creating maps, which solve real problems and challenges. In their own words –

“Natural Earth solves a problem: finding suitable data for making small-scale maps. In a time when the web is awash in geospatial data, cartographers are forced to waste time sifting through confusing tangles of poorly attributed data to make clean, legible maps. Because your time is valuable, Natural Earth data comes ready-to-use.”

We are at a point in time where amassing data is not enough but using it is of paramount importance. When Google launched Google Maps in 2005, it made headlines and it retained its top story position for the next decade. Since its launch, there has been a paradigm shift in using Maps. Today, it has evolved so much that it finds the shortest path to your destination, offers real-time alternative routes based on real-time conditions, allows searches for restaurants, gas stations, etc on the way. It also goes one step further to alleviate our traffic anxiety by assuring that we are still on the fastest path. Tell me this is not the future!

Now, how can you and I create maps which provide information, tell a story, solve challenges or surface meaningful information? Thanks to JavaScript libraries – they offer the tools and resources to ship your vision in less time.

The 2 most popular representations of Geographic information system (GIS) data are Raster and Vector. Raster is composed of pixels, comparable to tile images. Vectors, on the other hand, are composed of vertices and paths. Before moving further, let’s start with a humble beginning – GeoJSON. GeoJSON is a standards format for representing geographical features in JSON.

{
  "type": "Feature", //FeatureCollection
  "geometry": {
    "type": "Point", // LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon
    "coordinates": [125.6, 10.1]
  },
  "properties": {
    "name": "Dinagat Islands"
  }
}

The above metadata can be transformed into elements on the browser to make meaningful and striking maps. There are a lot of visualization libraries out there – Mapbox, Carto, ArcGIS Online, Here Data Lens, Google Maps, d3, Leaflet, GIS cloud, Mapzen, MapStack and much more.

To get our feet wet, let’s pick a library, use it to create a map, overlay data to present a message and finally sprinkle user interactions like zoom in/out, pan, and tooltips.

Have you ever wondered which country has the largest population? If you have wondered, let me tell you, you are either an Alien or a Sentinelese defector. If you are like the rest of us, China and India should have come to your mind. Let’s put that statement to test by visualizing population on a map. And to put a spin on that, how about visualizing population for a particular age group? Let’s settle on presenting population metrics for age group 16 in 2017.

The final version which you will be building is at the Github Repo. To follow along, clone or download it.

Visualization with Maps
Visualization with Maps

For familiarity sake, let’s use d3.js to draw our map. The game plan –

  1. Find an appropriate GeoJSON.
  2. Draw a map with d3.js by plotting features from the GeoJSON.
  3. Get population data and fill landmass colors correlating with population metrics.
  4. Add interaction.

Head to Natural Earth, navigate to Downloads, select ‘Cultural’ from the desired data set size (Large, Medium, Small) and download an appropriate cultural theme. For our use case, it’s best if we chose a small data size and download ‘Admin 0 – Countries’ theme.

Now, the shp file needs to be converted into GeoJSON. So, for conversion, download shapefile and follow instructions to convert shp to GeoJSON. Let’s just convert shp to GeoJSON in the terminal.

shp2json example.shp

One of the biggest disadvantages of GeoJSON is filesize. As you start collating multiple .shp files into a single GeoJSON, the file size grows out of proportion. This led Mike Bostock, the creator of d3.js to come up with a solution, TopoJSON. TopoJSON is a generic converter which removes repeated metadata and structures geometries in an efficient way.

var topology = topojson.topology({foo: geojson});

To read TopoJSON back into geo-standards, you also need to yank in topojson-client as a vendor dependency. I have already primed the TopoJSON for use, feel free to use it.

This is going to be a full-screen map, so let’s define the width and height –

var width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
    height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);

Append an svg parent to the body. We will add our map to as a sibling to this parent svg.

var svg = d3.select("body")
    .append("svg")
    .style("cursor", "move");

Enabling the svg to be responsive is literally a 2 liner –

svg.attr("viewBox", "50 10 " + width + " " + height)
    .attr("preserveAspectRatio", "xMinYMin");

We now have our canvas ready and all set to paint the map. Let’s load up the data – GeoJSON for drawing the map and population metrics for overlaying information. A quick shout out to api.population.io, our data source.

For take home, you could potentially leverage their RESTful services to visualize life expectancy or mortality distribution.

d3.queue()
    .defer(d3.json, "src/data/50m.json")
    .defer(d3.json, "src/data/population.json")
    .await(function (error, world, data) {
        if (error) {
            console.error('Oh dear, something went wrong: ' + error);
        }
        else {
            drawMap(world, data);
        }
    });

Once both datasets are loaded, we invoke the drawMap callback function where all of the action takes place. First, define the projection and path. Projection converts spherical polygonal geometry to planar polygonal geometry. In other words, coordinates of the world are converted into planar or flat coordinates so that it can be plotted on our 2D viewport. Path consumes these coordinates and draws lines and curves.

// geoMercator projection
    var projection = d3.geoMercator() //d3.geoOrthographic()
        .scale(130)
        .translate([width / 2, height / 1.5]);

// geoPath projection
    var path = d3.geoPath().projection(projection);

d3.js offers other projection types as well. Switching geoMercator to geoOrthographic generated a spherical projection.   

Map projection geoOrthographic
Map projection geoOrthographic

Using topojson-client, convert the TopoJSON back to GeoJSON by calling topojson.feature(). 

var features = topojson.feature(world, world.objects.countries).features;

Now, loop through the GeoJSON features and add details object with population metrics. This will be later used to generate fill colors based on population.

features.forEach(function (d) {
        d.details = populationById[d.properties.name] ? populationById[d.properties.name] : {};
    });

Finally, we are ready to draw the map.

map.append("g")
        .selectAll("path")
        .data(features)
        .enter().append("path")
        ..
        .attr("d", path)
        .style("fill", function (d) {
            return d.details && d.details.total ? color(d.details.total) : undefined;
        })
        .on('mouseover', function (d) {
            //On “mouseover”, set data and styling as appropriate.
        })
        .on('mouseout', function (d) {
            //On “mouseout”, set data and styling as appropriate.
        });

To enable zoom and pan, attach a “zoom” listener for the base svg.

var zoom = d3.zoom()
    .on("zoom", function () {
        var transform = d3.zoomTransform(this);
        map.attr("transform", transform);
    });

svg.call(zoom);

Clearly, drawing a map is not tedious. All it takes is awareness of the tools and utilities. This is great! But how about state names, street names and freeways like you see in Google Maps? I want to see more. Well, let’s reserve that topic for a future post, shall we?

References:

http://geoawesomeness.com/top-19-online-geovisualization-tools-apis-libraries-beautiful-maps/
http://colorbrewer2.org/
http://api.population.io/1.0/population/2017/aged/18/
https://github.com/d3/d3-queue
http://mapstarter.com/
https://medium.com/@aendrew/creating-topojson-using-d3-v4-10838d1a9538
https://github.com/topojson/world-atlas