Skip to content

python-geospatial/tile-server-lite

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tile-server-lite

From a local vector file to a live slippy map in one command — on-the-fly Mapbox Vector Tiles with a built-in MapLibre preview.

CI Python License: MIT

tile-server-lite is a minimal FastAPI server that reads any geopandas-readable vector file — GeoPackage, GeoParquet, GeoJSON, Shapefile — and serves it as XYZ Mapbox Vector Tiles (MVT), cut on the fly. Point it at a file, open your browser, and you get a working MapLibre GL JS map fit to your data's bounds.

Maintained by python-geospatial.com, a knowledge base for the modern Python geospatial stack.

What & why

Static vector tile pipelines (Tippecanoe, PMTiles) are the right call for large, published datasets. But while you are exploring — checking a fresh export, sharing a quick preview, iterating on a layer — pre-baking tiles is friction. tile-server-lite cuts tiles per request straight from your file, so there is no build step between "I have a file" and "I have a map".

It complements, rather than replaces, the static PMTiles workflow covered on the site: use this server for local development and previews, then bake to PMTiles for production hosting (see How it works).

Install

Not published to PyPI — install straight from the repository:

pip install "git+https://github.com/python-geospatial/tile-server-lite.git"

Or clone and install in editable mode:

git clone https://github.com/python-geospatial/tile-server-lite.git
cd tile-server-lite
pip install -e ".[dev]"

Usage

Serve a single GeoPackage:

tile-server-lite serve parcels.gpkg

Serve several files at once (each layer is named after its file stem), on a chosen host/port and zoom window:

tile-server-lite serve parcels.gpkg sensors.geoparquet \
    --host 0.0.0.0 --port 8080 --min-zoom 6 --max-zoom 18

Example startup output:

                      tile-server-lite
┏━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Layer   ┃ Geometry ┃ Features ┃ TileJSON                                 ┃
┡━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ parcels │ Polygon  │    1 240 │ http://127.0.0.1:8080/tilejson/parcels…  │
│ sensors │ Point    │      317 │ http://127.0.0.1:8080/tilejson/sensors…  │
└─────────┴──────────┴──────────┴──────────────────────────────────────────┘
Preview map: http://127.0.0.1:8080/  · Served by python-geospatial.com

Then:

  • open http://127.0.0.1:8080/ for the MapLibre preview,
  • fetch a tile at GET /tiles/parcels/16/35208/21489.mvt,
  • fetch the TileJSON at GET /tilejson/parcels.json.

Example TileJSON response (abridged):

{
  "tilejson": "3.0.0",
  "name": "parcels",
  "scheme": "xyz",
  "tiles": ["http://127.0.0.1:8080/tiles/parcels/{z}/{x}/{y}.mvt"],
  "minzoom": 6,
  "maxzoom": 18,
  "bounds": [13.4028, 52.5185, 13.4098, 52.5232],
  "vector_layers": [
    {"id": "parcels", "geometry_type": "Polygon",
     "fields": {"parcel_id": "Integer", "zoning": "String"}}
  ]
}

Deploy with Docker

A single-file Dockerfile is included. Build the image, then mount a directory of vector files at /data:

docker build -t tile-server-lite .

docker run --rm \
    -v $(pwd)/data:/data \
    -p 8080:8080 \
    tile-server-lite serve /data/parcels.gpkg --host 0.0.0.0 --port 8080

The map is then live at http://localhost:8080/. Binding to 0.0.0.0 inside the container lets the published port reach the server; keep it on 127.0.0.1 when running directly on your machine.

Features

  • Any geopandas-readable input — GeoPackage, GeoParquet, GeoJSON, Shapefile, and more.
  • True MVT — geometries clipped to each tile (with an edge buffer) and encoded with mapbox-vector-tile in the standard 0–4096 grid.
  • Spatial-index-backed selection — only features intersecting a tile are considered.
  • Valid TileJSON 3.0.0 — with 4326 bounds, zoom window, and vector_layers metadata.
  • Built-in MapLibre preview — served at /, auto-styled per geometry type, fit to bounds.
  • Correct CRS handling — reprojects once to Web Mercator for tiling; refuses files with a missing CRS instead of guessing silently.
  • One-file Docker deploy and a friendly click + rich CLI.

How it works

For each requested z/x/y tile the server:

  1. computes the tile's EPSG:3857 bounding box with mercantile,
  2. queries the layer's spatial index for intersecting features,
  3. clips them to the (buffered) tile box with Shapely,
  4. affine-transforms the clipped coordinates into the MVT 0–4096 grid (origin top-left, y down),
  5. encodes the result with mapbox-vector-tile.

The source file is reprojected to EPSG:3857 exactly once, at load time, because web map tiles are defined in Web Mercator. That is correct here — but do not reuse EPSG:3857 for metric analysis (areas, distances, buffers), where it distorts badly; use a projected/UTM CRS for measurement instead. For production, bake your explored layers into static PMTiles and serve those; this server is for the fast local loop before that step.

Learn more

Deep dives on the concepts behind this tool, from python-geospatial.com:

Development

git clone https://github.com/python-geospatial/tile-server-lite.git
cd tile-server-lite
pip install -e ".[dev]"

ruff check .
pytest -q

Tests generate tiny in-memory geometries in tmp_path and run entirely offline — no network access and no large fixtures required.

License

MIT · Copyright (c) 2026 python-geospatial.com

About

Minimal FastAPI vector tile server: serve any GeoPackage/GeoParquet as on-the-fly XYZ Mapbox Vector Tiles with a built-in MapLibre preview and one-file Docker deploy. Maintained by python-geospatial.com.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors