PostGIS to Protobuf (with vector tiles)
2015-03-17
I have seen a lot of talk about protobol buffers and vector tiles recently, which promises improved performance loading and rendering layers than just plain geojson. I've seen a lot of demos of the tech, but had trouble putting it all together. This is my first attempt at creating a server that will query PostGIS, serve vector tiles encoded into protobuf, and render them on a Leaflet map.
Create a layer in Leaflet, using the excellent Leaflet.MapboxVectorTile plugin.
var vectorTileLayer = new L.TileLayer.MVTSource({
url: '/vector-tiles/layername/{z}/{x}/{y}.pbf',
clickableLayers: ['layername'],
getIDForLayerFeature: function(feature) {
return feature._id
}
})
map.addLayer(vectorTileLayer)
Create a route to match the above URL. Create a node server with Express, and add a route similar to a standard tile layer:
router.get('/vector-tiles/:layername/:z/:x/:y.pbf', function(req, res) {
})
Get the bounding box of our data, using the sphericalmercator library
var SphericalMercator = require('sphericalmercator')
var mercator = new SphericalMercator({
size: 256 //tile size
})
var bbox = mercator.bbox(
+req.params.x,
+req.params.y,
+req.params.z,
false,
'4326'
)
Use this bounding box to query PostGIS for the data we need for this tile. Convert the resulting rows into a single geojson feature collection.
select st_asgeojson(geom) as feature from table where st_intersects(geom, bbox)
Now that we have the geojson for the tile, we can create a vector tile using node-mapnik
var mapnik = require('mapnik')
var vtile = new mapnik.VectorTile(+req.params.z, +req.params.x, +req.params.y)
vtile.addGeoJSON(JSON.stringify(geojson), 'layername')
Finally, use vtile.getData() to get the protobuf-encoded buffer from the vector tile, pass it through zlib.deflate to compress, set the correct headers, and serve it.
res.setHeader('Content-Encoding', 'deflate')
res.setHeader('Content-Type', 'application/x-protobuf')
zlib.deflate(vtile.getData(), function(err, pbf) {
res.send(pbf)
})
Your vectorTileLayer in Leaflet should now be working.
So far, this seems to load complex layers much faster than querying and returning the entire geojson for a bounding box, and has the advantage of sending actual vector data to the client, unlike a traditional tile layer.
Discuss with me on twitter.