* feat(python): expand Map API to cover more Add Data types (#246) Add leafmap-style methods so data can be added programmatically for the URL/cloud-native formats the Add Data menu supports, beyond the existing GeoJSON/XYZ/COG: - Web services: add_wms, add_wmts, add_wfs - Vector files (URL via the in-browser vector control, or local file read with GeoPandas and inlined): add_vector, add_geoparquet, add_flatgeobuf, add_shp - Tiled/cloud formats: add_vector_tiles, add_pmtiles - 3D / media: add_3d_tiles, add_video - add_raster (alias of add_cog) Each method builds a project-layer record that mirrors the TypeScript store-layer shapes, so the embedded app reconstructs the layer via the same restore paths (restoreVectorLayers / restoreRasterLayers / restoreThreeDTilesLayers) and core layer sync it uses for UI-added layers. Local vector files are inlined via the optional GeoPandas dependency since the browser cannot read a kernel-side file; remote URLs need no extras. Adds builder + Map-method tests and documents the new methods. * Address Claude and CodeRabbit review feedback - video_layer: add `source.type:"video"`, `metadata.sourceKind:"video-url"` and the corner bbox `metadata.bounds` so "Zoom to layer"/fitLayer works, matching the UI-created video layer (Claude). - video_layer: reject non-https:// URLs up front (browser media-src CSP is https-only), surfacing the problem at the call site instead of a silent blank render (Claude). - add_wfs: persist the WFS protocol fields (service/typeName/version/ outputFormat/srsName) on the source for round-trip editing and isWfsLayer(), and record metadata.featureCount like the UI does (Claude). - _read_local_vector: dispatch (Geo)Parquet to geopandas.read_parquet (the GDAL Parquet driver may be absent) and cap the converted GeoJSON at the same 50 MB limit as load_featurecollection (Claude). - tests: assert the new video/WFS fields, add a non-https video rejection test, and narrow the new pytest.raises(ValueError) assertions with match= (CodeRabbit). * Address Claude and CodeRabbit review feedback (round 2) - pmtiles_layer: populate metadata.nativeLayerIds (raster -> [`<id>-raster`], vector -> [`<id>`]) — isExternalNativeLayer() gates on a non-empty list and "pmtiles" has no fallback dispatch in syncLayer, so an empty list meant PMTiles layers never rendered (Claude, real bug). - _read_local_vector: accept a data_format hint that overrides suffix detection, and thread it from add_vector, so a GeoParquet under a non-standard name still uses geopandas.read_parquet (CodeRabbit). - video_layer: reject non-string / empty URL entries instead of silently dropping them (Claude). - add_vector: warn when render_mode / source_layer are passed for a local file, since they only apply to remote URLs (Claude). - vector_layer: document why metadata.nativeLayerIds is intentionally empty and the render_mode="tiles" fall-through caveat (Claude). - tests: assert pmtiles nativeLayerIds, non-string video URL rejection, the data_format threading, and the local-file ignored-kwargs warning. * Address Claude review feedback (round 3) - add_wfs: default max_features to 1000 (matching the UI) since the response is inlined; document that None requests every feature (Claude nit). - wmts_layer: clarify the docstring that the template is WMTS REST {z}/{y}/{x} (row before column), unlike XYZ {z}/{x}/{y}, to prevent users migrating from add_tile_layer from swapping the order (Claude nit). * Address Claude review feedback (round 4) - _append_query: split off any URL "#fragment" before appending the query string and re-attach it, so a service endpoint carrying a fragment no longer produces "…#frag?SERVICE=…" where the params are never sent (Claude). - add_vector: warn for ignored render_mode/data_format/source_layer on the __geo_interface__ path too, consistent with the local-file path (Claude). - vector_tiles_layer: warn when both source_layers and source_layer are given (source_layer is dropped), instead of silently ignoring it (Claude). - tests: cover the fragment handling and both new warnings.
8.6 KiB
Python package (Jupyter)
GeoLibre ships a Python package, geolibre, that embeds the full GeoLibre
app inside a Jupyter notebook cell as an anywidget,
with a leafmap-style API.
The widget loads the complete GeoLibre app (menus, panels, processing tools) in
an iframe. State syncs both ways through a single .geolibre.json project, so
data you add from Python appears in the UI, and edits you make in the UI
(panning, zooming, adding layers) are readable back from Python.
Install
pip install geolibre
Or with conda from conda-forge:
conda install -c conda-forge geolibre
Optional extras for add_geojson() from a GeoDataFrame and for reading local
vector files with add_vector() / add_geoparquet() / add_flatgeobuf() /
add_shp() (remote URLs for those formats need no extras):
pip install "geolibre[all]" # adds GeoPandas and Shapely
The optional [all] extra is pip-only. If you installed via conda, add it with
pip install "geolibre[all]" inside the same environment.
Quickstart
from geolibre import Map
m = Map(center=(-100, 40), zoom=4)
m.add_geojson("https://example.com/data.geojson", name="Data")
m
The full GeoLibre UI renders in the cell. Add more data and drive the view:
m.add_tile_layer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
name="OpenStreetMap",
attribution="(c) OpenStreetMap contributors",
)
m.add_cog("https://example.com/dem.tif", name="DEM", colormap="terrain")
m.add_basemap("dark")
m.set_center(-120, 47, zoom=8)
Two-way sync
Because the project syncs both ways, you can pan or zoom the map in the UI and then read the live state back from Python:
proj = m.to_project()
proj["mapView"]["center"] # reflects the live UI view
[layer["name"] for layer in proj["layers"]]
Save and reload projects, fully interchangeable with the desktop and web apps:
m.save_project("my-map.geolibre.json")
m2 = Map()
m2.load_project("my-map.geolibre.json")
m2
Map options
Map(
center=(-100, 40), # [lng, lat]
zoom=4,
basemap="dark", # a basemap name or a MapLibre style URL
height="800px",
layout="embed", # "embed" (compact UI), "full" (desktop UI), or "maponly"
theme="light", # "light" or "dark"
)
API reference
| Method | Description |
|---|---|
Map(center, zoom, basemap=, height=, layout=, theme=) |
Create a map. |
add_geojson(data, name=, **style) |
Add GeoJSON from a dict, file path, URL, JSON string, or GeoDataFrame. |
add_vector(data, name=, render_mode=, data_format=, source_layer=, **style) |
Add a vector dataset from a URL (GeoParquet, FlatGeobuf, zipped Shapefile, GeoJSON, …) or a local file (read via GeoPandas and inlined). |
add_geoparquet(data, name=, **style) |
Add a GeoParquet dataset (URL or local file). |
add_flatgeobuf(data, name=, **style) |
Add a FlatGeobuf dataset (URL or local file). |
add_shp(data, name=, **style) |
Add a Shapefile (zipped URL or local .shp). |
add_vector_tiles(url, name=, source_layers=, source_layer=, **style) |
Add a vector tile layer from a TileJSON endpoint. |
add_pmtiles(url, name=, tile_type=, source_layers=, **style) |
Add a PMTiles archive (vector or raster). |
add_tile_layer(url, name=, tile_size=, attribution=) |
Add a raster XYZ tile layer. |
add_wms(endpoint, layers, name=, styles=, image_format=, transparent=, tile_size=, **style) |
Add a WMS layer (GetMap, tiled raster). |
add_wmts(url, name=, tile_size=, **style) |
Add a WMTS layer from a tile URL template. |
add_wfs(endpoint, type_name, name=, version=, output_format=, srs_name=, max_features=, **style) |
Add a WFS layer (GetFeature GeoJSON, fetched and inlined). |
add_cog(url, name=, bands=, colormap=, rescale=) |
Add a Cloud Optimized GeoTIFF. |
add_raster(url, name=, bands=, colormap=, rescale=) |
Add a raster (COG/GeoTIFF); alias of add_cog. |
add_3d_tiles(url, name=, altitude_offset=, request_headers=, **style) |
Add a 3D Tiles tileset.json. |
add_video(urls, coordinates, name=, **style) |
Add a georeferenced video (four [lng, lat] corners). |
add_basemap(basemap) |
Set the background basemap. |
set_center(lng, lat, zoom=None) |
Center (and optionally zoom) the map. |
set_center_zoom(lng, lat, zoom=None) |
Alias of set_center (leafmap compatibility). |
remove_layer(layer_id) / clear_layers() |
Remove layers. |
to_project() |
Return the current project as a dict. |
load_project(src) |
Replace the project from a dict, JSON string, or .geolibre.json path. |
save_project(path) |
Write the current project to a .geolibre.json file. |
Style keyword arguments (for example fillColor, strokeColor, strokeWidth,
circleRadius) map to the GeoLibre layer style fields.
How it works
The wheel bundles the GeoLibre web build. At import time the package starts a
small localhost static server that serves the bundled app; the widget renders
that app in an iframe and exchanges the project over window.postMessage.
Adding data from Python rewrites the synced project and pushes it into the app;
UI edits flow back the same way.
!!! note "Environment support"
The interactive widget works in **local Jupyter, VS Code, Google Colab, and
JupyterHub / remote servers**:
- **Local Jupyter / VS Code** - the app is served directly from localhost.
- **Google Colab** - routes through Colab's built-in port proxy
(`google.colab.kernel.proxyPort`) automatically.
- **JupyterHub** (including managed/shared hubs, detected at runtime via
`JUPYTERHUB_SERVICE_PREFIX`) - the front-end probes two same-origin routes
and uses whichever is live, so a host needs only **one** of them:
- the Jupyter Server extension bundled with `geolibre`, mounted at
`{base_url}geolibre/app/` on the notebook server's own origin. It is
enabled automatically on `pip install geolibre` and needs no
`jupyter-server-proxy` and no extra port, so it works on locked-down
hubs that block raw-port proxying -- but it only registers after the
Jupyter server restarts, since it loads from a startup config drop-in.
- `jupyter-server-proxy` at `{base_url}proxy/{port}/`, which reaches the
kernel's localhost bundle in the **running** server with no restart,
wherever `jupyter-server-proxy` is installed.
- **Other remote servers** (Binder, remote JupyterLab over SSH/network) -
pass `Map(server_proxy=True)` to use that same dual-route remote path.
Set `Map(server_proxy=False)` to force the direct localhost path. If the app
fails to load on a hub, either install `jupyter-server-proxy`, or confirm the
extension is enabled with `jupyter server extension list` (look for
`geolibre`; run `jupyter server extension enable geolibre` if absent) and
**restart** the Jupyter server so the extension loads.
!!! warning "URL fetching"
`add_geojson(url)` fetches the URL from the **kernel**, following redirects,
so a notebook can reach any host the kernel can (including private and
link-local addresses such as cloud metadata endpoints). This is intended for
single-user local notebooks, where you already control the kernel. Private
and localhost URLs are intentionally allowed so you can load from a local
tile server. Do not load untrusted `.geolibre.json` projects or URLs on a
shared/multi-tenant kernel.
Building from source
The package lives in python/.
The bundled app is produced from the monorepo with:
npm run build:embed # builds the app and stages it into the wheel
python -m build # builds the wheel
python -m twine upload dist/* # upload to PyPI
pip install -e python # editable install for development
Changes to the Python code are picked up on kernel restart. Changes to the app
(TypeScript) require re-running npm run build:embed and restarting the kernel.