Taking Control: Building and Serving Your Own Offline Maps

Most guides on offline maps end at “download the region in Google Maps.” That is fine for tourists. It is not fine if you are managing a fleet of field devices, running a custom GIS application, or working with data you cannot hand to a third-party service.

This article is for the second group. It covers how to build your own offline map pipeline from raw data to a tile package that works on any device, without depending on Google, Mapbox, or any external service at runtime.


Why Build Your Own

Three scenarios make self-hosted offline maps worth the effort.

Proprietary data. Your organization has internal road networks, facility layouts, restricted site boundaries, or classified layers that cannot leave your infrastructure. Commercial offline map services require you to upload data to their servers. That is a non-starter for many industries.

Custom styling. Consumer offline map apps give you their style, sometimes with minor customization. If your use case requires specific cartographic conventions, branded colors, custom icons, or accessibility-optimized contrast, you need control over the rendering pipeline.

Scale and cost. Distributing offline maps to hundreds of field devices through a commercial SDK means paying per download or per device. Past a certain scale, self-hosted becomes significantly cheaper.


The Stack

A self-hosted offline map pipeline has four components:

  1. Source data — the geographic data you want to display
  2. Tile generation — converting source data into tiles
  3. Packaging — bundling tiles into a portable format
  4. Rendering — displaying the tiles on a device or in an app

Each component has open-source options that work together cleanly.


Source Data

OpenStreetMap is the default starting point for base map data. Planet-scale extracts are available from Geofabrik (by country and region) and BBBike (by city bounding box). These are updated weekly and cover road networks, buildings, land use, water bodies, and points of interest at a level of detail that rivals commercial providers.

For your own data layers, any format GDAL can read is usable: GeoJSON, Shapefile, GeoPackage, PostGIS tables, CSV with coordinates. These get merged into the tile generation pipeline alongside or instead of OSM data depending on your needs.

Elevation data for terrain and contour lines comes from the SRTM dataset (30m resolution globally) or Copernicus DEM (better resolution in Europe). Both are free and widely used.


Generating Tiles

Vector Tiles with Tilemaker

Tilemaker is a C++ tool that converts OSM data directly to vector tiles in MBTiles or directory format. It is fast, runs locally, and requires no database setup.

# Install (macOS)
brew install tilemaker

# Generate tiles from an OSM extract
tilemaker --input india-latest.osm.pbf \
          --output india.mbtiles \
          --config config.json \
          --process process.lua

The config file defines zoom levels, tile size, and output format. The Lua process script controls which OSM features are included and how they map to vector tile layers. Tilemaker ships with sensible defaults you can start from and customize.

For a typical country extract at zoom 0–14, tile generation takes minutes to a few hours depending on data density and your hardware. The output MBTiles file is self-contained and portable.

Raster Tiles with GDAL

If you need satellite imagery or custom raster layers in your offline package:

# Convert a GeoTIFF to a tile directory
gdal2tiles.py --zoom=0-14 --processes=4 satellite_image.tif output_tiles/

# Package the tile directory into MBTiles
mb-util output_tiles/ satellite.mbtiles

gdal2tiles.py supports TMS, XYZ, and WMTS tile schemes. Match the scheme to what your rendering client expects, XYZ is the most widely supported.

Custom Vector Layers with Tippecanoe

Tippecanoe, developed by Mapbox and now maintained as an open-source project, converts GeoJSON (and other formats) to vector tiles with fine control over feature simplification and zoom-level visibility.

# Convert a GeoJSON to MBTiles
tippecanoe -o custom_layer.mbtiles \
           --minimum-zoom=6 \
           --maximum-zoom=14 \
           --drop-densest-as-needed \
           input.geojson

The --drop-densest-as-needed flag automatically thins dense feature clusters at low zoom levels, keeping tile sizes manageable without manual configuration. Tippecanoe handles point, line, and polygon layers and supports attribute filtering to control what metadata travels with each feature.


Packaging: MBTiles

MBTiles is the standard portable format. It is a SQLite database with a defined schema. Each tile is a row. The entire map is a single file.

This matters for distribution. You copy one file to a device. No directory trees, no partial transfers. If the file is there, the map works.

Merging multiple MBTiles (base map plus custom layers) into a single package:

# tile-join merges multiple MBTiles sources
tile-join -o combined.mbtiles \
          basemap.mbtiles \
          custom_layer.mbtiles \
          satellite.mbtiles

tile-join is part of the Tippecanoe toolkit. It handles zoom-level conflicts by preferring the last-specified source at each zoom range, configurable with flags.

Inspect what you built before distributing:

# List tile counts and metadata
sqlite3 combined.mbtiles "SELECT zoom_level, count(*) FROM tiles GROUP BY zoom_level;"

Serving and Rendering

On a Server: TileServer GL

TileServer GL serves MBTiles over HTTP as a standard tile endpoint. Install it where your devices can reach it (local network, VPN, or public server depending on your security model):

npm install -g tileserver-gl
tileserver-gl combined.mbtiles

The server exposes standard XYZ tile URLs and a WMTS endpoint. Any mapping client that can consume standard tiles can point at it: Leaflet, Mapbox GL JS, QGIS, ArcGIS.

For fully offline scenarios with no server at all, read from MBTiles directly on the device.

On Mobile: Reading MBTiles Directly

Android. The Mapbox Maps SDK for Android supports offline tile packs, but for full control over source data, use MapLibre Native (the open-source Mapbox GL fork). Load your MBTiles file from local storage:

val sourceUri = "mbtiles:///sdcard/maps/combined.mbtiles"
val source = VectorSource("custom-source", sourceUri)
mapboxMap.style?.addSource(source)

iOS. MapLibre Native for iOS supports the same approach. The MBTiles file ships in the app bundle or downloads to the documents directory.

QGIS Mobile / QField. Accepts MBTiles directly as a raster or vector layer. Drop the file onto the device, add it as a layer in QField. No additional configuration.

OsmAnd. Supports .sqlitedb and .mbtiles files placed in the OsmAnd maps directory. Vector maps in OsmAnd’s own .obf format are generated with OsmAndMapCreator from OSM PBF data.


Keeping Maps Fresh

An offline map is a snapshot. The world changes. Your pipeline needs a refresh strategy.

For OSM-based maps, Geofabrik updates country extracts daily. A scheduled job that downloads the latest extract, regenerates tiles, and packages the output can run weekly or monthly depending on how quickly your area of interest changes.

#!/bin/bash
# Weekly refresh script
wget -N https://download.geofabrik.de/asia/india-latest.osm.pbf

tilemaker --input india-latest.osm.pbf \
          --output india_$(date +%Y%m%d).mbtiles \
          --config config.json \
          --process process.lua

# Push to distribution server or S3 bucket
rsync india_$(date +%Y%m%d).mbtiles user@mapserver:/var/maps/

For device fleets, push the updated MBTiles file when devices connect to the network. Differential updates (sending only changed tiles) are possible with osmupdate and careful pipeline design but add significant complexity. For most use cases, full regeneration on a weekly or monthly cycle is simpler and reliable enough.


Practical Considerations

Zoom level range. Tiles at zoom 0–10 cover the whole world at low detail. Zoom 14–16 is street level. Zoom 17–18 is building-level detail. Each additional zoom level quadruples the number of tiles. Choose the maximum zoom level based on your actual use case. Generating zoom 18 globally is a multi-terabyte project. Generating zoom 16 for a single state is manageable in gigabytes.

Coordinate systems. Web map tiles use Web Mercator (EPSG:3857) by convention. Your source data may be in a different CRS. Reproject with GDAL or ogr2ogr before tile generation, not during. Reprojecting on the fly inside the tile generator adds complexity and potential for subtle errors.

Tile size. The vector tile specification recommends tiles under 500KB. Tiles larger than this cause rendering lag on mobile devices. If Tilemaker or Tippecanoe is producing large tiles at certain zoom levels, tighten the feature inclusion criteria for those levels.

Licensing. OSM data is licensed under the Open Database License (ODbL). Derived products must carry attribution and, if distributed, share-alike. If your use case prohibits open licensing, you need a commercial data source.


Summary

Building your own offline map pipeline gives you control over data, style, freshness, and distribution at a cost structure that scales with your infrastructure rather than your usage volume. The core tools — Tilemaker, Tippecanoe, GDAL, TileServer GL, and MapLibre — are all open source, well-maintained, and production-tested. The MBTiles format makes distribution simple. The rendering side is handled by standard clients your team likely already uses.

The investment is in understanding the pipeline and automating the refresh cycle. Once that is in place, you have a mapping infrastructure that runs entirely under your control, works without any external dependency, and handles both fieldwork and application development use cases.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *