Simple Python wrapper for MapLibre GL Native using native Rust renderer
Project description
mlnative
Render static map images from Python using MapLibre Native.
Platform: Linux x64, ARM64
Python: 3.12+
Uses the maplibre-native Rust crate for high-performance native rendering.
Quick Start
pip install mlnative
Check the local install and native renderer binary:
python -m mlnative doctor
# Optional full renderer check, no network tiles required:
python -m mlnative doctor --render
from mlnative import Map
with Map(512, 512) as m:
png = m.render(center=[-122.4194, 37.7749], zoom=12)
open("map.png", "wb").write(png)
For geocoding examples, install the optional extra:
pip install 'mlnative[geo]'
Features
- Zero config - Works out of the box with OpenFreeMap tiles
- HiDPI support -
pixel_ratio=2for sharp retina displays - Batch rendering - Efficiently render hundreds of maps
- Optional geocoding extra - Use
mlnative[geo]for address lookup examples - Custom markers - Add GeoJSON points, lines, polygons
Screenshots
Map Styles
Different OpenFreeMap styles (rendered from "Sydney Opera House"):
| Liberty (default) | Positron (light) | Dark Matter |
|---|---|---|
Styles from OpenFreeMap
HiDPI / Retina Rendering
Same location, different pixel ratios:
| Standard (1x) | HiDPI (2x) |
|---|---|
| 400x300 px | 800x600 px |
Both displayed at 200px width. The 2x version has 4x more pixels for sharper details.
Both images show the exact same geographic area. The 2x version has 4x more pixels for sharper text and details on retina displays.
Examples
For the complete API reference, see docs/API.md.
Render from address (optional geo extra)
from geopy.geocoders import ArcGIS
from mlnative import Map
geolocator = ArcGIS()
location = geolocator.geocode("Sydney Opera House")
with Map(512, 512) as m:
png = m.render(
center=[location.longitude, location.latitude],
zoom=15
)
Install first with pip install 'mlnative[geo]'.
Fit bounds to show area
from mlnative import Map, feature_collection, point
# Show multiple locations
markers = feature_collection([
point(-122.4194, 37.7749), # SF
point(-122.2712, 37.8044), # Oakland
])
with Map(800, 600) as m:
# Load style as dict to modify it
style = {"version": 8, ...} # your style
m.load_style(style)
m.set_geojson("markers", markers)
# Fit map to show all markers
center, zoom = m.fit_bounds(
(-122.5, 37.7, -122.2, 37.9), # xmin, ymin, xmax, ymax
padding=50
)
png = m.render(center=center, zoom=zoom)
Batch render multiple cities (optional geo extra)
from geopy.geocoders import ArcGIS
geolocator = ArcGIS()
# Geocode multiple cities
cities = ["London", "New York", "Tokyo"]
locations = [geolocator.geocode(city) for city in cities]
# Create views for each city
views = [
{"center": [loc.longitude, loc.latitude], "zoom": 10}
for loc in locations
]
with Map(512, 512) as m:
pngs = m.render_batch(views) # Returns list of PNG bytes
# pngs[0] = London, pngs[1] = New York, pngs[2] = Tokyo
HiDPI / Retina rendering
Use pixel_ratio to render high-resolution images for crisp display on retina/HiDPI screens.
center = [2.3522, 48.8566] # Paris
# Standard display (1x) - 512x512 image
with Map(512, 512, pixel_ratio=1) as m:
png = m.render(center=center, zoom=13)
# Retina/HiDPI display (2x) - 1024x1024 image
with Map(512, 512, pixel_ratio=2) as m:
png = m.render(center=center, zoom=13)
# Same geographic area, but text appears sharper
Key points:
pixel_ratio=2creates an image 2x larger in each dimension (4x total pixels)- Shows the exact same geographic area as
pixel_ratio=1 - Text, icons, and lines are rendered sharper, not smaller
- Common values: 1 (standard), 2 (retina), 3 (ultra-HD)
API Reference
Map(width, height, pixel_ratio=1.0)
Create map renderer. Context manager ensures cleanup.
Parameters:
width,height: Output dimensions in CSS/logical pixelspixel_ratio: Scale factor for HiDPI (1=normal, 2=retina, 3=ultra-HD)- Output image dimensions will be
width × pixel_ratiobyheight × pixel_ratio - Geographic coverage remains the same regardless of pixel_ratio
- Output image dimensions will be
timeout: Optional renderer command timeout in seconds. Defaults toMLNATIVE_TIMEOUTor 30.
render(center, zoom, bearing=0, pitch=0)
Render single view. Returns PNG bytes.
center:[longitude, latitude]- Center can be a list or tuple of two finite numbers.
zoom: 0-24bearing: Rotation in degrees (0-360)pitch: Tilt in degrees (0-85)
render_batch(views)
Render multiple views efficiently.
views = [
{"center": [lon, lat], "zoom": z},
{"center": [lon, lat], "zoom": z, "bearing": 45},
]
# Per-view GeoJSON updates are not supported here. Use set_geojson()
# and render() in a loop when each image needs different source data.
# Large batches are capped to keep memory use predictable.
fit_bounds(bounds, padding=0, max_zoom=24)
Calculate center/zoom to fit bounding box. Bounds must stay within Web Mercator latitude limits (about ±85.0511°).
Bounds can be a list or tuple: (xmin, ymin, xmax, ymax).
center, zoom = m.fit_bounds((xmin, ymin, xmax, ymax))
png = m.render(center=center, zoom=zoom)
set_geojson(source_id, geojson)
Update GeoJSON source in style (requires dict style, not URL). Each update reloads the full style in the current backend, so keep source payloads modest.
m.set_geojson("markers", {"type": "FeatureCollection", "features": [...]})
load_style(style)
Load custom style (URL, file path, or dict).
# OpenFreeMap styles
m.load_style("https://tiles.openfreemap.org/styles/liberty")
m.load_style("https://tiles.openfreemap.org/styles/positron")
# MapLibre demo
m.load_style("https://demotiles.maplibre.org/style.json")
# Custom style dict
m.load_style({"version": 8, "sources": {...}, "layers": [...]})
GeoJSON Helpers
from mlnative import point, feature_collection, from_coordinates, from_latlng, bounds_to_polygon
# Create point
sf = point(-122.4194, 37.7749, {"name": "San Francisco"})
# From coordinate tuples
fc = from_coordinates([(-122.4, 37.8), (-74.0, 40.7)])
# From GPS (lat, lng) order
fc = from_latlng([(37.8, -122.4), (40.7, -74.0)])
# Convert bounds to polygon
poly = bounds_to_polygon((-122.5, 37.7, -122.3, 37.9))
Notes
pixel_ratio and HiDPI rendering
The pixel_ratio parameter controls the resolution of the output image:
| pixel_ratio | Output size | Use case |
|---|---|---|
| 1 | 512x512 → 512x512 | Standard displays |
| 2 | 512x512 → 1024x1024 | Retina/HiDPI displays |
| 3 | 512x512 → 1536x1536 | Ultra-HD displays |
- Higher
pixel_ratio= larger output image - Same geographic area shown regardless of pixel_ratio
- Text and icons scale properly (sharper, not smaller)
- fit_bounds() automatically accounts for pixel_ratio
Other notes
- Default style: OpenFreeMap Liberty (no configuration needed)
- GeoJSON updates: Requires style loaded as dict, not URL
- Platform: Linux x64 and ARM64 wheels are published. Other platforms require a source build and compatible native dependencies.
Troubleshooting
- Start with
python -m mlnative doctor; add--renderfor a local renderer smoke check. - Native renderer binary not found: install a platform wheel or run
just build-rustfor source builds. PATH lookup is disabled unlessMLNATIVE_USE_SYSTEM_BINARY=1is set. - Protocol version mismatch: rebuild the Rust renderer with
just build-rust; the Python package and binary are out of sync. - Timeout waiting for renderer: increase
MLNATIVE_TIMEOUTfor slow tile/style services. A timed-out renderer is stopped; create a newMapto retry. set_geojson()fails with URL-loaded style: load the style as a dict before mutating sources.
Server example safety
The FastAPI and web UI examples bind to 127.0.0.1 and use a small style allowlist. Before exposing a render endpoint publicly, add authentication, per-client quotas/rate limits, cache hot responses, cap worker concurrency, and restrict renderer network egress.
Development
See CONTRIBUTING.md for local development, CI/CD, style, and release guidance.
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 mlnative-0.3.13.tar.gz.
File metadata
- Download URL: mlnative-0.3.13.tar.gz
- Upload date:
- Size: 47.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e35af0722e09481aa5ce327e8fc32b0ab00970d2a3734c8aa9920b0f9cd8a0b
|
|
| MD5 |
3a88f02fc58eb206c32f539be0bf2512
|
|
| BLAKE2b-256 |
229868e1eca4622c1f3d97f466187d50a8178f9a212865cc5691647512e19834
|
Provenance
The following attestation bundles were made for mlnative-0.3.13.tar.gz:
Publisher:
release.yml on adonm/mlnative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mlnative-0.3.13.tar.gz -
Subject digest:
7e35af0722e09481aa5ce327e8fc32b0ab00970d2a3734c8aa9920b0f9cd8a0b - Sigstore transparency entry: 1770696077
- Sigstore integration time:
-
Permalink:
adonm/mlnative@f1111de4a0c035edfa05d2e4244816251ff88550 -
Branch / Tag:
refs/tags/v0.3.13 - Owner: https://github.com/adonm
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f1111de4a0c035edfa05d2e4244816251ff88550 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mlnative-0.3.13-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: mlnative-0.3.13-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 8.8 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57bf11fbb474c2430ed38505d814647ce8d297de53f2227d1054dacfa7e31d23
|
|
| MD5 |
b1a345e4a59478b97bec05c0d3040b84
|
|
| BLAKE2b-256 |
8484079a8f494a0cd0d49fc58081aa80908ff01c46e222dd33a4c8b2a1ce724f
|
Provenance
The following attestation bundles were made for mlnative-0.3.13-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on adonm/mlnative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mlnative-0.3.13-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
57bf11fbb474c2430ed38505d814647ce8d297de53f2227d1054dacfa7e31d23 - Sigstore transparency entry: 1770696362
- Sigstore integration time:
-
Permalink:
adonm/mlnative@f1111de4a0c035edfa05d2e4244816251ff88550 -
Branch / Tag:
refs/tags/v0.3.13 - Owner: https://github.com/adonm
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f1111de4a0c035edfa05d2e4244816251ff88550 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mlnative-0.3.13-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: mlnative-0.3.13-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 8.2 MB
- Tags: Python 3, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
353d6f23a40a7433684128a26b7e0c259c379bc38eb585873048b071a24b5df7
|
|
| MD5 |
770e569b5dabca150f773319fe4cd004
|
|
| BLAKE2b-256 |
df5c14e5a08aa9dd8988c5c83b3c00fd10246759591c589ae2e4a9a4dc28dba3
|
Provenance
The following attestation bundles were made for mlnative-0.3.13-py3-none-manylinux_2_28_aarch64.whl:
Publisher:
release.yml on adonm/mlnative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mlnative-0.3.13-py3-none-manylinux_2_28_aarch64.whl -
Subject digest:
353d6f23a40a7433684128a26b7e0c259c379bc38eb585873048b071a24b5df7 - Sigstore transparency entry: 1770696680
- Sigstore integration time:
-
Permalink:
adonm/mlnative@f1111de4a0c035edfa05d2e4244816251ff88550 -
Branch / Tag:
refs/tags/v0.3.13 - Owner: https://github.com/adonm
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f1111de4a0c035edfa05d2e4244816251ff88550 -
Trigger Event:
push
-
Statement type: