Odd Maps and How to Make them
- I ended official support for TileMill 2016. The Mapbox way to make this kind of map is now Studio. TileStream Hosting is now just called Mapbox.
- I no longer work at Development Seed - my current job status is listed on /about.
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
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.