Tom MacWright

tom@macwright.com

Odd Maps and How to Make them

Update: This post was published in 2011. As of 2017,

Here’s a quick guide to making odd maps. Since most of the time I’m working on TileMill or some part of MapBox’s mapping stack, I make a lot of test maps. The normal stuff gets boring quick, so I tend to make more odd things.

Pixelated Countries

The pixel size of this map is 100,000 meters.

It’s not too hard: just use node.js to run some Javascript to round every coordinate in a world GeoJSON file. I made the rounding value a variable, r, to be able to tinker with values.

var fs = require('fs'),
    countries = JSON.parse(
        fs.readFileSync('countries.geojson', 'utf-8')),
    r = 100000;

function re(n) { return Math.floor(n / r) * r; }

countries.features = countries.features.map(function(f) {
    f.geometry.coordinates =
    f.geometry.coordinates.map(function(c) {
        return c.map(function(d) {
            if (d[0].length === undefined) {
                return [re(d[0]), re(d[1])];
            } else {
                return d.map(function(e) {
                    return [re(e[0]), re(e[1])];
                });
            }
        });
    });
    return f;
});

fs.writeFileSync('countries_px_d.geojson', JSON.stringify(countries), 'utf-8');

So you’d call this file doit.js and have countries.geojson in the same directory, and then run node doit.js and it’ll get done.

Everything Flipped

This map is not a West Wing reference, and I take a hard stance against Mercator hate.

var fs = require('fs'),
    countries = JSON.parse(
        fs.readFileSync('countries.geojson', 'utf-8')),
    r = 100000;

function re(n) { return -n; }

countries.features = countries.features.map(function(f) {
    f.geometry.coordinates =
    f.geometry.coordinates.map(function(c) {
        return c.map(function(d) {
            if (d[0].length === undefined) {
                return [re(d[0]), re(d[1])];
            } else {
                return d.map(function(e) {
                    return [re(e[0]), re(e[1])];
                });
            }
        });
    });
    return f;
});

fs.writeFileSync('reverse.geojson', JSON.stringify(countries), 'utf-8');

No surprises here: basically the same code, but instead of rounding values, I’m flipping them with re(n).

If there can be takeaways here:

GeoJSON

GeoJSON is a wonderful format for messing around in. It’s easy to generate, parse, and modify in practically any language: I’m most comfortable with Javascript and Python, but it flies great in Ruby and what-have-you too.

That said, it’s inefficient. So, I usually use it just as a go-between. Get really friendly with ogr2ogr, because it’ll do the conversion, and lots more. Something like

ogr2ogr -f "GeoJSON" countries.geojson 10m-admin-0-countries.shp
node reverse.js
ogr2ogr reverse.shp reverse.geojson
shapeindex reverse.shp

So: you convert to GeoJSON, do stuff, and then back to a shapefile. Shapefiles are faster and (usually) more space-efficient than GeoJSON.

Python, Ruby, Whatever

What you actually use as a scripting language is pretty unimportant: you just need to get data in, and spit it out. Javascript’s handy, because it’s natural with JSON and with V8/node, it’s faster than Python. But TileMill is unaware of the fact that Javascript or Ruby’s grubby hands were all over this GeoJSON file.

On the same point, these scripts don’t need to be hyper-fast. They generally run once, and even if you have slow constructs, like having to keep a gig of data in memory, if it works, it works. Chill out and make crazy stuff.

Makefiles

For more complex map builds, you can pull together your ogr2ogr magic and your processing scripts and even the open command that opens up QGIS to preview into a Makefile.

I don’t abuse Makefiles or do magic or real scripting in them: they’re just pretty decent for remembering what commands you want to run to do a thing. For instance, I pulled together the stuff for my presentation at foss4g with this little guy:

clean:
        rm -rf testlayer
        rm sorted_merc.*

build:
        python sorted.py testlayer \
            110m-admin-0-countries/110m_admin_0_countries.shp
        ogr2ogr -s_srs EPSG:4326 -t_srs EPSG:900913 \
            sorted_merc.shp testlayer/sorted.shp
        shapeindex sorted_merc.shp
        zip -r sorted_merc.zip sorted_merc.*

.PHONY: build
The `.PHONY` is a trick I learned from [Konstantin](https://kkaefer.com/): it's what `make` runs when you don't give it a specific argument.

See Chris Forbes comment below for what .PHONY actually does.

Not Caring

Making odd maps is generally a practice of making things that look wild, and complex. Usually they boil down into sixty lines of code, that’s not very elegant. This is actually a pretty great way to practice not caring: not making a general solution, not using a more elegant tool that does the thing ‘correctly’, and not optimizing unless you need it.

One could definitely make a reversed map with proj4 and a pixelated map with a custom Mapnik. But there’s a difference between doing those things and what I end up doing: this can be fun.

Sweet, sweet libraries

Here’s a map of sorted countries that I made for my presentation at foss4g.

When you actually need to do stuff with shapes, you need to bring in the big guns: which in my case usually means Shapely , a swiss-army knife of geographic & geometrical functions that works with OGR. OGR’s the less savory part of the equation, being kind of a ‘wrapper for Python’ that feels like a wrapper and not like Python. But Shapely is wonderful!

Here’s the Python source for this one. As you can see, an unfortunate amount of time is devoted to Shapefile + OGR bookkeeping and boilerplate. Also: this isn’t knocking the OGR project in general - it’s actually a massively impressive wealth of code that can parse and emit practically everything, and does it remarkably fast.

My workflow is obviously a bit TileMill-centric: since most of this work is just a weird route to testing various bits of functionality and code. I’m looking to try out more prototyping with Polymaps, but for the time being, QGIS is the only middle-step that I have for testing that things look right.

Projections, darned projections

You may have noticed in the last one, countries have that awful skewed look remniscent of Greenland taking over every Mercator map. Basically, if you’re doing your final output in Mercator, it’s wise to do your datasource in Mercator as well.

In some cases, it’s kind of cool: I generated this maze map in EPSG:4326 (longitude, latitude)

And you can see it becoming more exaggerated in the north and south. Fine for this effect, but if it was done in EPSG:900913, aka ‘Spherical Mercator’, it would look a bit less odd. But, still about as odd as a neon green maze on a map.

Here’s the code that generates this maze, from an ASCII representation by this .js file taken from Rosetta Code. It’s a simple Python script using Shapely and OGR.