ZIPScribble Map: Switzerland – Part I

Having seen a visualization by Robert Kosara of EagerEyes a loooong time ago, I wanted to try to reproduce it for Switzerland using Processing. This is the first installment of a two-parts post covering this project, in which I will describe how to arrive at an intermediate result. I’ll do that in some detail, maybe this is helpful to somebody.

The visualization is called the ZIPScribbleMap: “ZIP” for postal codes, “Scribble” for rather obvious reasons (as in “it looks like what I doodle while on the phone!”):

Data

First of all I needed a dataset of Swiss postal codes. Ideally this dataset would contain the place names along with the postal codes (for error/quality checking) and would be geocoded (that is, contain geographic coordinates). However, if the postal codes were not geocoded, one could resort to using a (for example, Google’s) geocoding service to hopefully obtain coordinates for the postal codes).

Luckily, I could avoid this extra step, because after a brief search it turned out that Geonames does offer datasets of geocoded postal codes on a per-country basis for download: http://download.geonames.org/export/zip (scroll down for a readme; entry page is here with reference to postal codes in first paragraph).

The data is CC-BY-3.0-licensed, which means we can use it to our ends (even commercial ones) provided we attribute it to Geonames. The Readme accompanying the data says a link to the Geonames website is okay for this purpose.

Basemap

Next I felt I needed a nice looking base map to plot the postal code data on, rather than just an empty canvas. In order to prepare one I decided to use TileMill by MapBox (see this post). TileMill is available for Mac OS and Ubuntu. So I fired up my Ubuntu desktop computer and had TileMill installed. TileMill runs in the browser.

I edited an existing stylesheet to get a very reduced basemap with bright colours. The basemap contains country outlines and major lakes. I exported the basemap as a PNG file for use in Processing.

Note: When you do the export, you have to pay attention to note down the extent coordinates (north, east, south and west boundary coordinates). You need these later on in order to get the spatial reference in Processing right: Processing has to project geographically referenced data (basemap and postal codes) consistently into the screen spatial reference system.

Side-note: While figuring out the design of the basemap I exported different versions several times, because I forgot to take note of the extent coordinates. Later I dropped defining an extent by mouse but entered the extent coordinates numerically into TileMill instead.

Finally: This is what my basemap of Switzerland looked like:

Basemap for Switzerland, by TileMill

The Mechanics of Part I

Time to fire up Processing. I’m using version 1.5.1 on Ubuntu:

Let’s summarise: By now I had my basemap as well as my postal code data ready. The latter is saved in a text file. Both files have to be stored in the data folder of the Processing sketch. You can move them there manually or use the Sketch > Add File…dialog in Processing.

For using the basemap in Processing, you best refer to the Simple static map example by Till Nagel. He offers a nifty MercatorMap class for download, which one can use for handling the projection from geographic coordinates to screen coordinates. Download and save it as MercatorMap.pde in the Processing sketch folder (not the data folder).

Using the MercatorMap class I could load the basemap in Processing as follows (this is where you need the extent coordinates, which you noted when you exported the basemap from TileMill):

[sourcecode language=”java” highlight=”12″]
PImage img;
MercatorMap mercatorMap;

void setup() {
smooth();
noLoop();
// Set up canvas
size(1024, 673);
// Load image
img = loadImage(“mapSWI_bright.png”);
// Initiate mercator map to handle projection
mercatorMap = new MercatorMap(width, height, 47.9, 45.7, 5.78, 10.67);
[/sourcecode]

Outside the setup() method I defined a PImage object in order to store the PNG file of the basemap and a MercatorMap object for handling the projection. In the setup() method I define the canvas size, load the basemap file and (in line 12) instantiate the MercatorMap using the sketch width and height as well as the extent coordinates.

In a second step I added some font definitions (the fonts can be created using Tools > Create Font… in Processing) and the functionality to read the postal code data from the Geonames text file:

[sourcecode language=”java” highlight=”2,3,5,6,17,18,19,20,21,22″]
PImage img;
PFont fontSmall, fontBig, fontBold;
PVector pnt;
MercatorMap mercatorMap;
String[] lines;
int index = 0;

void setup() {
smooth();
noLoop();
// Set up canvas
size(1024, 673);
// Load image
img = loadImage(“mapSWI_bright.png”);
// Initiate mercator map to handle projection
mercatorMap = new MercatorMap(width, height, 47.9, 45.7, 5.78, 10.67);
// Set up fonts for labelling
fontSmall = loadFont(“DejaVuSansCondensed-10.vlw”);
fontBig = loadFont(“DejaVuSerifCondensed-25.vlw”);
fontBold = loadFont(“DejaVuSerifCondensed-Bold-25.vlw”);
// Load zip code data
lines = loadStrings(“zipcodes_SWI.txt”);
[/sourcecode]

Line 22 reads the postal code from the Geonames text file and stores it in a String array lines. Since I had defined the array outside the setup() method, I can access it now inside the draw() method:

[sourcecode language=”java” highlight=”7,8,9,10,11,12,13,14″]
void draw() {
// Draw basemap
image(img, 0, 0);
// Draw zip code locations
noStroke();
fill(250, 150, 0, 80);
while (index < lines.length) {
String[] row = split(lines[index], ‘t’);
float lat = float(row[9]);
float lon = float(row[10]);
pnt = mercatorMap.getScreenLocation(new PVector(lat, lon));
ellipse(pnt.x, pnt.y, 6, 6);
index = index + 1;
}
[/sourcecode]

Let’s look at what happens here in a bit more detail: The following part is inside a loop which iterates through the entries in the String array lines:

[sourcecode language=”java” firstline=”8″]
String[] row = split(lines[index], ‘t’);
float lat = float(row[9]);
float lon = float(row[10]);
[/sourcecode]

Line 8 takes the current entry of the lines array and splits it wherever it finds a tabulator (or ‘t’ in Java). The result is stored in a new String array row. Lines 9 and 10 pick the correct entries from the array, convert them to a floating point number and store it in the variables lat (latitude) and lon (longitude). Thus, now we have the geographic coordinates of a postal code right there.

The next part is still in the loop which iterates through the postal code data. Line 11 creates a new PVector (a Processing object) from the two coordinates I just extracted. This PVector object is subjected to the getScreenLocation() method of Till Nagel’s MercatorMap object.

[sourcecode language=”java” firstline=”11″]
pnt = mercatorMap.getScreenLocation(new PVector(lat, lon));
ellipse(pnt.x, pnt.y, 6, 6);
index = index + 1;
[/sourcecode]

The getScreenLocation() method is pretty self-explanatory: It takes geographic coordinates and using the extent coordinates from when I set up the MercatorMap object, converts them into screen coordinates of this Processing sketch. Then, using the x and y coordinates of the Object pnt we draw an ellipse of size (6, 6), that is, a circle, before we advance the index and continue to iterate through the postal code data (as long as there is data left to read).

Result

Finally, the result is here (click on the figures to see them bigger). I produced versions with different basemaps and with/without additional, labelled cities:

On the way to the ZIPScribble Map: Switzerland these are only intermediate results. Yet I find them nice and insightful enough. Bigger cities (with several postal codes) and agglomerations are quite clearly visible. Also the mountainous regions of Switzerland such as the Alps are discernible in the picture. In alpine regions municipalities and thus postal code regions tend to be bigger than in the rather densely populated lowlands. Overall the maps are certainly not bad approximations of population density.

Any feedback or questions are welcome, feel free to sound off in the comment section or shot me an e-mail.

Below I include the Processing source code of the last version of the Postal Code map:

[sourcecode language=”java” collapse=”true” gutter=”false”]</span>
<pre>// Ralph Straumann
// visurus.wordpress.com
// 2011-10-16
// CC-BY-NC

// Thanks to:
// http://download.geonames.org/export/zip/CH.zip
// http://tillnagel.com/wp-content/uploads/2011/06/MercatorMap.java via http://tillnagel.com/2011/06/tilemill-for-processing

PImage img;
PFont fontSmall, fontBig, fontBold;
PVector pnt;
MercatorMap mercatorMap;
String[] lines;
int index = 0;
boolean placeLabels = true;

void setup() {
smooth();
noLoop();

// Set up canvas
size(1024, 673);

// Load image – either bright or dark basemap
img = loadImage(“mapSWI_bright.png”);
//img = loadImage(“mapSWI_dark.png”);

// Load zip code data
lines = loadStrings(“zipcodes_SWI.txt”);
// Set up fonts for labelling
fontSmall = loadFont(“DejaVuSansCondensed-10.vlw”);
fontBig = loadFont(“DejaVuSerifCondensed-25.vlw”);
fontBold = loadFont(“DejaVuSerifCondensed-Bold-25.vlw”);
// Initiate mercator map to handle projection
mercatorMap = new MercatorMap(width, height, 47.9, 45.7, 5.78, 10.67);
}

void draw() {
// Draw basemap
image(img, 0, 0);

// Draw zip code locations
noStroke();
fill(250, 150, 0, 80);
while (index < lines.length) {
String[] row = split(lines[index], ‘t’);
float lat = float(row[9]);
float lon = float(row[10]);
pnt = mercatorMap.getScreenLocation(new PVector(lat, lon));
ellipse(pnt.x, pnt.y, 6, 6);
index = index + 1;
}

if (placeLabels == true) {
fill(0);
textFont(fontSmall);
pnt = mercatorMap.getScreenLocation(new PVector(46.9167,7.4667));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Berne”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.5842,7.5876));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Basle”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.2095,6.1438));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Geneva”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.0833,8.2667));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Lucerne”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.4236,9.3622));
ellipse(pnt.x, pnt.y, 4, 4);
text(“St. Gallen”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.2295,7.3568));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Sion”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.7151,8.622));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Schaffhausen”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.1889,9.0247));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Bellinzona”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.85,9.5));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Chur”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.1324,7.2441));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Bienne”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.7554,7.6236));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Thun”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.0083,8.9495));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Lugano”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.4916,8.7295));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Winterthur”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.7787,6.6339));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Yverdon-les-Bains”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.3667,8.55));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Zurich”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.5534,6.6971));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Lausanne”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.289,7.5458));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Sierre”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.3433,7.9052));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Olten”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(47.0543,9.4488));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Sargans”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.4855,9.8335));
ellipse(pnt.x, pnt.y, 4, 4);
text(“St. Moritz”, pnt.x+5, pnt.y-5);
pnt = mercatorMap.getScreenLocation(new PVector(46.7783,9.879));
ellipse(pnt.x, pnt.y, 4, 4);
text(“Davos”, pnt.x+5, pnt.y-5);
}

