Package: pymgl
PyMGL: Maplibre GL Native Static Renderer for PythonThis package provides an interface tomapblibre-gl-nativeto render Mapbox GL
styles to PNG images.WARNING: this package is under active development and the API may change without notice.GoalsThis package is intended to provide a lightweight interface tomaplibre-gl-nativefor rendering Mapbox GL 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 withmaplibre-gl-native;
it does not provide higher-level functionality such as a web server or a CLI.For a stand-alone service implmenting rendering functionality, seembgl-renderer(implemented in NodeJS).InstallSupported operating systemsMacOS 10.15+ (x86_64 only)Wheels are available on PyPI:pipinstallpymglTo verify that it installed correctly, run the included test suite:python-mpipinstallpytestPillownumpypixelmatchpython-dotenv
python-mpytest--pyargspymgl-vUbuntu 22.04 & 20.04Due to the complexity of building manylinux wheels that include OpenGL and
successfully compilemaplibre-gl-native, wheels are only available for
Ubuntu 22.04 and 20.04.Wheels are available on the release page in Github. Download and install from there.Unfortunately, Python wheel names are very restrictive, so we have added.ubuntu-22.04and.ubuntu-20.04suffixes to the wheel names, which have to
be stripped off before you can install them.Something like this for Ubuntu 22.04:wgethttps://github.com/brendan-ward/pymgl/releases/download/<release>/pymgl-<release>-<Pythonversion>-<Pythonversion>-linux_x86_64.whl.ubuntu-22.04# rename file to remove .ubuntu-22.04 suffixfind.-typef-name"*.whl.ubuntu-22.04"-print0-execbash-c'mv "${0}" "${0//.ubuntu-22.04/}"'{}\;python3-mpipinstall--find-links.pymglYou also need to install the following runtime dependencies:Ubuntu 22.04:apt-getinstalllibicu70\libcurl4\libjpeg-turbo8\libpng16-16\libprotobuf23\libuv1\libx11-6\libegl1\libopengl0\xvfbUbuntu 20.04:apt-getinstalllibicu66\libcurl4\libjpeg-turbo8\libpng16-16\libprotobuf17\libuv1\libx11-6\libegl1\libopengl0\xvfbYou must have Xvfb running in order to successfully usepymgl. You can
setup and run Xvfb manually, or wrap calls to python inXvfb-run.To verify that it installed correctly, run the included test suite:python-mpipinstallpytestPillownumpypixelmatchpython-dotenv
xvfb-run-a--server-args="-screen 0 1024x768x24 -ac +render -noreset"\python-mpytest--pyargspymgl-vWindowsWindows is not and will not be supported.UsageTo create a map object, you must always provide a Mapbox GL style JSON string or
URL to a well-known style hosted by Mapbox or Maptiler:frompymglimportMapstyle="""{"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 thestylessection for more information about map styles.Other than style, all other parameters are optional with default values.NOTE:styleandratiocannot 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=<mapboxtoken>,provider="mapbox")Valid providers aremapbox,maptiler, andmaplibre.Map propertiesYou 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 degreesmap.setPitch(pitch)# map pitch in degreesmap.setFilter(layerId,filterJSONorNone)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.zoommap.bearingmap.pitchYou 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 Nonemap.getPaintProperty(<layerId>,<property>)# returns JSON value or Nonemap.getLayerJSON(<layerId>)# returns JSON describing layerNOTE: paint properties may be decoded to their internal representation. For
example, a CSS color string#FF0000will 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=<mapboxtoken>,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.fromurllib.requestimporturlopenurl=f"https://api.mapbox.com/styles/v1/mapbox/streets-v11?access_token={MAPBOX_TOKEN}"withurlopen(url)asr:style=r.read()map=Map(style.decode("UTF-8")token=<mapboxtoken>,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 theSDF image docsfor more information about using SDF
images.RenderingYou can render the map to PNG bytes:img_bytes=map.renderPNG()This returnsbytescontaining the RGBA PNG data.You can render the map to a raw buffer as a numpy array (uint8dtype):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 asPilloworpyvipsto combine with other image
operations.Map instancesWARNING: 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>)delmap# must manually delete BEFORE creating a new instance assigned to thismap=Map(<style>,<width>,<height>)For this reason, you should consider using a context manager:withMap(<style>,<width>,<height>)asmap: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()StylesPyMGL should support basic styles as of Mapbox GL JS 1.13.Remote tilesets, sources, and assetsRemote 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 mbtilesLocal MBTiles are supported, but must be provided using an absolute path to the
mbtiles file as the sourceurlof a tileset; it must resolve to an actual file.Local MBTiles are denoted with ambtiles://URI prefix.Example:{"sources":{"source_id":{"url":"mbtiles:///<pymgl_root_dir>/tests/fixtures/geography-class-png.mbtiles",...}},"layers":[...],...}Local filesGeoJSON 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 thetileskey of a source is NOT currently supported by Maplibre GL Native;
attempting to do so will fail.ImagesYou must register the image with the map instance before rendering the map. Seemap.addImage()above.{"sources":{...},"layers":[{...,"paint":{"fill-pattern":"pattern"}},]}You can use map images as fill patterns or icon images.Adding sources and layers after constructionYou can add sources and layers dynamically after constructing the map instance:importjsonmap=Map("")# construct with empty stylemap.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 stateYou can get, set, and remove feature state after the map has been loaded.map=Map(<stylewithsource"exampleSource"andlayer"exampleLayer">,...)map.load()map.getFeatureState("exampleSource","exampleLayer","0")# returns Nonemap.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 NoneNOTE: features must already have a unique, numeric ID set on each feature. There
is currently no support forpromoteIdlike 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 keyUnsupported featuresPyMGL does not support alternative projections or 3D terrain.DevelopingDependencies:MacOS:Developing on MacOS requires the following binary libraries to be installed
viahomebrew:cmakeninjaDeveloping on Ubuntu 22.04 or 20.04 requires the following binary libraries:cmakeninja-buildbuild-essentiallibcurl4-openssl-devlibicu-devlibpng-devlibprotobuf-devlibturbojpeg0-devlibx11-devlibegl-devlibopengl-devxvfbTo run on Linux, XVFB must also be running; otherwise the process will segfault.Seedocker/README.mdfor more information.Developing on other Linux versionsSee.github/workflows/build_wheels.ymlfor the overall structure for building
and testing on Linux. You will most likely need to adapt the names of the
packages to the package manager for your version of Linux.Debian BullseyeWe've heard (#7) that it is possible to successfully build and operate PyMGL
on Debian Bullseye with the following dependencies:Build:apt-get-yinstall\curl\build-essential\cmake\ccache\ninja-build\pkg-config\libcurl4-openssl-dev\libicu-dev\libturbojpeg0-dev\libpng-dev\libprotobuf-dev\libuv1-dev\libx11-dev\libegl-dev\libopengl-dev\xvfb\libjpeg-dev\libsqlite3-dev\libopengl0\gitRuntime:aptinstall--no-install-recommends-y\xvfb\xauth\curl\libicu67\libjpeg-turbo-progs\libpng16-16\libprotobuf23\libuv1\libx11-6\libegl1\libopengl0nanobindnanonbindis used to provide bindings for Python against a C++ class that wrapsmaplibre-gl-nativefor easier rendering operations.It is included here as a git submodule, per theinstallation instructions.gitsubmoduleaddhttps://github.com/wjakob/nanobindvendor/nanobindcdvendor/nanobind
gitsubmoduleupdate--init--recursiveMaplibre-gl-nativeMaplibre-gl-native is included as a git submodule, and it includes many submodules
of its own.gitsubmoduleadd-bmasterhttps://github.com/maplibre/maplibre-gl-nativevendor/maplibre-gl-nativeGit submodulesRungitsubmoduleupdate--initWe only need some of the submodules under maplibre-gl-native. In particular,
we do not needmaplibre-gl-jsor Android / IOS dependencies.Run the following:cdvendor/maplibre-gl-native

gitsubmoduleupdate--init--recursive\vendor/boost\vendor/cpp-httplib\vendor/earcut.hpp\vendor/eternal\vendor/googletest\vendor/polylabel\vendor/protozero\vendor/mapbox-base\vendor/unique_resource\vendor/vector-tile\vendor/wagyu\vendor/zip-archiveTo later updatemaplibre-gl-native:cdvendor/maplibre-gl-native
gitcheckoutmain
gitpullorigincd../..
gitcommit-am"update maplibre-gl-native"tolatestArchitectureThis package is composed of 2 main parts:wrapper around Maplibre GL native classes to make constructing and managing
properties of the map easierPython bindings created using nanobind against that wrapperThe wrapper is located insrc/map.cpp.BuildC++ testsSeetests/READMEfor more information.Building using CMake directly is useful when building the C++ level tests of the
Maplibre GL wrapper.mkdirbuildcdbuild

cmake..-GNinja-DCMAKE_BUILD_TYPE=Debug

ctest-VBuild Python extensionThe Pythonsetup.pyscript manages building the library and extension using
CMake.From project root directory:pythonsetup.pybuild_ext--inplaceDocstrings / type informationDocstrings are maintained in bothsrc/_pymgl.cppandpymgl/__init__.pyi.Python-friendly type annotations are maintained inpymgl/__init__.pyi.Note:pymgl/__init__.pyiis necessary to support autocompletion and tooltips
in VSCode.See alsombgl-rendererprovides a NodeJS API, CLI, and server based on the NodeJS bindings to Mapbox GL Native.CreditsThis project was developed with the support of theU.S. Fish and Wildlife ServiceSoutheast Conservation Adaptation Strategyfor
use in theSoutheast Conservation Blueprint ViewerandSouth Atlantic Conservation Blueprint Simple Viewer.This project is made possible because of themapbox-gl-nativeproject byMapboxby the efforts of the Maplibre community maintaining the open-source fork of that
project atmaplibre-gl-native.