Empty Pipes



Isochrone Driving Maps of the World

  • 04 Mar 2016
  • |
  • maps
  • javascript
  • d3.js
  • leaflet
  • |

One of my favorite things about maps is the context they provide for overlayed information. This can range from the mundane and orthodox (such as roads and boundaries) to the esoteric and abstract. Nearly a year ago I wrote a blog post which overlayed travel time (isochrone) data on top of a map of Europe. It showed how long it would take somebody to travel from a given city to any other point in Europe using only public transportation. In this post, I do the exact same thing using driving times.

Driving times starting in Vienna, Austria. Notice how the frontier of the contours is more rounded than in Lincoln, NE.

Driving times starting in Lincoln, Nebraska.

The wonderful thing about portraying driving times is that it's possible to make such maps for cities from all over the world. In doing so, we can see the how the transportation infrastructure of a region meshes with the natural features to create a unique accessibility profile. Lincoln (Nebraska) is centered in the USA and has a characteristic diamond shaped travel time profile. Why? Anyone that has looked at a map of the region will certainly have noticed that the roads are arranged in a grid pattern. Thus it takes much longer to travel along the diagonal than to travel straight north and south. Vienna, Austria, on the other hand, has a more circular accessibility profile due to the abundance of roads going in all directions.

The Andes mountains block driving to the east of Santiago, Chile

The Darien Gap separates Central America from South America

Looking at the isochrone map of Santiago, Chile, one can clearly see the how the Andes mountains block travel east of the city. Southeast of panama city, the odd fact that you can't drive between North / Central America and South America becomes clear. Zooming in at identical levels, you can compare cities and see the difference in accessibility of the relatively wealthy South Africa with that of the less developed, wilder Congo. The differences in accessibility between different cities of the world can range from the trivial (Denver, CO vs Lincoln, NE) to the substantial (Perth, Australia vs. Sydney, Australia). Individual cities can have a wide automobile-reachible area (Moscow, Russia) or a narrow, geography, politics and infrastructure-constrained area (Irkutsk, Russia).

The accessibility profile of Kinshasa, Democratic Republic of the Congo is rugged and discontinuous.

Cape Town, South Africa has good links to the interior of the country as well as to Namibia in the north.

Whatever the case, the places that are most interesting are always those that are also most familiar. For this reason, I've provided overlays for most major cities around the world. The map currently shows Vienna, but clicking any of the links below will open a map for that particular city. The travel times were obtained using GraphHopper and OpenStreetMap data so don't be surprised if they differ from those of Google maps.


Other Cities


Errata / Disclaimer

Everything is an estimate. Rounding errors abound. Don't use this for anything but entertainment and curiosity. But you already know that.

Some data may be missing. There may be faster routes. Google Maps certainly finds some better ones. If you find issues, please let me know and I'll do my best to fix them.


How it's made, technically

For each starting city, an area encompassing 30 degrees north, east, south and west was subdivided into a .1 degree grid. Directions from the starting city to each point were calculated using graphhopper and the OSM map files. From this, contours were extracted using matplotlib's contourf function. These contour files are stored as JSONs and loaded as a D3.js overlay on top of a leaflet map.

All of the code used to create these maps is on github. If you have any questions, feel free to ask.


Background Information and Motivation

This project was a logical extension of the isochrone train maps of Europe project.


ES6 modules using Webpack and Gulp

  • 02 Mar 2016
  • |
  • javascript
  • es6
  • tutorial
  • |

Overview

One of the most common scenarios I run into when creating javascript applications is the following.

  1. I start working on some application (let's call it foo).

  2. I start working on a different program (let's call it bar) and need some of the functionality that I already implemented in foo.

In python, accomplishing this is trivial by placing the source files in the same directory and saying import foo. In javascript, it's a little more complicated. Rather than recapitulate a lot of existing documentation about the different ways that one can implement modules in javascript (CommonJS, AMD, etc...), I'll just dive into how I use gulp and webpack to take advantage of the ECMAScript 6 syntax for modules to create reusable javascript components.

Goal

The goal of this project is to create two components: foo and bar. foo will export a function called drawCircle that we can import into bar. bar will then draw a rectangle on top of the circle.

Here's the code for app/scripts/bar.js

import d3 from 'd3';
import {drawCircle} from 'foo';

export function drawRectangleAndCircle(divName) {
    drawCircle(divName);

    d3.select('svg')
    .append('rect')
    .attr('x', 75)
    .attr('y', 75)
    .attr('width', 100)
    .attr('height', 100)
    .classed('bar-rectangle', true)
    .attr('opacity', 0.7)
}

And the relevant code from app/index.html which uses bar.js:

 <!-- build:js scripts/bar.js -->
  <script src='scripts/bar.js'></script>
  <!-- endbuild -->

  <script type='text/javascript'>

  bar.drawRectangleAndCircle('#circle');

  </script>

In order for all of this to function, we need some tools to manage which modules we have installed (npm), resolve the import statements (webpack), translate the es6 to more universally understandable es5 (babel) and to build everything into one package (gulp). The easiest way to get started is to use yeoman to scaffold out the application. To install yeoman, simply use npm:

npm install -g yo

Then, install the generator for this tutorial:

npm install -g generator-gulp-webpack-es6

Setting up foo

Create a directory to host the foo module and scaffold it out:

mkdir foo
cd foo
yo gulp-webpack-es6

When yeoman asks for Your project name:, make sure to enter foo:

You can just use the default values for the other fields. The default values for author name and email address are taken from your ~/.gitconfig if you have one.

That's it. We now have a module which simply draws a circle in the browser. Witness it in all its glory by running:

gulp serve

And pointing your browser to 127.0.0.1:9000. The result should look like this:

Now, we want to install this package locally so that we can import it from our soon-to-be-created package bar. To do this we need to make one minor change to our webpack.config.js by adding the following lines:

  externals: {
      "d3": "d3"
  },

These are necessary to tell webpack not to include d3 in the packaged file. Under normal circumstances this wouldn't be necessary but if I omit it and use npm link as described below, I get the following error:

Uncaught TypeError: Cannot read property 'document' of undefined

It's seems like it happens either because webpack adds a use strict; somewhere in the bundled foo package, or because d3 is included in both foo and bar. In either case, specifying that it's an external package seems to resolve the problem.

To build and install foo so that it can be imported by other modules, just run the following two commands.

gulp build
npm link

The first will compile the code into a single javascript file and the second will install it as a global module on the local computer. From now on, whenever we need to make changes to foo we just need to run gulp build and they will be reflected in every local package that uses foo.

Setting up bar

To create the calling package bar we follow a very similar procedure:

mkdir bar
cd bar
yo gulp-webpack-es6

When yeoman asks for Your project name:, make sure to enter bar. You can use the default values for the other fields.

Now because we want to use the functionality provided in foo, we need to install it:

npm link foo

Adn then we need to modify app/scripts/bar.js and have it implement its own functionality:

import d3 from 'd3';
import {drawCircle} from 'foo';

export function drawRectangleAndCircle(divName) {
    drawCircle(divName);

    d3.select('svg')
    .append('rect')
    .attr('x', 75)
    .attr('y', 75)
    .attr('width', 100)
    .attr('height', 100)
    .classed('bar-rectangle', true)
    .attr('opacity', 0.7)
}

And finally we need to change the <script> code inside index.html to call the newly created function from bar:

<script type='text/javascript'>

bar.drawRectangleAndCircle('#circle');

</script>

If everything worked as it should, running gulp serve should show a lovely circle / square combination:

Generator directory structure

The directory structure of the componenets generated using gulp-webpackes6 is organized so that the configuration files are in the root directory, all of the code is in the app subdirectory, unit tests are in test and the compiled javascript is in dist:

.
├── app
│   ├── index.html
│   ├── scripts
│   │   ├── foo.js
│   │   └── helper_module.js
│   └── styles
│       └── foo.css
├── bower.json
├── dist
│   ├── index.html
│   └── scripts
│       └── foo.js
├── gulpfile.babel.js
├── package.json
└── webpack.config.js

Changing the module's name

The default module is created so that the main file is in app/scripts/module_name.js where module_name is the name given in the generator questionnaire. To change it to something else, you can either re-run the generator or make the following changes:

  • Rename app/scripts/module_name.js
  • Change occurences of module_name in app/index.html
  • Change the entry line in webpack.config.js

Largest Ski Areas On Each Continent

  • 29 Nov 2015
  • |
  • leaflet
  • maps
  • ski-areas
  • |

The chart below shows the five largest ski areas on each continent (except Antarctica). Africa only has three entries because those are the only ski areas I found with annotated ski lifts and slopes. Everything is calculated according to some reasonable yet arbitrary heuristics so please take the rankings with a grain of salt. If you click on a ski area, it will be displayed in the map below.



The Map

All the ski areas in the chart above are annotated on the map below. Clicking on an area above centers the map below.



How it's made

Each ski area is defined by something like an alpha shape, calculated over all the lifts and slopes. Each "alpha shape" -like area is calculated by first creating a Delaunay triangulation of all ski lift and slope waypoints. Every triangle which has a side greater than 1km is then removed. The result is a set of disconnected shapes all composed of multiple triangles. Each aggregation of triangles is what I consider a ski area.

In some cases, this can lead to two ski resorts being connected even though you might have to walk up to 1km to get from one lift to another (e.g. Kitzbühel / SkiWelt above). In the opposite case (e.g. Oukaimeden), a long lift may not be counted simply because the points defining it create degenerate triangles. Nevertheless, in most cases the shapes created reflect the ski areas they represent quite well.

Here's the recipe to re-create the graphic:

  1. Download OpenStreetMap dumps of each continent
  2. Filter for lifts and slopes
  3. Sample points along long segments to make sure they're not split
  4. Calculate a Delaunay triangulation of all the OSM nodes and the sampled points
  5. Exclude all triangles which have an edge longer than 1km (concave hull of sorts)
  6. Polygonize
  7. Google the towns near the resorts to figure out what the resorts are called
  8. Convert to topojson and downsample to reduce the size
  9. Make table using d3.js
  10. Create zoomable map using leaflet.js and a d3.js overlay

Questions? Comments? Twitter (@pkerpedjiev) or email (see about page).