Adam Hooper's Engineering Tips

SVG/JavaScript mapping

Mapping is all the rage on the Web these days, but it often involves complicated mapping servers, complicated features like zooming and tiling, and complicated coordinate systems.

Try answering this: how can I display a map of a country which shows a certain economic indicator (for instance, proportion of people with education) by administrative region?

Yes, you can fire up Mapnik, worry about OpenStreetMap layers and install server after server to preprocess and prepare the data. But I'm looking for a one-sentence answer.

Here's mine: I display the map as SVG and color it with JavaScript.

Example

I'll start with the fun stuff. Here's an interactive map, entirely public-domain. Play all you want, and "View Source" to play even more. Below, I'll describe how this came to be.

Here is the actual data used for the map. What I mean is, the JavaScript reads the data from this table and colours the map with it. Click to change the indicator shown on the map.

Region
Dodoma315834676723336
Arusha2053234273185311
Kilimanjaro1281235664398518
Tanga31502367806417
Morogoro2661326355284510
Pwani39563462576336
Dar es Salaam87119328889859
Lindi44442072404165
Mtwara285928694611285
Ruvuma156322777367425
Iringa167625676028486
Mbeya166924555332539
Singida27612960527215
Tabora31552769396244
Rukwa306124765132164
Kigoma284821766039216
Shinyanga40463268696243
Kagera255934816813532
Mwanza275230718410425
Mara2462297055234310

Source: 2001 Household Budget Survey

How it works

This guide is written for somebody familiar enough with HTML and JavaScript, and inspired enough by the above map, to want to play around for a few hours.

In broad strokes, you must:

  1. Create an SVG map of the area in question, entirely from closed path elements, with class attributes indicating the Region and District.
  2. Output all the data to map, as in the above table.
  3. Write JavaScript to parse the data and update style attributes in the map.

I suggest you "View Source" on this page if you want to follow along.

1. Create an SVG map

This will take some hunting, and I'm the wrong person to ask for advice. I created this map of Tanzania by running the mi2svg tool on a public-domain MapInfo (.mif) file from maplibrary.org, which I found by starting at OpenStreetMap's Tanzania wiki page. I tidied it up in Inkscape and removed Lake Victoria. It's a bit disappointing because most lakes and national parks aren't shown, and some of its administrative regions are missing or misspelled, but it's better than nothing.

Insert the SVG image into an XHTML document using an iframe tag.

I'm afraid I'm no expert on either SVG or mapping. I don't know any good sources for maps and I don't even know whether SVG can be embedded in HTML documents in the same way. I stopped when I got something that works for me.

2. Output the data

There's a big fad out there called unobtrusive JavaScript. Rather than go into it in detail, I'll merely suggest that instead of outputting a JavaScript array programmatically, you output an actual table element and parse it out in JavaScript. You won't regret it.

I can't help you find the data, either. Census data is always nice, and your organization's in-house data may be great.

3. Write the JavaScript

On document load, my JavaScript (which, incidentally, is public-domain--you are welcome to use it) does this:

  1. Parses the data from the HTML table (into an Object mapping "keys" to numeric values)
  2. Analyzes that data to find the maximum value (it assumes 0 is the semantic minimum) and does any necessary preprocessing so we get a quick function to map values to colors
  3. Walks through every path in the SVG document, looks up our value (using the path's "key", like the key in step 1), finds the color (using our analysis from step 2), and sets the fill color appropriately in the path's style

The crux is the "key": once SVG paths and HTML tds can be translated into the same String key, everything falls into place. If your SVG map and dataset agree, everything will go smoothly. In the case of this particular map, there are some issues which can be resolved by tweaking the SVG map, the dataset, and/or the JavaScript. (I tweaked all three.)

Adding the interactive legend is a simple JavaScript (and styling) exercise.

Pleasures

It's interactive. Just play with the values above and watch it work. Isn't it fun?

It's simple.

Advantages of SVG come for free: for instance, maps scale very nicely to large sizes and high resolutions. To render a PNG version, just use "Print Screen".

Every step is fast. The processing time scales linearly with the size of the dataset and the complexity of the SVG file. (Okay, the sort() I put in the analysis step doesn't technically scale linearly, but it's darned fast anyway.)

The data is open. You can just as easily set it up so that the data is a set of editable text-boxes or is one big CSV textarea. Imagine copy/pasting random spreadsheets into a page to produce maps. (The code, while simple, is not included here. Yet.)

The complex task of generating maps from data can be left in the hands of the slightly-computer literate. There's nothing more satisfying to a programmer than programming himself out of a task forevermore.

Limitations

Great system, right? There are some flaws.

For one thing, Internet Explorer (up to version 8) doesn't support SVG. You'll have to find a workaround (complicated canvas magic? server-side SVG rendering?) which might remove interactivity. Or you can just ignore Internet Explorer or wait for people to take up Internet Explorer 9. (I used this particular map in an internal website at an office where nobody uses Internet Explorer.)

The SVG file is hard to get right: you'll find problems in any map data, and if you're trying to merge multiple maps (for instance, merging lakes and rivers with administrative regions) you might run into inconsistencies. I'm by no means an expert at generating an SVG map.

Your dataset and your SVG must share the exact same definitions. This can be finicky. In this example map of Tanzania, the SVG borders are at the district level while the dataset operates in terms of regions. The dataset isn't perfect, either: it's missing Manyara and the five regions of Zanzibar. Try not to think in terms of "correct" and "incorrect": just get things to match up. Set VERBOSE=true in the JavaScript to display those inconsistencies.

The more time you spend, the more ideal your SVG and dataset can become.

Finally, if you're trying to generate a map while keeping the data behind it private, this whole system will not work. If you "Print Screen" to make a PNG file, though, the PNG version won't carry your data table.

Because each SVG file is unique and each dataset is, too, lots of the JavaScript code here isn't portable to another project. So the last limitation of this mapping technique is its learning curve.

Still, it's easier than setting up a map server.

Conclusions

I can't think of another way to map data in such a quick and satisfying manner, either on web pages or in standalone applications. The concept and the code are simple and intuitive.

Internet Explorer may prevent this kind of map from adorning many websites, but intranets everywhere can use maps like this to bring data from complicated databases into simple graphics.

Legal

I declare this code to belong to the public domain in every way possible and in every country. Use it however you want without fear of reprisals from me (though if you seek to use these ideas or any others to plan raids and bombings, I urge you, on a more personal level, to reconsider your ambitions).

If you'd like me to share the code with you under a more restrictive license, send me the license file and I'll probably agree.