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.
Releases: https://github.com/ocha-rosea/mapbox-tileset-uploader/releases
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
- Mapbox Account: Sign up at mapbox.com
- Access Token: Use a non-URL-restricted token with the following scopes:
tilesets:writetilesets:readtilesets: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
.geojsonand 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.exelauncher is broken, MTU automatically falls back to the in-processmapbox-tilesetsmodule. - For best reliability and startup speed, prefer onedir mode.
- UI startup window state (maximized/full-size on launch) is controlled in
src/mtu/ui.pyand applies to both source and packaged builds.
Unsigned Distributables (Temporary)
Current distributed binaries are unsigned. Until code signing is enabled, you may see an OS trust warning.
Windows (SmartScreen):
- In the "Windows protected your PC" dialog, click More info.
- Confirm the app source/path, then click Run anyway.
Linux:
- There is no SmartScreen-style prompt, but your desktop/session may warn about unknown publishers.
- Make the app executable if needed:
chmod +x <binary>.
Signing note:
- Windows Authenticode signing (PFX certificate) and Linux signing/notarization are separate systems.
- Do not expect the same Windows signing keys/certificate to satisfy Linux distribution signing requirements.
License
MIT License - see LICENSE for details.
Related Projects
- mapbox-tilesets - Official Mapbox Tilesets CLI
- geoBoundaries - Open political boundary data
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mtu-1.1.3.tar.gz.
File metadata
- Download URL: mtu-1.1.3.tar.gz
- Upload date:
- Size: 48.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
034d3c047a4e89e8fae813d6389a58616212dced8e688c0eca1a4d77b050fea3
|
|
| MD5 |
9ba17f65ecd9975873bfb18504dad8cd
|
|
| BLAKE2b-256 |
9568882bc30f4a7e04e85bf0da1d2735b67d3c9cd2ddab894a96cfb40cf14884
|
Provenance
The following attestation bundles were made for mtu-1.1.3.tar.gz:
Publisher:
release.yml on ocha-rosea/mapbox-tileset-uploader
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mtu-1.1.3.tar.gz -
Subject digest:
034d3c047a4e89e8fae813d6389a58616212dced8e688c0eca1a4d77b050fea3 - Sigstore transparency entry: 1041744774
- Sigstore integration time:
-
Permalink:
ocha-rosea/mapbox-tileset-uploader@9526017794cb4271b078a78d3d12bf232a62e0b0 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ocha-rosea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9526017794cb4271b078a78d3d12bf232a62e0b0 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file mtu-1.1.3-py3-none-any.whl.
File metadata
- Download URL: mtu-1.1.3-py3-none-any.whl
- Upload date:
- Size: 53.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b61c562c91ba257ff00a90871be1a13373e93554d3e3b7d629c9fd993a0d8ea7
|
|
| MD5 |
e9877b996fee164f821f508e85411d5b
|
|
| BLAKE2b-256 |
a99171301fbe0af60e8f4a647491c27039e3135a3d6611d67aa4e1af5e87ee10
|
Provenance
The following attestation bundles were made for mtu-1.1.3-py3-none-any.whl:
Publisher:
release.yml on ocha-rosea/mapbox-tileset-uploader
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mtu-1.1.3-py3-none-any.whl -
Subject digest:
b61c562c91ba257ff00a90871be1a13373e93554d3e3b7d629c9fd993a0d8ea7 - Sigstore transparency entry: 1041744895
- Sigstore integration time:
-
Permalink:
ocha-rosea/mapbox-tileset-uploader@9526017794cb4271b078a78d3d12bf232a62e0b0 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ocha-rosea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9526017794cb4271b078a78d3d12bf232a62e0b0 -
Trigger Event:
workflow_dispatch
-
Statement type: