Skip to main content

A CLI tool to upload GeoJSON, TopoJSON, Shapefile, GeoPackage, and other GIS formats to Mapbox as vector tilesets

Project description

Mapbox Tileset Uploader

Mapbox Tileset Uploader (mtu) is a Python package for preparing and publishing GIS data to Mapbox vector tilesets. It provides three interfaces: a CLI, a Python API, and a desktop UI. The project includes a modular converter pipeline that normalizes supported GIS formats to GeoJSON before validation, recipe creation, and tileset publishing.

PyPI version PyPI Downloads Python 3.10+ License: MIT

Features

  • Upload: GIS files to Mapbox Tiling Service (MTS)
  • Multi-format support: GeoJSON, TopoJSON, Shapefile, GeoPackage, KML/KMZ, FlatGeobuf, GeoParquet, GPX
  • Geometry validation: warns about invalid geometries without modifying data
  • Remote sources: download and upload from URLs
  • Conversion pipeline: automatic format detection and conversion to GeoJSON
  • Configurable output: zoom levels, layer names, and recipes
  • Upload guardrails: default 1 GB upload cap across UI, CLI, and API (optional full-cap mode up to Mapbox's 20 GB per-file limit); backend always enforces Mapbox's hard 20 GB limit
  • Interfaces: CLI, Python API, and desktop UI
  • Architecture: modular converter system with optional dependencies

Installation

Basic Installation (GeoJSON & TopoJSON only)

pip install mtu

With Optional Format Support

# Shapefile support
pip install mtu[shapefile]

# GeoPackage, KML, FlatGeobuf support (via fiona)
pip install mtu[fiona]

# GeoParquet support
pip install mtu[geoparquet]

# GPX support
pip install mtu[gpx]

# Geometry validation (via shapely)
pip install mtu[validation]

# All formats and validation
pip install mtu[all-formats]

# All features including dev tools
pip install mtu[all]

Install from Source

git clone https://github.com/ocha-rosea/mapbox-tileset-uploader.git
cd mapbox-tileset-uploader
pip install -e ".[all]"

Supported Formats

Format Extensions Dependencies Notes
GeoJSON .geojson, .json None Native support
TopoJSON .topojson None Full decoder with transform support
Shapefile .shp, .zip pyshp Supports zipped shapefiles
GeoPackage .gpkg fiona Supports layer selection
KML/KMZ .kml, .kmz fiona Handles zipped KMZ
FlatGeobuf .fgb fiona Cloud-optimized format
GeoParquet .parquet, .geoparquet geopandas, pyarrow Columnar format
GPX .gpx gpxpy Tracks, routes, waypoints

Check available formats with:

mtu formats

Architecture

flowchart LR
  subgraph Interfaces["Entry Points"]
    CLI[CLI: mtu]
    UI[Desktop UI: mtu ui]
    API[Python API]
  end

  subgraph Core["Core Upload Engine src/mtu/uploader.py"]
    Input["Input handler file or URL"]
    Detect["Format detection"]
    Registry["Converter registry"]
    Convert["Format converter to GeoJSON"]
    Validate["Geometry validator optional"]
    Recipe["Recipe and tileset config builder"]
    Upload["Upload and publish orchestration"]
  end

  subgraph Integrations["External Services"]
    Files["Local or remote GIS data"]
    MbxCLI["Mapbox Tilesets CLI"]
    MTS["Mapbox Tiling Service"]
    Studio["Mapbox Studio tileset"]
  end

  CLI --> Input
  UI --> Input
  API --> Input

  Files --> Input
  Input --> Detect --> Registry --> Convert --> Validate --> Recipe --> Upload
  Upload --> MbxCLI --> MTS --> Studio

Data Flow Diagram

flowchart TD
  A["User starts upload from CLI, Desktop UI, or API"] --> B{Input source}
  B -->|Local file| C[Read from disk]
  B -->|Remote URL| D[Download to temp file]

  C --> E[Detect extension / format]
  D --> E

  E --> F[Resolve converter from registry]
  F --> G["Convert input to GeoJSON FeatureCollection"]
  G --> H{Validation enabled?}

  H -->|Yes| I[Run geometry validator]
  I --> J[Collect warnings]
  H -->|No| K[Skip validator]

  J --> L["Build source and recipe payload"]
  K --> L

  L --> M[Create/replace tileset source]
  M --> N[Create/update recipe]
  N --> O[Publish tileset]
  O --> P[Poll publish job status]
  P --> Q{Completed?}

  Q -->|Success| R["Return UploadResult and Studio link"]
  Q -->|Failed| S[Return parsed actionable error]

Prerequisites

  1. Mapbox Account: Sign up at mapbox.com
  2. Access Token: Use a non-URL-restricted token with the following scopes:
    • tilesets:write
    • tilesets:read
    • tilesets:list

URL-restricted tokens can fail for CLI/desktop uploads because these flows do not send browser referrer URLs.

Set your credentials as environment variables:

export MAPBOX_ACCESS_TOKEN="your-token-here"
export MAPBOX_USERNAME="your-username"

CLI Usage

The CLI command is mtu.

Upload from URL

mtu upload \
  --url https://example.com/data.geojson \
  --id my-tileset \
  --name "My Tileset"

Upload from Local File

mtu upload \
  --file data.geojson \
  --id my-tileset \
  --name "My Tileset"

By default, upload size is capped at 1 GB. To allow larger files (up to Mapbox's 20 GB limit):

mtu upload \
  --file large-data.geojson \
  --id my-tileset \
  --name "My Tileset" \
  --use-mapbox-full-upload-cap

Desktop UI (Windows/macOS/Linux)

Launch the desktop uploader interface:

mtu ui

Or use the dedicated script:

mtu-ui

The desktop app provides:

  • Intro/welcome page (first screen) with workflow, zoom defaults, and Mapbox cost/limit notes
  • GIS file picker (including .geojson and zipped shapefile .zip)
  • Mapbox credentials config panel (saved to ~/.mtu/desktop_config.json)
  • Tileset controls for final tileset name, zoom range, description, and attribution
  • Mapbox capacity guard (user-configured MB capacity/usage) to block uploads when projected usage exceeds limit
  • Optional description, attribution, validation toggle, and dry-run mode
  • Auto-generated tileset ID (reported in the status log), warnings, and Mapbox Studio link on success

Upload with Custom Options

mtu upload \
  --file data.topojson \
  --id boundaries-adm1 \
  --name "Administrative Boundaries Level 1" \
  --layer boundaries \
  --min-zoom 2 \
  --max-zoom 12 \
  --description "Admin level 1 boundaries" \
  --attribution "© OpenStreetMap contributors"

Convert TopoJSON to GeoJSON

mtu convert input.topojson output.geojson --pretty

Convert Any Supported Format

# Shapefile to GeoJSON
mtu convert boundaries.shp boundaries.geojson

# GeoPackage to GeoJSON
mtu convert data.gpkg output.geojson

# Zipped shapefile
mtu convert archive.zip output.geojson

Validate GIS Files

Validate geometry without uploading:

mtu validate data.geojson
mtu validate boundaries.shp --verbose

Show Available Formats

mtu formats

Show Configuration Help

mtu info

List Resources

# List all tileset sources
mtu list-sources

# List all tilesets
mtu list-tilesets

Delete Resources

# Delete a tileset source
mtu delete-source my-source-id

# Delete a tileset
mtu delete-tileset my-tileset-id

Dry Run Mode

Validate your file without uploading:

mtu upload --file data.geojson --id test --name "Test" --dry-run

Python API Usage

from mtu import TilesetUploader
from mtu.uploader import TilesetConfig

# Initialize uploader (uses environment variables by default)
uploader = TilesetUploader()

# Or provide credentials explicitly
uploader = TilesetUploader(
    access_token="your-token",
    username="your-username"
)

# Optional: allow larger files up to Mapbox's 20 GB per-file limit
uploader_full_cap = TilesetUploader(
  access_token="your-token",
  username="your-username",
  use_mapbox_full_upload_cap=True,
)

# Configure the tileset
config = TilesetConfig(
    tileset_id="my-tileset",
    tileset_name="My Tileset",
    layer_name="data",
    min_zoom=0,
    max_zoom=10,
    description="My geographic data"
)

# Upload from URL
results = uploader.upload_from_url(
    url="https://example.com/data.geojson",
    config=config
)

# Upload from local file
results = uploader.upload_from_file(
    file_path="data.geojson",
    config=config
)

if results.success:
    print(f"Tileset uploaded: {results.tileset_id}")
    if results.warnings:
        print(f"Warnings: {results.warnings}")
else:
    print(f"Error: {results.error}")

Upload with Validation

# Enable geometry validation (warns but doesn't modify data)
uploader = TilesetUploader(validate_geometry=True)

results = uploader.upload_from_file("data.geojson", config)

# Check validation results
if results.validation_result:
    for warning in results.validation_result.warnings:
        print(f"  [{warning.severity}] {warning.message}")

Using Format Converters

from mtu.converters import get_converter, get_supported_formats

# List available formats
formats = get_supported_formats()
for fmt in formats:
    status = "✓" if fmt["available"] else "✗ (missing deps)"
    print(f"{fmt['format_name']}: {status}")

# Convert any supported format
converter = get_converter(file_path="data.shp")  # Auto-detect by extension
result = converter.convert("data.shp")

print(f"Converted {result.feature_count} features from {result.source_format}")
print(f"Warnings: {result.warnings}")

# Or specify format explicitly
converter = get_converter(format_name="geopackage")
result = converter.convert("data.gpkg", layer_name="boundaries")

Geometry Validation

from mtu import validate_geojson, GeometryValidator

# Quick validation
result = validate_geojson(geojson_data)
print(f"Valid: {result.valid}, Features: {result.feature_count}")

# Custom validation options
validator = GeometryValidator(
    check_coordinates=True,   # Check coordinate bounds
    check_winding=True,       # Check polygon winding order  
    check_duplicates=True,    # Check duplicate vertices
    check_closure=True,       # Check ring closure
    check_intersections=True, # Check self-intersections (requires shapely)
    max_warnings=100          # Limit warnings
)

result = validator.validate(geojson_data)
for warning in result.warnings:
    print(f"Feature {warning.feature_index}: {warning.message}")

TopoJSON Conversion

from mtu.converters import get_converter

# Convert TopoJSON to GeoJSON
converter = get_converter(format_name="topojson")
result = converter.convert(topojson_data)
geojson = result.geojson

# From file
result = converter.convert("data.topojson")

# Select specific object in TopoJSON
result = converter.convert(topojson_data, object_name="countries")

Custom Recipes

For advanced configurations, provide a custom MTS recipe:

mtu upload \
  --file data.geojson \
  --id my-tileset \
  --name "My Tileset" \
  --recipe custom-recipe.json

Recipe file example (custom-recipe.json):

{
  "version": 1,
  "layers": {
    "boundaries": {
      "source": "mapbox://tileset-source/{username}/{source-id}",
      "minzoom": 0,
      "maxzoom": 14,
      "features": {
        "simplify": {
          "outward_only": true,
          "distance": 1
        }
      }
    }
  }
}

Advanced

Build Windows Executable (Self-Contained)

You can package the desktop UI as a self-contained Windows executable using PyInstaller.

Use a non-conda CPython interpreter (python.org build). Conda-based interpreters can produce _tkinter DLL load failures in packaged apps.

Suggested setup:

winget install -e --id Python.Python.3.11
py -3.11 -m venv C:\Users\<you>\mtu-winbuild
C:\Users\<you>\mtu-winbuild\Scripts\python.exe -m pip install --upgrade pip

From the project root:

./scripts/build_windows_exe.ps1 -PythonPath C:\Users\<you>\mtu-winbuild\Scripts\python.exe

PyInstaller may generate .spec files during local packaging. These are optional build recipes; the project build script above does not require committing them.

This creates a self-contained app in dist/mtu-desktop/ (recommended, onedir mode).

To create a portable ZIP (shareable folder build):

Compress-Archive -Path .\dist\mtu-desktop\* -DestinationPath .\dist\mtu-desktop-portable.zip -Force

Run it by extracting the ZIP, then launching:

./mtu-desktop.exe

If you build with an alternate output name (for example mtu-desktop-si), use the matching folder/exe name.

For a single-file executable:

./scripts/build_windows_exe.ps1 -PythonPath C:\Users\<you>\mtu-winbuild\Scripts\python.exe -OneFile

Notes:

  • The generated executable includes Python runtime and dependencies.
  • If your system tilesets.exe launcher is broken, MTU automatically falls back to the in-process mapbox-tilesets module.
  • For best reliability and startup speed, prefer onedir mode.
  • UI startup window state (maximized/full-size on launch) is controlled in src/mtu/ui.py and applies to both source and packaged builds.

License

MIT License - see LICENSE for details.

Related Projects

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mtu-1.1.2.tar.gz (47.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mtu-1.1.2-py3-none-any.whl (52.6 kB view details)

Uploaded Python 3

File details

Details for the file mtu-1.1.2.tar.gz.

File metadata

  • Download URL: mtu-1.1.2.tar.gz
  • Upload date:
  • Size: 47.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mtu-1.1.2.tar.gz
Algorithm Hash digest
SHA256 357ed3bdce96d59ed0e01abe62b25176e8f3b69c884ecacc158c5dba9736a745
MD5 f20109d5b5b9324fae1da7bd8bc02937
BLAKE2b-256 069f5aa7cbd14f7b18b558985dc6f792ede7ae3c9ed1d6dda025549f7d32fe87

See more details on using hashes here.

Provenance

The following attestation bundles were made for mtu-1.1.2.tar.gz:

Publisher: release.yml on ocha-rosea/mapbox-tileset-uploader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mtu-1.1.2-py3-none-any.whl.

File metadata

  • Download URL: mtu-1.1.2-py3-none-any.whl
  • Upload date:
  • Size: 52.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mtu-1.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 eed175343e9df689e90df7ba949fb676ccf4180590064326d2dfeb2893e1f50a
MD5 b06737888fd7267f377130c2fd33c777
BLAKE2b-256 10ab5780cab4d86bac813bf4f4c29d06e40ead39e155cfeec8f0bc322e26fb26

See more details on using hashes here.

Provenance

The following attestation bundles were made for mtu-1.1.2-py3-none-any.whl:

Publisher: release.yml on ocha-rosea/mapbox-tileset-uploader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page