Skip to main content

mlnative

PyPI version License

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=2 for 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
Liberty Positron Dark

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=2 creates 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 pixels
  • pixel_ratio: Scale factor for HiDPI (1=normal, 2=retina, 3=ultra-HD)
    • Output image dimensions will be width × pixel_ratio by height × pixel_ratio
    • Geographic coverage remains the same regardless of pixel_ratio
  • timeout: Optional renderer command timeout in seconds. Defaults to MLNATIVE_TIMEOUT or 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-24
  • bearing: 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 --render for a local renderer smoke check.
  • Native renderer binary not found: install a platform wheel or run just build-rust for source builds. PATH lookup is disabled unless MLNATIVE_USE_SYSTEM_BINARY=1 is 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_TIMEOUT for slow tile/style services. A timed-out renderer is stopped; create a new Map to 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

Metadata

Release files for mlnative 0.3.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mlnative 0.3.13
File Size Uploaded
mlnative-0.3.13.tar.gz 47.2 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for mlnative 0.3.13
File Interpreter ABI Platform
mlnative-0.3.13-py3-none-manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.28+ x86-64 Details
mlnative-0.3.13-py3-none-manylinux_2_28_aarch64.whl Python 3 none Linux glibc 2.28+ ARM64 Details

Total release size: 17.0 MB

Release files / mlnative-0.3.13.tar.gz

Download URL mlnative-0.3.13.tar.gz
Size 47.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7e35af0722e09481aa5ce327e8fc32b0ab00970d2a3734c8aa9920b0f9cd8a0b
BLAKE2b-256 checksum
How to use checksums
229868e1eca4622c1f3d97f466187d50a8178f9a212865cc5691647512e19834
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 9, 2026.

Transparency log

Release files / mlnative-0.3.13-py3-none-manylinux_2_28_x86_64.whl

Download URL mlnative-0.3.13-py3-none-manylinux_2_28_x86_64.whl
Size 8.8 MB
Tags Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
57bf11fbb474c2430ed38505d814647ce8d297de53f2227d1054dacfa7e31d23
BLAKE2b-256 checksum
How to use checksums
8484079a8f494a0cd0d49fc58081aa80908ff01c46e222dd33a4c8b2a1ce724f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 9, 2026.

Transparency log

Release files / mlnative-0.3.13-py3-none-manylinux_2_28_aarch64.whl

Download URL mlnative-0.3.13-py3-none-manylinux_2_28_aarch64.whl
Size 8.2 MB
Tags Linux glibc 2.28+ ARM64 Python 3
SHA-256 checksum
How to use checksums
353d6f23a40a7433684128a26b7e0c259c379bc38eb585873048b071a24b5df7
BLAKE2b-256 checksum
How to use checksums
df5c14e5a08aa9dd8988c5c83b3c00fd10246759591c589ae2e4a9a4dc28dba3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 9, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page