Skip to main content

Python wrapper for MapLibre Native

Project description

PyMGL: Maplibre GL Native Static Renderer for Python

This package provides an interface to Mapblibre Native to render Mapbox GL / Maplibre GL styles to PNG images.

WARNING: this package is under active development and the API may change without notice.

Goals

This package is intended to provide a lightweight interface to maplibre-native for rendering Mapbox GL / Maplibre GL styles to PNG image data using Python. This is particularly useful for server-side rendering of maps for use in reports.

This package provides only the Python API for interacting with maplibre-native; it does not provide higher-level functionality such as a web server or a CLI.

Install

Supported operating systems

MacOS 12+, Ubuntu 18+, Debian 10+, Fedora 29+, RHEL 8+, Alma Linux 8+

x86_64 and arm64 wheels are available on PyPI:

pip install pymgl

NOTE: x86_64 wheels are not currently available for MacOS.

To verify that pymgl installed correctly, install with the test dependencies and run the included test suite:

pip install pymgl[test]
pytest --pyargs pymgl -v

Windows

Windows is not and will not be supported.

Usage

To create a map object, you must always provide a Mapbox GL / Maplibre GL style JSON string or URL to a well-known style hosted by Mapbox or Maptiler:

from pymgl import Map

style = """{
    "version": 8,
    "sources": {
        "basemap": {
            "type": "raster",
            "tiles": ["https://services.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}"],
            "tileSize": 256
        }
    },
    "layers": [
        { "id": "basemap", "source": "basemap", "type": "raster" }
    ]
}"""

map = Map(style, <height=256>, <width=256>, <ratio=1>, <longitude=0>, <latitude=0>, <zoom=0>, <token=None>, <provider=None>)

See the styles section for more information about map styles.

Other than style, all other parameters are optional with default values.

NOTE: style and ratio cannot be changed once the instance is constructed.

You can use a well-known style instead of providing a style JSON string, but you must also provide a token and identify the correct provider:

map = Map("mapbox://styles/mapbox/streets-v11", token=<mapbox token>, provider="mapbox")

Valid providers are mapbox, maptiler, and maplibre.

Map properties

You can set additional properties on the map instance after it is created:

map.setCenter(longitude, latitude)

map.setZoom(zoom)

map.setSize(width, height)

map.setBearing(bearing)  # map bearing in degrees

map.setPitch(pitch)  # map pitch in degrees

map.setFilter(layerId, filterJSON or None)

map.setPaintProperty(layerId, property, value)

map.setVisibility(layerId, True / False)

You can retrieve these values using attributes, if needed:

map.size  # (width, height)

map.center  # (longitude, latitude)

map.zoom

map.bearing

map.pitch

You can also retrive information about the map's style or a specific layer:

map.listLayers()  # [<layerId1>, ...]

map.listSources()  # [<sourceId1>, ...]

map.getFilter(<layerId>)  # returns JSON value or None

map.getPaintProperty(<layerId>, <property>)  # returns JSON value or None

map.getLayerJSON(<layerId>)  # returns JSON describing layer

NOTE: paint properties may be decoded to their internal representation. For example, a CSS color string #FF0000 will be returned as ["rgba", 255, 0, 0, 1].

IMPORTANT: if you are using a remotely-hosted style, you need to force the map to load - which loads all underying assets - before listing the style's layers, sources, or other properties.

map = Map("mapbox://styles/mapbox/streets-v11", token=<mapbox token>, provider="mapbox")

map.listLayers()  # []
map.load()
map.listLayers()  # [<layerId1>, ...]

Alternatively, you can download the style yourself and provide that as input to the Map, and it will show all layers without requiring a render first. However, not all assets will be loaded until the first render.

from urllib.request import urlopen

url = f"https://api.mapbox.com/styles/v1/mapbox/streets-v11?access_token={MAPBOX_TOKEN}"

with urlopen(url) as r:
    style = r.read()

map = Map(style.decode("UTF-8") token=<mapbox token>, provider="mapbox")
map.listLayers()  # [<layerId1>, ...]

You can auto-fit the map to bounds instead of using center longitude / lantitude and zoom:

map.setBounds(xmin, ymin, xmax, ymax, <padding=0>)

You can register an image for use with your style by providing an ID, raw image bytes, width, height, pixel ratio, and indicate if it should be interpreted as SDF:

map.addImage("id", img_bytes, width, height, <ratio=1>, <make_SDF=False>)

See the SDF image docs for more information about using SDF images.

Rendering

You can render the map to PNG bytes:

img_bytes = map.renderPNG()

This returns bytes containing the RGBA PNG data.

You can render the map to a raw buffer as a numpy array (uint8 dtype):

array = map.renderBuffer()

The array is a sequence of RGBA values for each pixel in the image.

This may be useful if you are going to immediately read the image data into another package such as Pillow or pyvips to combine with other image operations.

Map instances

WARNING: you must manually delete the map instance if you assign a new map instance to that variable, or this package will segfault (not yet sure why). This problem does not occur if separate instances are assigned to separate variables.

map = Map(<style>, <width>, <height>)

del map  # must manually delete BEFORE creating a new instance assigned to this

map = Map(<style>, <width>, <height>)

For this reason, you should consider using a context manager:

with Map(<style>, <width>, <height>) as map:
    map.renderPNG()

You can also use the map instance to directly render to PNG, if you don't need to set other properties on the map instance:

Map(<style>, <width>, <height>).renderPNG()

Styles

PyMGL should support basic styles as of Mapbox GL JS 1.13.

Remote tilesets, sources, and assets

Remote tilesets, tile sources, and assets (glyphs, sprites) should be well-supported. These are loaded by the underlying C++ library outside our control. Invalid URLs will generally raise errors. However, network timeouts or incorrect formats may cause the process to crash.

Local mbtiles

Local MBTiles are supported, but must be provided using an absolute path to the mbtiles file as the source url of a tileset; it must resolve to an actual file.

Local MBTiles are denoted with a mbtiles:// URI prefix.

Example:

{
    "sources": {
        "source_id": {
            "url": "mbtiles:///<pymgl_root_dir>/tests/fixtures/geography-class-png.mbtiles",
            ...
        }
    },
    "layers": [...],
    ...
}

Local files

GeoJSON files and other local file assets are supported, but must be provided using an absolute path to the file source.

Example:

{
    "sources": {
        "geojson": {
            "type": "geojson",
            "data": "file:///<pymgl_root_dir>/tests/fixtures/test.geojson"
        }
    },
    "layers": [...],
    ...
}

WARNING: providing a URI to tiles under the tiles key of a source is NOT currently supported by Maplibre Native; attempting to do so will fail.

Images

You must register the image with the map instance before rendering the map. See map.addImage() above.

{
    "sources": {...},
    "layers": [
        {
            ...,
            "paint": {
                "fill-pattern": "pattern"
            }
        },
    ]
}

You can use map images as fill patterns or icon images.

Adding sources and layers after construction

You can add sources and layers dynamically after constructing the map instance:

import json

map = Map("")  # construct with empty style

map.addSource("my_id", json.dumps({
    "type": "geojson",
    "data": {"type": "Point", "coordinates": [0, 0]}
}))

map.addLayer(json.dumps({
    "id": "geojson-point",
    "source": "geojson",
    "type": "circle",
    "paint": { ... }
}))

Feature state

You can get, set, and remove feature state after the map has been loaded.

map = Map(<style with source "exampleSource" and layer "exampleLayer">, ...)

map.load()
map.getFeatureState("exampleSource", "exampleLayer", "0")  # returns None
map.setFeatureState("exampleSource", "exampleLayer", "0", "{\"a\": true}")
map.getFeatureState("exampleSource", "exampleLayer", "0")  # returns "{\"a\": true}"

# remove the state value for key "a"
map.removeFeatureState("exampleSource", "exampleLayer", "0", "a")
map.render()
map.getFeatureState("exampleSource", "exampleLayer", "0")  # returns None

NOTE: features must already have a unique, numeric ID set on each feature. There is currently no support for promoteId like in MapLibre GL JS.

IMPORTANT: the map must be loaded before getting or setting feature state. You must manually force a render in order for the map to update feature state after removing a state key

Unsupported features

PyMGL does not support alternative projections or 3D terrain.

Developing

Dependencies:

MacOS:

Developing on MacOS requires the following binary libraries to be installed via homebrew:

  • cmake
  • ninja

Developing on Ubuntu requires the following binary libraries:

  • cmake
  • ninja-build
  • build-essential
  • libcurl4-openssl-dev
  • libicu-dev
  • libpng-dev
  • libwebp-dev
  • libprotobuf-dev
  • libjpeg-turbo8-dev
  • libx11-dev
  • libegl-dev
  • libopengl-dev
  • xvfb

To run on Linux, XVFB must also be running; otherwise the process will segfault.

See docker/README.md for more information.

nanobind

nanonbind is used to provide bindings for Python against a C++ class that wraps maplibre-native for easier rendering operations.

It is included here as a git submodule, per the installation instructions.

git submodule add https://github.com/wjakob/nanobind vendor/nanobind
cd vendor/nanobind
git submodule update --init --recursive

Then to upgrade to a specific version of nanobind for development, if needed:

cd vendor/nanobind
git checkout <version tag>

Maplibre Native

Maplibre Native is included as a git submodule, and it includes many submodules of its own.

git submodule add -b main https://github.com/maplibre/maplibre-native vendor/maplibre-native

Git submodules

Run

git submodule update --init

We only need some of the submodules under maplibre-native. In particular, we do not need maplibre-gl-js or Android / IOS dependencies.

Run the following:

cd vendor/maplibre-native

git submodule update --init --recursive \
    vendor/boost \
    vendor/cpp-httplib \
    vendor/earcut.hpp \
    vendor/eternal \
    vendor/googletest \
    vendor/metal-cpp \
    vendor/polylabel \
    vendor/protozero \
    vendor/mapbox-base \
    vendor/unique_resource \
    vendor/unordered_dense \
    vendor/vector-tile \
    vendor/wagyu \
    vendor/zip-archive

To later update maplibre-native:

cd vendor/maplibre-native
git checkout main
git pull origin

cd ../..
git commit -am "update maplibre-native" to latest

Architecture

This package is composed of 2 main parts:

  • wrapper around Maplibre Native classes to make constructing and managing properties of the map easier
  • Python bindings created using nanobind against that wrapper

The wrapper is located in src/map.cpp.

Build

C++ tests

See tests/README for more information.

Build Python extension

The Python setup.py script manages building the library and extension using CMake.

From project root directory:

python setup.py build_ext --inplace

Docstrings / type information

Docstrings are maintained in both src/_pymgl.cpp and pymgl/__init__.pyi.

Python-friendly type annotations are maintained in pymgl/__init__.pyi.

Note: pymgl/__init__.pyi is necessary to support autocompletion and tooltips in VSCode.

Building wheels

Most wheels are automatically built by Github when pushing a new version tag. Linux Arm64 wheels must be built locally on an Arm64 machine (e.g., MacOS host).

These are created using the manylinux_2_28 Docker container.

docker build -f ci/Dockerfile.manylinux_2_28_aarch64 -t pymgl-manylinux_2_28_aarch64 .
docker run -v "$PWD/:/app" pymgl-manylinux_2_28_aarch64 ci/build_linux_wheels.sh

This will create aarch64 wheels in dist that can be uploaded directly to PyPI.

See also

mbgl-renderer provides a NodeJS API, CLI, and server based on the NodeJS bindings to Mapbox GL Native.

Credits

This project was developed with the support of the U.S. Fish and Wildlife Service Southeast Conservation Adaptation Strategy for use in the Southeast Conservation Blueprint Viewer.

This project is made possible because of the mapbox-gl-native project by Mapbox by the efforts of the Maplibre community maintaining the open-source fork of that project at maplibre-native.

Project details


Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

pymgl-0.5.0-cp313-cp313-manylinux_2_28_x86_64.whl (23.6 MB view details)

Uploaded CPython 3.13 manylinux: glibc 2.28+ x86-64

pymgl-0.5.0-cp313-cp313-manylinux_2_28_aarch64.whl (23.0 MB view details)

Uploaded CPython 3.13 manylinux: glibc 2.28+ ARM64

pymgl-0.5.0-cp313-cp313-macosx_12_0_arm64.whl (2.7 MB view details)

Uploaded CPython 3.13 macOS 12.0+ ARM64

pymgl-0.5.0-cp312-cp312-manylinux_2_28_x86_64.whl (23.6 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.28+ x86-64

pymgl-0.5.0-cp312-cp312-manylinux_2_28_aarch64.whl (23.0 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.28+ ARM64

pymgl-0.5.0-cp312-cp312-macosx_14_0_arm64.whl (2.7 MB view details)

Uploaded CPython 3.12 macOS 14.0+ ARM64

pymgl-0.5.0-cp311-cp311-manylinux_2_28_x86_64.whl (23.6 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.28+ x86-64

pymgl-0.5.0-cp311-cp311-manylinux_2_28_aarch64.whl (23.0 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.28+ ARM64

pymgl-0.5.0-cp311-cp311-macosx_14_0_arm64.whl (2.7 MB view details)

Uploaded CPython 3.11 macOS 14.0+ ARM64

pymgl-0.5.0-cp310-cp310-manylinux_2_28_x86_64.whl (23.6 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.28+ x86-64

pymgl-0.5.0-cp310-cp310-manylinux_2_28_aarch64.whl (23.0 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.28+ ARM64

pymgl-0.5.0-cp310-cp310-macosx_12_0_universal2.whl (2.7 MB view details)

Uploaded CPython 3.10 macOS 12.0+ universal2 (ARM64, x86-64)

pymgl-0.5.0-cp39-cp39-manylinux_2_28_x86_64.whl (23.6 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.28+ x86-64

pymgl-0.5.0-cp39-cp39-manylinux_2_28_aarch64.whl (23.0 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.28+ ARM64

pymgl-0.5.0-cp39-cp39-macosx_12_0_universal2.whl (2.7 MB view details)

Uploaded CPython 3.9 macOS 12.0+ universal2 (ARM64, x86-64)

File details

Details for the file pymgl-0.5.0-cp313-cp313-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e47d826f6d534e6b50ab54a30afa709aaa9c61c2e36eff8f51ef5bc39f139b47
MD5 57861899505f6eca809e1c75615aa217
BLAKE2b-256 d82077638a79c127f4814952166ba4bee358b5c1b0cc37ede30d24a40bbe3875

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp313-cp313-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp313-cp313-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 6c283d971691ff25cf9913f101db88747d551455fb081d86ec3e39c31106d0ee
MD5 fb308a984bd824cdf428c3d788b35301
BLAKE2b-256 81796e42d22fa090e0f6b245a36f38c751a1739a96011ca6ff7cbcc915b8e06a

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp313-cp313-macosx_12_0_arm64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp313-cp313-macosx_12_0_arm64.whl
Algorithm Hash digest
SHA256 c6820c1eb479af2e25e73c7c4f1259379a29f845fa8d7d266ca5a640de16fb08
MD5 2b35a879c162738a8b7f7fa38189dc18
BLAKE2b-256 61df11f2697cbd7b62df5f7c28071be767fc65ec0ccd8e44fb32c0717cd7fc91

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 79cd0bd0011a040adfd8f7fe259f88e4a5b897133525a16c22bc67721f11aded
MD5 96d089ea548fdff61beceb1d8d5585ee
BLAKE2b-256 3a166271dff48faa53933b6c7f79c4a4ae73c1eb0b1c26f94b93ab53d566b370

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp312-cp312-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp312-cp312-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 0bb0ab32abf5b96e16683a19b972958faf868fabc1ff8b537ec9a166ccd19c6c
MD5 98f73efbdfde02d392ceb1c18da9cba4
BLAKE2b-256 a449ae076c89104e31af01f874cdcc9306065a6404d069ba8f7ad207a66bf731

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp312-cp312-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 bbbd5a38b8616df88fb2715f4662c1e503fa51118c3caead32a6b7db61b562c1
MD5 5f6dec28a21204c0a5fba9196b81bb95
BLAKE2b-256 2c7914d055013613d53367616697ad9481f96d15a86907c0f2b96e32cd47f8e0

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 49a69cfd0b4f73bd26a5ca3266a271642e72d35cac36ebb9c71aa613abb83057
MD5 3f48419a0e217730f50f5b88191b2087
BLAKE2b-256 1f17c847929395e916070ce7b2c521759301019f15881351fb765e8deb7c6ff1

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp311-cp311-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp311-cp311-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 7bbc9a0975c5df58fc12c3be9843e7c16fe7e2434d7636a49355d14472967883
MD5 af4bcb5612e7a6a5eec8f4b399dece6e
BLAKE2b-256 d3d0c9314a319f82f78ea256ea17cf9e6b66bd6676336da0147db5c0b94f29bf

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp311-cp311-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 a62704eb4b0111a72707cbbf977dd1bde917d5456c3176bf3e66fa8e74962110
MD5 adc1305b6e462d43e09565fceb0668e7
BLAKE2b-256 696f4368520d3701956ea7052ec671df41fe7e2221e0ca824a60294b6a22281b

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp310-cp310-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp310-cp310-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a0e90da9d45aaa2816e7f838507ccb16a9e12dd7c0765e6c565e030d8f1e66e1
MD5 cbfce93e57d7e2a810c11092e10a24dd
BLAKE2b-256 4f8bc61be5c0d3e1f5be7374c8aca6926beb0e6aade824259effd19f5b0ed10a

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp310-cp310-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp310-cp310-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 46fa4dfae2baf7d3acf23a060c059562d947cabd908cdd4bfdd11558f3cad4ee
MD5 b86d470472b574c8e0b5cb37c6930234
BLAKE2b-256 939e6c291c12219067f3abb6ca90141d5254ca8d6cd7f0304b6db41950d4468d

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp310-cp310-macosx_12_0_universal2.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp310-cp310-macosx_12_0_universal2.whl
Algorithm Hash digest
SHA256 57a146031d795fe198e402191061e5c23be2ae8590c4d8e5689a4f02b9e7ac24
MD5 69b10488d94b837c40b86e093b72e395
BLAKE2b-256 4aa086710829084b697c010858a2c6330ccff45dd4c1416875479153242c366f

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp39-cp39-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp39-cp39-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ab29884fd0ae3d4a33859dc1effeaa77e7cd60bdcc1173e8e65054a77e65ba90
MD5 45583775e80d7776f73545116b14c9fb
BLAKE2b-256 f32ff0729d1fc3f01ec85577170d09908116c7ef108b79e89fe80af1a81a67be

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp39-cp39-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp39-cp39-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 06eafb82fcf04565477332edd086a9615091d006cd0a78b726e83501ed18277c
MD5 f8575afe7e7862d42bbe05833a64f187
BLAKE2b-256 ccbaa0788be4555ef93bc89a154b5f452bfc314d236179b409b73d57a93ea997

See more details on using hashes here.

File details

Details for the file pymgl-0.5.0-cp39-cp39-macosx_12_0_universal2.whl.

File metadata

File hashes

Hashes for pymgl-0.5.0-cp39-cp39-macosx_12_0_universal2.whl
Algorithm Hash digest
SHA256 9e2c66824201062bfde1ceee82e789819f283a3c821a2e574bf2e9af3602faa6
MD5 5e9b5713a0d37f7ae0be0fd7b4b6e769
BLAKE2b-256 58315fe6aa2d1e40b72e0879730b9b77c2ecae0e704842f3ec28bd36646baf22

See more details on using hashes here.

Supported by

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