Skip to main content

Simple Python wrapper for MapLibre GL Native using native Rust renderer

Project description

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
from mlnative import Map
from geopy.geocoders import ArcGIS

# Geocode an address
geolocator = ArcGIS()
location = geolocator.geocode("San Francisco")

# Render map at that location
with Map(512, 512) as m:
    png = m.render(
        center=[location.longitude, location.latitude],
        zoom=12
    )
    open("map.png", "wb").write(png)

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
  • Address geocoding - Built-in support via geopy
  • 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

Render from address

from mlnative import Map
from geopy.geocoders import ArcGIS

geolocator = ArcGIS()
location = geolocator.geocode("Sydney Opera House")

with Map(512, 512) as m:
    png = m.render(
        center=[location.longitude, location.latitude],
        zoom=15
    )

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

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.

from geopy.geocoders import ArcGIS

geolocator = ArcGIS()
location = geolocator.geocode("Paris")

# Standard display (1x) - 512x512 image
with Map(512, 512, pixel_ratio=1) as m:
    png = m.render(
        center=[location.longitude, location.latitude],
        zoom=13
    )

# Retina/HiDPI display (2x) - 1024x1024 image
with Map(512, 512, pixel_ratio=2) as m:
    png = m.render(
        center=[location.longitude, location.latitude],
        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

render(center, zoom, bearing=0, pitch=0)

Render single view. Returns PNG bytes.

  • center: [longitude, latitude]
  • 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, "geojson": {"markers": {...}}},
]

fit_bounds(bounds, padding=0, max_zoom=24)

Calculate center/zoom to fit bounding box.

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).

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

# 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)])

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 only (macOS/Windows builds disabled due to upstream issues)

Development

See docs/CI.md for CI/CD setup and requirements.

License

Apache-2.0

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

mlnative-0.3.5.tar.gz (72.5 kB view details)

Uploaded Source

Built Distributions

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

mlnative-0.3.5-py3-none-manylinux_2_28_x86_64.whl (20.9 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

mlnative-0.3.5-py3-none-manylinux_2_28_aarch64.whl (20.5 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

File details

Details for the file mlnative-0.3.5.tar.gz.

File metadata

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

File hashes

Hashes for mlnative-0.3.5.tar.gz
Algorithm Hash digest
SHA256 c9d9468b683e50f5eb5ae7e8ebceb0263bcefe6ad34d138dfc1066cccf4f7c55
MD5 fe48b1e6e3feda2548b67a6ab2531b60
BLAKE2b-256 c25c633ff218c4031efb4b9e2ece2c02c0cbd5564dd7b3c3980a52e7e3b00978

See more details on using hashes here.

Provenance

The following attestation bundles were made for mlnative-0.3.5.tar.gz:

Publisher: release.yml on adonm/mlnative

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

File details

Details for the file mlnative-0.3.5-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for mlnative-0.3.5-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a49481341546dcded56fb98cc116677ffa7d63a79c8f6eb66ecadd66a421e65c
MD5 5c3d6cfab22db212d4be341eb18e05d1
BLAKE2b-256 fa9b5d9129eec2c2cd50145377134c4227f076b389b6e0c972b54d64db031eb9

See more details on using hashes here.

Provenance

The following attestation bundles were made for mlnative-0.3.5-py3-none-manylinux_2_28_x86_64.whl:

Publisher: release.yml on adonm/mlnative

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

File details

Details for the file mlnative-0.3.5-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for mlnative-0.3.5-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 ace53c14cd3f898f928d5b7ddae905d6b3ece26d9c2f51975b0d038813e5576b
MD5 7f326aedf089ca525a2df82c58c1bcd7
BLAKE2b-256 fff08d5ff61e4b905ae0d75263680aae103bcfff116e84d84a017cd188e93f1d

See more details on using hashes here.

Provenance

The following attestation bundles were made for mlnative-0.3.5-py3-none-manylinux_2_28_aarch64.whl:

Publisher: release.yml on adonm/mlnative

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