Crunchy Spatial: Tile Serving

Paul Ramsey

3 min read

Beautiful, responsive maps are best built using vector tiles, and PostgreSQL with PostGIS can produce vector tiles on-the-fly.

However, to use vector tiles in a beautiful, responsive map, you need to be able to access those tiles over the HTTP web protocol, and you need to be able to request them using a standard XYZ tiled map URL.

Crunchy Spatial_Spatial Diagram-1

It's possible to write your own HTTP wrapper for the PostGIS vector tile generator, but you don't need to!

pg_tileserv is a lightweight vector tile server specifically written to publish tiles from a PostgreSQL/PostGIS database.

pg_tileserv has the following features:

  • Written in Go to allow for simple deployment of binaries with no complex dependency chains or library versioning issues.
  • Ready-to-run defaults so that basic deployment just requires setting a database configuration string and running the program.
  • Simple web user interface to explore the published tile services, and view the services as maps.
  • On-the-fly attribute filtering to strip out columns you don't want to retrieve from the server, for smaller, faster tiles.
  • Function-based tile generation, so you can generate tiles from any function that takes in XYZ tile coordinates and outputs MVT tiles.

public parcels

Want to see pg_tileserv in action? Here's a five-step demo! (Most of the steps just involve getting some spatial data in a database: if you already have a database, just skip down to step 3 and input your own database connection information).

  1. Make a database, and enable PostGIS.
createdb postgisftw
psql -d postgisftw -c 'create extension postgis'
  1. Download some spatial data, and load it into PostGIS.
curl -L -o
shp2pgsql -s 4326 -D -I ne_50m_admin_0_countries | psql -d postgisftw
  1. Download and unzip the pg_tileserv binary for your platform

  2. Set the DATABASE_URL environment variable to point to your database, and start the service.

export DATABASE_URL=postgresql://postgres@localhost:5432/postgisftw
./pg_tileserv --debug
  1. Point your browser to the service web interface URL.

  2. Explore the data!

The service includes both a human-viewable interface, and a JSON-based API for programmatic service discovery. The JSON API starting point is:

* http://localhost:7800/index.json

You can see examples of maps that configure using the JSON API by viewing the source of the human-viewable interface.

pg_tileservUsing the data loaded in this example, building a web map that visualizes the tiles is as simple as pointing to the tile source URL. A web map can be as small as these examples (Leaflet, Openlayers, Mapbox GL JS):

<html lang="en">
		<meta charset="utf-8" />
		<title>Vector Tiles in Leaflet</title>

		<!-- CSS for Leaflet map -->

		<!-- JS for Leaflet map -->

		<!-- Leaflet plugin for vector tiles support -->

		<!-- Set up a full-screen map -->
			#map {
				height: 100%;
				width: 100%;
			body {
				padding: 0;
				margin: 0;
			#map {
				z-index: 1;

		<!-- Put the map in this element -->
		<div id="map"></div>

			// Leaflet map object
			var map ='map').setView([0, 0], 2)

			// Add a base map layer to the map
			var baseUrl = '{z}/{x}/{y}.png'
			var baseLayer = L.tileLayer(baseUrl).addTo(map)

			// Add the tile layer to the map
			var vectorServer = 'http://localhost:7800/'
			var vectorLayerId = 'public.ne_50m_admin_0_countries'
			var vectorUrl = vectorServer + vectorLayerId + '/{z}/{x}/{y}.pbf'
			var vectorTileStyling = {}
			// Rendering options
			vectorTileStyling[vectorLayerId] = {
				fill: true,
				fillColor: 'green',
				fillOpacity: 0.1,
				color: 'green',
				opacity: 0.7,
				weight: 2,
			var vectorTileOptions = {
				rendererFactory: L.canvas.tile,
				vectorTileLayerStyles: vectorTileStyling,
			var vectorLayer = L.vectorGrid
				.protobuf(vectorUrl, vectorTileOptions)

leaflet tile map

Paul Ramsey

Written by

Paul Ramsey

March 5, 2020 More by this author