Polymaps
When SimpleGeo announced Polymaps back in August the geo community was quick to notice the potential of this modern web mapping library. We could now visualize our vector-based datasets with complex geometry without the need for generating tile sets with staggering numbers of images, only to have to re-generate these tiles as the geometry behind them changes. But this announcement also prompted a few folks to ask the question, “How do I generate these GeoJSON tiles?”
There are only a few solutions that I know of. TileStache is a python-based web application that can stream and cache (I think) GeoJSON tiles among other formats. Arc2Earth supports streaming GeoJSON tiles from any datasource in an Arc2Earth or Arc2Cloud instance by using the “tiles” endpoint (http://my-a2e-instance.appspot.com/a2e/data/datasources/myDatasource/tiles/{Z}/{X}/{Y}?f=gjson).
A few projects I’ve been working on recently have been using MapFish, which is an open source framework for creating web mapping applications. MapFish is built on the Pylons web framework and integrates tightly with a number of other open source projects (SQLAlchemy, GeoAlchemy, Shapely). It’s got two core components:
- a RIA-oriented JavaScript toolset for the client side (of which I have admittedly zero experience)
- a server framework for creating web services to Create, Read, Update and Delete features (CRUD) via HTTP POST, GET, PUT and DELETEs
While pondering home-grown solutions to stream GeoJSON tiles to the client, I realized that MapFish had all of the components needed to perform the conversion of a typical map tile request (http://my-geo-app.com/parcels/14/4516/6477) into a GeoJSON FeatureCollection, which is what Polymaps needs to display features.
The Solution
What we need to do is take a layer request that contains a zoom level, an X and a Y coordinate and then somehow create a bounding box in our layer’s coordinate system (SRID 4326). The first step is to properly route the request to its appropriate controller and action. We can easily do this by editing the routing.py in our MapFish config directory.
A vector tile request might look like: http://my-mapfish-app.com/my_layer/geojson/14/4516/6477
Our tile requests should now be forwarded to the “geojson” controller. The controller simply takes the request and related variables (Z, X and Y in our case) and calls geojson method of the controller class.
Protocol’s geojson method is really where all of the magic happens. Here we pass our x, y and z variables and, with the help of Shapely, create a Polygon (line 46). This is the bounding box of the requested tile. Now we convert the Shapely polygon into the well known binary geometry we need for the geometry filter (lines 48 - 50). Lastly we combine the attribute and geometry filter, add the “Access-Control-Allow-Origin: *” header required for Polymaps and return to MapFish’s read method that create GeoJSON FeatureCollections from spatial and attribute queries (lines 52 - 58). Most of the code to convert tiled map requests to Lat/Lng bounding boxes comes from maptiler.org.
The Fine Print
Since I’m creating a bounding box with Lat/Lng values, your MapFish layer needs to support SRID 4326. There’s probably an easy work around for this that someone can figure out. But for me this worked as my data was already in SRID 4326.
Also, we’re not caching any of these results so that future requests to the same vector tile don’t require as much overhead. If we were to cache the tiles we’d lose the ability to add query parameters to further filter our requests.
Performance Notes
Streaming GeoJSON tiles to the client can produce some pretty nice visualizations, but there are a few things to think about. Lots of bytes are traveling from the server to your web app and you’ll want to shave off every 1 or 0 you can.
By default MapFish returns not only the GeoJSON geometry for a feature, but also a bounding box. For me it made sense to alter the source code to omit this extra (unnecessary in my case) geometry.
You’d also do yourself a favor to eliminate as many feature properties as you can. You might not think removing a handful of properties would make much of a difference, but when you’re returning scores of features within each map tile, these start to add up quickly.
The real beauty behind streaming these vector tiles is that you can simplify the geometry at the lower zoom levels but return to the full detailed geometry at the higher zoom levels. This makes sure we remove as many vertices we can when dealing with large numbers of features. I’ve left out the code for the zoom-dependent geometry simplification in this blog post to keep things as simple as possible, but feel free to contact me to get more information.
OK, Show Me
Check out the Tiled GeoJSON demo* to see things in action. Remember that since we can still pass url parameters to the MapFish back-end, you can adjust the range for a few of the layer attributes.

* Sorry, no longer available. I’m tired of paying my AWS bill.