Tutorial: Prerendering OSM map tiles in z/x/y format

We have shown in our last tutorial how we can set up a map server with OSM, a postgis table and mapnik. But sometimes, it’s not an option to install all of that onto a server. The solution is simple: We can prerender all tiles and serve them through a simple webserver like Apache. This tutorial will show how to do it.

To prerender map tiles in the usual OSM z/x/y format, we first need to import our data into a postgis table. It’s also necessary to set up openstreetmap-carto and mapnik. We’ve shown how to do this in our last tutorial.

Then, we need to clone the mapnik-stylesheets repository:

# git clone https://github.com/openstreetmap/mapnik-stylesheets.git

This repository contains the scripts generate_tiles.py and generate_tiles_multiprocess.py, which we need to *drumroll* generate our tiles. Both scripts operate the same, and we’ll show how you need to edit them later.

As of today, this script is not compatible with Python 3, so we need to use Python 2. To make the script work, we need to have mapnik installed on Python 2.

The easiest way to check for mapnik is simply importing it into python: python -c "import mapnik". If that shows no errors, mapnik is already installed on your system. If you need to reinstall mapnik, do it via sudo apt install libmapnik-dev mapnik-utils python-mapnik. Don’t try pip install mapnik as that will install a very outdated version that will probably not work with the newest version of openstreetmap-carto. If you have followed our Mapserver Tutorial part 1 you will already have these packages.

To start rendering our tiles, we need to edit one of the tile generation scripts. Since both of them operate the same, we’ll just use generate_tiles.py, but the same applies to generate_tiles_multiprocess.py as well.

All the parameters we need to edit are down in the main function of the script. When you download the script, it will probably look something like this:

    home = os.environ['HOME']
    try:
        mapfile = os.environ['MAPNIK_MAP_FILE']
    except KeyError:
        mapfile = home + "/svn.openstreetmap.org/applications/rendering/mapnik/osm-local.xml"
    try:
        tile_dir = os.environ['MAPNIK_TILE_DIR']
    except KeyError:
        tile_dir = home + "/osm/tiles/"

    if not tile_dir.endswith('/'):
        tile_dir = tile_dir + '/'

To use the script, we need to set the mapfile variable to the style.xml we generated with carto. We also need an output directory for our files. This directory needs to be created manually. For my use case, this is what I ended up with:

if __name__ == "__main__":
    home = os.environ['HOME']
    try:
        mapfile = os.environ['MAPNIK_MAP_FILE']
    except KeyError:
        mapfile = home + "/openstreetmap-carto/style.xml"
    try:
        tile_dir = os.environ['MAPNIK_TILE_DIR']
    except KeyError:
        tile_dir = home + "/osm/output/"

    if not tile_dir.endswith('/'):
        tile_dir = tile_dir + '/'

If you’re missing style.xml, you can create it by cloning https://github.com/gravitystorm/openstreetmap-carto.git and using carto on the project mml file. More details can be found in part 1 of our map server tutorial.

The next section in generate_tiles.py defines which parts of the world will be rendered.


    # Change the following for different bounding boxes and zoom levels
    #
    # Start with an overview
    # World
    bbox = (-180.0,-90.0, 180.0,90.0)

    render_tiles(bbox, mapfile, tile_dir, 0, 5, "World")

    minZoom = 10
    maxZoom = 16
    bbox = (-2, 50.0,1.0,52.0)
    render_tiles(bbox, mapfile, tile_dir, minZoom, maxZoom)

    # Muenchen
    bbox = (11.4,48.07, 11.7,48.22)
    render_tiles(bbox, mapfile, tile_dir, 1, 12 , "Muenchen")

Usually, you’ll only want to render parts of your world, which is why the overview is useful so you don’t end up with blank tiles. Then, you’re free to define as many bounding boxes as you’d like for the areas you want to generate tiles for.

In this example, I’m rendering Switzerland. Its bounding box is [5.9559, 45.818, 10.4921, 47.8084].

The fourth and fifth parameters in render_tiles(bbox, mapfile, tile_dir, 0, 5, "World") are the minimum and maximum zoom levels. Adjust these depending on your requirements.

Then, we’re ready to execute our script via python generate_tiles.py. At first, the script will most likely fail with a lot of errors due to not installed fonts. OpenStreetMap recommends the following fonts to be installed:

# sudo apt-get install fonts-noto-cjk fonts-noto-hinted fonts-noto-unhinted fonts-hanazono ttf-unifont

In my case, I still had to remove Noto Emoji Regular and unifont Medium from my style.xml.

Afterwards, we’re ready to go to fire up generate-tiles.py. Now, Mapnik will prerender all of these tiles in XYZ format, which we can then serve through a classic webserver without any other nuts and bolts attached. Pretty awesome!