Self-hosting Mapbox vector tiles

As pointed out by @Greg, instead of TileStream (my first attempt) you should use Tilelive to host your own vector tiles.

Tilelive isn't a server itself but a backend framework that deals with tiles in different formats from different sources. But it's based on Node.js so you can turn it into a server in a pretty straight-forward way. To read tiles from a .mbtiles source as exported by Mapbox Studio, you need the node-mbtiles tilelive module.

Side note: Current Mapbox Studio has a bug under Windows and OS X that prevents an exported .mbtiles file to show up at your chosen destination. Workaround: Just grab the latest export-xxxxxxxx.mbtiles file in ~/.mapbox-studio/cache.

I found two server implementations (ten20 tile server by alexbirkett and TileServer by hanchao) who both use Express.js as a web app server.

Here is my minimalistic approach which is loosely based on these implementations:

  1. Install Node.js
  2. Grab the node packages with npm install tilelive mbtiles express
  3. Implement the server in the file server.js:

    var express = require('express');
    var http = require('http');
    var app = express();
    var tilelive = require('tilelive');
    require('mbtiles').registerProtocols(tilelive);
    
    //Depending on the OS the path might need to be 'mbtiles:///' on OS X and linux
    tilelive.load('mbtiles://path/to/osm_roads.mbtiles', function(err, source) {
    
        if (err) {
            throw err;
        }
        app.set('port', 7777);
    
        app.use(function(req, res, next) {
            res.header("Access-Control-Allow-Origin", "*");
            res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            next();
        });
    
        app.get(/^\/v2\/tiles\/(\d+)\/(\d+)\/(\d+).pbf$/, function(req, res){
    
            var z = req.params[0];
            var x = req.params[1];
            var y = req.params[2];
    
            console.log('get tile %d, %d, %d', z, x, y);
    
            source.getTile(z, x, y, function(err, tile, headers) {
                if (err) {
                    res.status(404)
                    res.send(err.message);
                    console.log(err.message);
                } else {
                  res.set(headers);
                  res.send(tile);
                }
            });
        });
    
        http.createServer(app).listen(app.get('port'), function() {
            console.log('Express server listening on port ' + app.get('port'));
        });
    });
    

    Note: The Access-Control-Allow-... headers enable cross-origin resource sharing (CORS) so webpages served from a different server may access the tiles.

  4. Run it with node server.js

  5. Set up the webpage using Mapbox GL JS in minimal.html:

    <!DOCTYPE html >
    <html>
      <head>
        <meta charset='UTF-8'/>
        <title>Mapbox GL JS rendering my own tiles</title>
        <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.4.0/mapbox-gl.css' rel='stylesheet' />
        <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.4.0/mapbox-gl.js'></script>
        <style>
          body { margin:0; padding:0 }
          #map { position:absolute; top:0; bottom:50px; width:100%; }
        </style>
      </head>
      <body>
        <div id='map'>
        </div>
        <script>
          var map = new mapboxgl.Map({
            container: 'map',
            center: [46.8, 8.5],
            zoom: 7,
            style: 'minimal.json'
          });
        </script>
      </body>
    </html>
    
  6. Indicate the location of the tile source and style the layers with the following minimal.json:

    {
      "version": 6,
      "constants": {
        "@background": "#808080",
        "@road": "#000000"
      },
      "sources": {
        "osm_roads": {
          "type": "vector",
          "tiles": [
            "http://localhost:7777/v2/tiles/{z}/{x}/{y}.pbf"
          ],
          "minzoom": 0,
          "maxzoom": 12
        }
      },
      "layers": [{
        "id": "background",
        "type": "background",
        "paint": {
          "background-color": "@background"
        }
      }, {
        "id": "roads",
        "type": "line",
        "source": "osm_roads",
        "source-layer": "roads",
        "paint": {
          "line-color": "@road"
        }
      }]
    }
    
  7. Serve the webpage and rejoice.


The hosting of the vector tiles on your own is relatively straightforward. The MBTiles contains .pbf files which must be exposed to the web. That's it.

Probably easiest is to use a simple open-source server such as TileServer-PHP and put the MBTiles file to the same folder as the project files. The TileServer do all the hosting config for you (CORS, TileJSON, correct gzip headers, etc). Installation means just unpacking on a PHP enabled webserver.

If you want to start the TileServer-PHP on your laptop you can with Docker. The ready to use container is on DockerHub. Under Mac OS X and Windows it runs in a couple of minutes with the Kitematic graphical user interface: https://kitematic.com/. In Kitematic just search for "tileserver-php" and start the ready to use container/virtual machine with the project inside. Then click on "Volumes" and drop into the folder your MBTiles file. You get a running hosting for your vector tiles!

Such vector tiles could be opened in the MapBox Studio as a source, or displayed with MapBox GL JS WebGL viewer.

Technically it is even possible to host the vector tiles as a plain folder on any web server or a cloud storage, or even GitHub, if you unpack the individual .pbf out of the MBtiles container with a utility like mbutil, set the CORS, TileJSON and gzip correctly. Bellow is a GitHub project demonstrating such approach as well.

Try this viewer: MapBox GL JS viewer

and see the related repos:

  • https://github.com/klokantech/tileserver-php
  • https://github.com/klokantech/vector-tiles-sample
  • https://github.com/klokantech/mapbox-gl-js-offline-example

Not to toot my own horn, but https://github.com/spatialdev/PGRestAPI is a project I've been working on that hosts .mbtiles vector tile exports from Mapbox Studio.

Still needs lots of documentation, but basically, drop your .mbtiles files into /data/pbf_mbtiles and restart the node app. It will read thru that folder and offer endpoints for your vector tiles.

It will also look thru /data/shapefiles, and create dynamic Mapbox Vector Tiles on the fly based on your .shp. You can also point at a PostGIS instance and get dynamic vector tiles.

We use them in conjunction with https://github.com/SpatialServer/Leaflet.MapboxVectorTile, a Leaflet/Mapbox Vector Tile library we've also been working on.