// Add labels
fill(180);
textFont(fontBig);
text(“Postal Codes”, 30, 45);
textFont(fontBold);
text(“Switzerland”, 30, 73);
textFont(fontSmall);
fill(130);
text(“Postal codes CC-BY”, 30, 632);
text(“www.geonames.org”, 30, 645);
textAlign(RIGHT);
text(“CC-BY-NC”, 990, 632);
text(“visurus.wordpress.com”, 990, 645);

save(“PostalCodesSWI.png”);
}
[/sourcecode]

Thanks to Robert Kosara, TileMill, Till Nagel and Geonames!

Ralph Straumann

Ralph is a world-citizen, a geoinformation specialist by profession, and interested in many topics. Here, he'll confine himself mostly to things geo-visual.

You may also like...

10 Responses

  1. Till says:

    Great description of your process, and nice looking results!

    For the next version, one idea would be to get cities and their geolocations from some API directly, e.g. Geoname’s cities method ( http://api.geonames.org/cities?north=47.9&south=45.7&east=10.67&west=5.7&lang=de&maxRows=15&username=demo ). Simply filter out all non-Swiss cities, and you’re good.

  2. Ralph says:

    Thank you for your comment, Till!

    I must admit I had a only a faint idea that Geonames has an API (haven’t used it yet, ever). I suppose you’re suggesting using the API for labelling major cities on the map? Or is there also a query which would yield postal codes along with the city names?

    In any case, thank you for your work and your instructional blog posts!

  1. 27 October 2011

    […] the first installment of this series I explained my process up to an intermediate result: a map depicting all the postal […]

  2. 19 November 2011

    […] I’ve blogged a two-parts tutorial on how to create ZIPScribble Maps using the Processing visualization […]

  3. 22 November 2011

    […] you wonder about the process of producing ZIP Scribble Maps, check out my tutorial: Part I Part II LD_AddCustomAttr("AdOpt", "1"); LD_AddCustomAttr("Origin", "other"); […]

  4. 22 November 2011

    […] you wonder about the process of producing ZIP Scribble Maps, check out my tutorial: Part I Part […]

  5. 20 January 2012

    […] great visualisations of ZIP/postal codes in a country  – there’s also a great piece on the site about how they’re drawn. I think they’re great views of the country – I’m […]

  6. 27 January 2012

    […] have made various ZIPScribble Maps – Switzerland, Germany, France, Italy – as well as a two-part tutorial on how to produce them using Geonames data, TileMill basemaps and Processing as […]

  7. 6 February 2012

    […] I just got news that TileMill now also runs on Windows (besides Mac OS and Linux). TileMill is a browser-based tool to prepare map tiles which you can, for example, overlay on Google Maps or use as basemap in a Processing sketch. The latter of which I have in my ZIPScribble maps series (here, here or here; some background on using TileMill with Processing can be found here and here). […]

  8. 16 July 2014

    […] visuals are inspired by the work of Robert Kosara (The US ZIPScribble Map), Ralph Straumann (ZIPScribble Map: Switzerland / Interactive ZIPScribble Maps) and Ben Fry (zipdecode). Have a look at their blogs as […]

Please let us know your opinion: Reply here

%d bloggers like this: