Tuesday, March 7, 2017

Playing with Mapbox Vector Tiles (Part 2 - Generating custom tiles)

Part 1. End-to-End experiment
Part 2. Generating custom tiles

On my previous post I've played around with various tools on the Mapbox vector tiles ecosystem. On this post I'm taking it further: generating my own vector tiles.

There are various options to generate vector tiles:
  • using a server that generates the vector tiles dynamically based on other data (such as PostGIS)
  • programatically generating the protobuf (rolling your own code or using existing libs)
  • using a tool that receives various inputs and outputs the corresponding vector tiles, which can then be used as shown on my previous post
Most options for these approaches are properly identified at this page: https://github.com/mapbox/awesome-vector-tiles

On this post I'm going to focus on this third option, particularly using a tool from Mapbox called Tippecanoe. I'm not an expert on any of these alternatives (I'm learning as I'm writing this post) but Tippecanoe seems incredibly robust, including a great deal of customisation options when generating the tiles.

So, first things first, what exactly is Tippecanoe?

It's simply a command-line tool that receives one or more GeoJson files as input and generates the corresponding vector tiles.

The readme is great so I'm not going to duplicate everything on this post. Basically installing is as simple as:
brew install tippecanoe

And running as
tippecanoe -o file.mbtiles [file.json ...]

Let me create a very basic map to see how this works.
  • To convert a Shapefile to Geojson the easiest way is probably to use ogr2ogr from GDAL with the following command:
ogr2ogr -f GeoJSON -t_srs crs:84 world.geojson ne_10m_admin_0_countries.shp
(Alternatively, for a UI driven experience, I would suggest opening it in QGis and simply saving the map as GeoJSON)
  • Now running tippecanoe on it:
tippecanoe -o world.mbtiles -z6 world.geojson

The "z" param is used to specify the highest zoom level to which tiles are generated. The default of 14 would take a looong time to finish. For the sake of this demo I'm going to keep things small and fast. For reference each zoom level requires 4^(zoom level) tiles.

After the tiles are generated we end-up with a .mbtiles file. This is basically a SQLite database including all the generated tiles inside, making it quite convenient to store, transfer and use.

I can try these vector tiles immediately but, without a style, nothing would be displayed. Fortunately tileserver-gl also provides a "data" mode where it shows the various layers of a mbtiles set.

So, simply running it against my "world.mbtiles" output from the previous step:
tileserver-gl-light world.mbtiles

Opening http://localhost:8080/data/world will show our map with a single layer called "worldgeojson"

I'm ready to create my style:

Using an editor is obviously ideal (I'll get to that later) but we can easily create a really dumb style "by hand":

style.json
{
   "version": 8,
   "sources": {
      "myworldmap": {
         "type": "vector",
         "url": "mbtiles://{v3}"
      }
   },
   "layers": [
      {
         "id": "background",
         "type": "background",
         "paint": {
            "background-color": "rgb(200, 200, 255)"
         }
      },
      {
         "id": "countries",
         "type": "fill",
         "source": "myworldmap",
         "source-layer": "worldgeojson",
         "paint": {
            "fill-color": "rgb(200,100,50)"
         }
      }
   ]
}

This is basically:
  • Creating a source for vector tiles (as in defining where to load the tiles from)
  • Creating a style layer to paint the background with a blue color
  • Creating a style layer that points to the "worldgeojson" layer from the vector data and filling it with a flat color
To use it with tileserver-gl I had to create a simplified configuration file:

config.json
{
  "options": {
    "paths": {
      "root": ".",
      "styles": ".",
      "mbtiles": "."
    }
  },
  "styles": {
    "world": {
      "style": "style.json"
    }
  },
  "data": {
    "v3": {
      "mbtiles": "world.mbtiles"
    }
  }
}

Running tileserver-gl as:
tileserver-gl-light -c config.json world.mbtiles 

Opening a page at: http://localhost:8080/styles/world/#0/0/0 will show our custom map with our custom style:

Creating the styles manually is not very practical. Fortunately there are various tools that might help with that:
It provides a rich UI where one can define colors, filters, fonts, etc in a very user friendly fashion.


It's particularly useful for maps created and hosted in Mapbox. Regardless, you can still edit the styles online and then download them to use with your local mbtiles, although requiring a little bit of editing on the output to point to the relevant sources.

This is just a page with the map on one side and the Json style on the other. Simple and effective, although doesn't provide many bells and whistles.
This is actually an open-source editor that provides some of the functionality of Mapbox Studio without any restrictions.

Using it is very simple. You can either go directly to http://maputnik.com/editor/ or clone the git repo from https://github.com/maputnik/editor and run it as:

npm install
npm start

By default the editor runs on port 8888. The first thing to do is to add a source for our generated tiles. If you still have tileserver-gl running from one of the previous steps you'll have a tilejson file automatically prepared at: http://localhost:8080/data/v3.json.

Go to "Source" on the top and create a new source as:


  • Source ID: mysource
  • Source Type: Vector (TileJSON URL)
  • TileJSON URL: http://localhost:8080/data/v3.json
Afterwards we're ready to create a new layer that references data from this particular source. Press Add Layer on the left and add the layer data:



Also, create another layer for the background:


Now just choose a flat color for these layers. You'll end-up with something like this:

That's it for now. On my third and final post I'm going to build up on these techniques and do something more advanced/interesting.




No comments:

Post a Comment