Published on

Adding dynamic geo data to existing MBTiles using Martin

Introduction

So, for the project I'm working on I wanted to host my own map tiles but also wanted to dynamically update only one or two layers of the tiles I will be serving. I didn't want to recreate map tiles every time I had to add a new route so I decided to go with Martin which allows me to have a base map data as a .mbtile but also attach dynamic data that sits in some database. The database can be easily updated with new routes/geo data for specific layers. This way I don't have to recreate and reupload a large .mbtile file every time I want to add a new route.

I didn't find a lot of information or a quick start guide how to achieve it so I'm sharing this info for others.

What will we do?

Let's create a map for Netherlands with additional map layer that will contain some bike routes added by us.

The map will be served via map tiles using Martin. We will be returning MVT(Mapbox Vector Tile) files to client based on requested coordinates and zoom level.

Base map tiles

First let's grab some already created tiles in a .mbtiles format. Here's a great website where you can download more or less up to date generated tiles: https://osm.dbtc.link/mbtiles/

Let's go with the Netherlands map as it's the smallest one up there, that is: {DATE}-netherlands.mbtiles As of time of writing this the most up to date file is: 2024-07-07-netherlands.mbtiles and I will be using it further in the article.

Then move the .mbtiles file to a desired folder: {YOUR_PATH}/martin

Dynamic database data

Next let's set up a simple PostGIS databse to hold our data about bike routes. We will create a table called "bike-routes" where we can add new bike routes or delete existing one and Martin will connect that data with our base map tiles.

Let's use a Docker Compose for a quick setup of PostGIS database. Create a new folder: {YOUR_PATH}/postgis and create a docker-compose.yaml file there.

{YOUR_PATH}/postgis/docker-compose.yaml:

services:
  postgis:
    image: postgis/postgis
    restart: always
    environment:
      - POSTGRES_DB=gis
      - POSTGRES_USER=gis
      - POSTGRES_PASSWORD=password
    ports:
      - 5432:5432
    volumes:
      - ./data/postgis:/var/lib/postgresql/data

This standard docker compose config will host a database on a port 5432 and also store the data from database locally inside current directory, in data folder.

Inside the martin directory in your terminal use this commend to run the docker container:

docker-compose up

Now we have a database where we can add our dynamic geo data.

Adding geojson to database

We will be adding "Limes Route" bike route geojson to our database. You can grab it here: [link]

Move the geojson file to {YOUR_PATH}/martin folder and run the command:

ogr2ogr -f "PostgreSQL" PG:"dbname=gis user=gis password=password host=localhost port=5432" "path.geojson" -nln paths -append

Note that you will need to install GDAL to use the ogr2ogr command. You can install GDAL using these commands (on mac):

`sudo port install gdal`

Installing Martin and creating config file

Download Martin from the release page: https://github.com/maplibre/martin/releases

Inside the martin folder create file config.yaml with minimal config.

{YOUR_PATH}/martin/config.yaml:

listen_addresses: '0.0.0.0:3000'
worker_processes: 4
cache_size_mb: 512

mbtiles:
    sources:
        basemap: './2024-07-07-netherlands.mbtiles'
        
postgres:
    connection_string: 'postgresql://gis:password@localhost:5432/gis'

    auto_publish:
        from_schemas:
            - public
        tables:
            source_id_format: '{table}'
            clip_geom: true
            buffer: 64
            extent: 4096

Now we are all set up and can start our Martin server. Move to the {YOUR_PATH}/martin directory in your terminal and run:

martin --config config.yaml

Martin catalog

We can also verify that the Martin is serving specified mbtiles file and data from PostGIS databse by navigating to: http://0.0.0.0:3000/catalog

You will see the following:

{
    "tiles":{
       "basemap":{
          "content_type":"application/x-protobuf",
          "content_encoding":"gzip",
          "name":"OpenMapTiles",
          "description":"A tileset showcasing all layers in OpenMapTiles. https://openmaptiles.org",
          "attribution":"<a href=\"https://www.openmaptiles.org/\" target=\"_blank\">&copy; OpenMapTiles</a> <a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\">&copy; OpenStreetMap contributors</a>"
       },
       "paths":{
          "content_type":"application/x-protobuf",
          "description":"public.paths.wkb_geometry"
       }
    },
    "sprites":{},
    "fonts":{}
 }

Now you can access your vector files by specifying host url and the tiles source in this format: {HOST_URL}/{SOURCE_ID}/{Z}/{X}/{Y} where:

  • SOURCE_ID - is the id of the source of the tiles
  • Z - zoom level
  • X,Y - coordinates

For example: http://localhost:3000/basemap/8/131/85 will return a tile from basemap tiles source with zoom level equal 8 and coordinates x=131, y=85.