Quantcast
Channel: vis4.net » mercator
Viewing all articles
Browse latest Browse all 3

Look, Ma, No More Mercator Tiles

$
0
0

Using open source tools it is now super easy to make your own map tiles, and with a little extra work you can render them in whatever map projection you want. No more excuses to use Mercator! For example, here is a map we published today at The Upshot. It shows where prime-age women are working more or less then average, and includes data from county-level in the overview map down to every census tract once you zoom in. And all is nicely projected in Albers Equal-Area Conic, a projection widely adopted as standard for U.S. maps.



       

So how does this work?

After many years of blindly accepting the dominance of Web Mercator tile maps, I was quite surprised to learn how easy it is to use whatever projection you want. So how does this work? The answer is that it works because Mapnik, the core of many open source tile mapping frameworks, supports custom projections out of the box. It is just the tools built around Mapnik that are not supporting other projections.

Tilemill, for instance, is a super nice tool for styling map tiles in a CSS like language. But if you export your map as tiles using Tilemill, you are stuck with Web Mercator, even though internally it is Mapnik that renders all the tiles. Fortunately this doesn’t mean that you have to deal with Mapnik and it’s quirks directly to get custom projections.

Step 1: Export Mapnik XML and change projection

So the first step is to style your map just as you would with a Mercator map, enjoying the full feature-set of Tilemill. Once you’rd done with that you export your project as Mapnik XML. Think of this XML file as the entire description of your map. It contains all the references to the source layers and all the styles for the map features — in one single file. If you actually read the code of the file you will quickly realize how extremely lucky we are to have Tilemill and CartoCSS. And you might also notice that in the root element of the document, named <Map>, you find the the definition of the map projection as Proj.4 string in the srs attribute (for spatial reference system). And you can simply change it to whatever you want. In this case I replaced the Mercator projection with the Albers projection (copied from the Proj.4 definition linked here):

<Map srs="+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 +x_0=0 +y_0=0 +ellps=sphere +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +nadgrids=@null" background-color="#ffffff">

I copied the Proj.4 string from spatialreference.org, where you find many other standard projections as well. (Hint: If you plan on aligning the tile map to a D3.js projected vector overlay you should append +nadgrids=@null to the Proj.4 string)

To preview the projection I used TileStache. All it needs is a JSON configuration file that points to the exported Mapnik XML as input layer.

{
    "cache": {
        "name": "Disk", "path": "tiles"
    },
    "layers": {
        "map": {
            "provider": {"name": "mapnik", "mapfile": "mapnik.xml"},
            "projection": "spherical mercator"
        }
    }
}

Then you run tilestache-server.py with your config and open http://localhost:8080/map/preview.html to preview the freshly rendered projected tiles, just as you would in Tilemill.

Step 2: Figuring out which tiles to render

Now we come to the first tricky part of the process. We need to figure out which tiles we actually want to be rendered. You probably already know the zoom levels and the bounding box in WGS 84 lat/lon coordinates, but the latter won’t help us much since TileStache is designed for rectangular projections like Mercator. Fortunately TileStache also takes a text file with a list of Z/X/Y tile coordinates as input. To get this list of tiles I wrote a Python script (feel free to re-use if you want).

The script uses mercantile, a Python library for tile calculations, which returns the tile coordinates for points in WGS84 lat/lon, assuming that the tiles are projected in Web Mercator. To trick the library into giving me the correct tiles, I converted the points to the custom projection first and then projected them “back” from Web Mercator (even though the coordinates aren’t in Mercator). When mercantile gets my “fake” lat/lon coordinates it projects them to Mercator (reversing my inverse projection) and ends up with the Albers Equal Area coordinates.

def get_tile(lon, lat, z):
    pt = albers(lon, lat) # project to custom projection
    pt2 = mercator(pt[0], pt[1], inverse=True) # project "back" from Mercator
    return mercantile.tile(pt2[0],pt2[1], z)

Using this function I then compute the top-left and bottom-right tile for each zoom level and add every tile in between the two to my tile list.

The bounding box coordinates deserve a further note. Bounding boxes cannot be projected between non-rectangular coordinate systems. Here is an example showing a lat/lon bounding box that works fine in Mercator, projected to the Albers projection. Not only do we get too much empty space but we’re also missing significant parts of U.S. territory.

So instead we have to do the reverse approach and grab the Albers bounding box and then project it back to WGS 84 lat/lon (I used QGIS for this step). This bounding box I then used in the Python script to generate the tile urls.

Finally we run tilestache-seed.py to pre-generate all the tiles in our list (you probably want to host them as static files somewhere), which may or may not take quite a while to finish.

$ python make-tile-urls.py
$ tilestache-seed.py -c config.json --tile-list=tile-urls.txt --layer=map

Step 3: Translating coordinates in web map

Once we generated all our tiles we almost made it to the end. The tiles can be used just like Mercator tiles, so you are free to pick your favorite tilemap framework such as Leaflet.js, Polymaps, OpenLayers or whatever you prefer. For our women employment map I went with ModestMaps which I like for its simplicity and “hackability”.

However, all of these frameworks assume that your tiles are in Mercator projection, so the built-in conversion from WGS 84 lat/lon to Mercator tile coordinates won’t work for us — unless we do the same trick I showed above.

First, we are going to need the two projections Web Mercator and Albers, which you find in the Proj.4 JavaScript fork. Needless to say that you should use the exact same projection definition here.

    // Web Mercator
var mercator = proj4('+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 '
               + '+x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext '
               + '+over +no_defs'),
    // Albers Equal Area
    albers = proj4('+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 '
              + '+x_0=0 +y_0=0 +ellps=sphere +nadgrids=@null '
              + '+towgs84=0,0,0,0,0,0,0 +units=m +no_defs');

Using these projection classes we can convert coordinates from the ‘real’ WGS 84 lat/lon coordinates to the ‘wrong’ lat/lon coordinates on our map, by first projecting to Albers, and then inverse projecting “back” from Web Mercator.

function WGS84ToMap(pt) {
    pt = mercator.inverse(albers.forward([pt.lon, pt.lat]));
    return { lon: pt[0], lat: pt[1] };
}

The same works in the reverse direction, too. I used this to display the current map coordinates in the URL hash, simply by passing the map center (the ‘wrong’ coordinates I get from map.getCenter()) to mapToWGS84().

function mapToWGS84(pt) {
    pt = albers.inverse(mercator.forward([pt.lon, pt.lat]));
    return { lon: pt[0], lat: pt[1] };
}

And that’s basically all it needed to get the tile map library working with my custom projected tiles.

Of course there is a lot more I could write about (and I will in future posts), but for now, let’s wrap this up: Making tile maps using custom projections isn’t that hard, and the beautiful maps are definitely worth it. You can use Tilemill just as usual, export Mapnik XML, change the projection in the XML and then render the tiles using TileStache. You can use these tiles in any tile map framework, but you have to convert coordinates from and to your ‘wrong’ lat/lons.

If you have further questions or know a different approach for tile maps in custom projections, let us know in the comment section. Also, if you happen to work at Mapbox or another company that produces tile map tools with a built-in limitation to rectangular projections, I would be curious to hear why you’re not willing to go the extra mile for the sake of beautiful maps.

And in case you find this blog post helpful, please tweet it! :)

Flattr this!


Viewing all articles
Browse latest Browse all 3

Latest Images

Trending Articles



Latest Images