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 provide GeoPandas/Shapely support for GeoDataFrames and local vector files, plus xarray/rioxarray/rasterio/rio-tiler support for in-memory rasters:
pip install "geolibre[all]" # both
pip install "geolibre[vector]" # GeoPandas/Shapely only
pip install "geolibre[raster]" # xarray/rioxarray/rasterio/rio-tiler only
[all] is the union of the two, so it now installs rasterio (and GDAL with it);
use [vector] to keep an existing vector-only environment as light as before.
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)
Google Earth Engine layers are optional and need pip install earthengine-api
plus credentials (ee.Authenticate() once, then a Google Cloud project):
import ee
ee.Authenticate() # once per machine
ee.Initialize(project="your-google-cloud-project")
m.add_ee_layer(ee.Image("USGS/SRTMGL1_003"), {"min": 0, "max": 3000}, name="SRTM")
add_ee_layer evaluates the Earth Engine object in the kernel and adds the
resulting tile URL as a raster layer (ImageCollections are mosaicked, vector
objects are styled into raster tiles — for those, vis_params takes
ee.FeatureCollection.style() keys such as color, fillColor, width, and
pointSize, not image keys). That URL is tied to an Earth Engine map id that
expires, so a saved project may need the Earth Engine layer regenerated when it
is reopened. The result is a plain raster tile layer, not one of the live layers
the app's own Earth Engine panel manages.
add_raster / add_cog also accept a local GeoTIFF path on the kernel host:
the file is served by the bundled localhost server so the app can read it. This
works directly in local Jupyter and VS Code. In Google Colab, where the kernel
proxy does not preserve the byte-range semantics required by browser COG
rendering, GeoLibre renders local rasters as PNG XYZ tiles in the kernel instead.
JupyterHub can route the COG through the kernel port when
jupyter-server-proxy is available. A static-server-extension-only deployment
cannot expose kernel files, so pass a hosted URL there. The served URL is
session-scoped, so a project saved with a local raster will not restore it when
reopened later — pass a hosted URL for durable projects.
Install geolibre[raster] to visualize an in-memory xarray object. Spatial
dimensions named lon/lat or longitude/latitude default to EPSG:4326;
otherwise provide CRS and dimension names explicitly. Dataset variables become
bands unless one is selected:
m.add_raster(data_array, name="Temperature", colormap="viridis")
m.add_raster(
dataset,
name="Temperature",
array_args={"variable": "temperature", "isel": {"time": 0}},
)
The temporary Cloud-Optimized GeoTIFF backing an xarray layer is removed when the widget is
closed, so xarray layers have the same session-only limitation as local files.
Locally the browser reads that COG directly; in Colab rio-tiler renders it into
ordinary PNG XYZ tiles to avoid Colab's incompatible byte-range proxy behavior.
Call m.close() when you are done to remove it promptly; otherwise it is removed
when the Map is garbage collected or when the kernel exits normally. A kernel
that is killed outright leaves the file behind in the system temp directory.
Add markers and data-driven symbology without precomputing styles:
m.add_marker(-122.4, 37.8, properties={"name": "San Francisco"})
m.add_marker_cluster([(-122.4, 37.8), (-122.3, 37.9), (-122.5, 37.7)])
m.add_heatmap([(-122.4, 37.8), (-122.3, 37.9)], radius=35)
m.add_csv("cities.csv", x="longitude", y="latitude", name="Cities")
m.add_choropleth(
"https://example.com/counties.geojson",
column="population",
colormap="blues",
scheme="quantile",
)
Add a legend, a colorbar, and a swipe (split-map) comparison:
# A built-in land-cover legend, or your own {label: color} dict.
m.add_legend(builtin="nlcd")
m.add_legend(legend_dict={"Water": "#0000ff", "Land": "#00ff00"})
# A colorbar for a continuous raster.
m.add_colorbar(colormap="terrain", vmin=0, vmax=4000, label="Elevation", units="m")
# Compare two layers (or a layer against the basemap) with a swipe slider.
before = m.add_cog("https://example.com/before.tif", name="Before")
after = m.add_cog("https://example.com/after.tif", name="After")
m.split_map(before, after)
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
to_project(), save_project(), and to_html() redact credentials — API keys,
tokens, authenticated request headers, environment variables, geocoder keys, and
credential URL parameters — so anything you serialize, commit, or share is safe
by default. Pass keep_credentials=True to to_project() or save_project()
for a trusted local file that must keep working without re-entering them:
m.save_project("private.geolibre.json", keep_credentials=True)
The project trait itself is unredacted, so live two-way sync with the widget
keeps authenticated layers rendering.
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"
)
Interactive scripting¶
Beyond adding data, the widget can query the live app and react to it — the
same surface as the in-app Python Console. These
calls round-trip to the running map, so the map must be displayed first (show
m in a cell), then run the queries in a later cell.
m.get_center() # live [lng, lat], reflecting UI pans/zooms
m.get_bounds() # [west, south, east, north]
m.fly_to(-122.4, 37.8, zoom=10)
m.identify(-122.4, 37.8) # features at a point, like clicking the map
# Layer objects: read and mutate layers, read their features
for layer in m.layers:
layer.opacity = 0.6
features = m.layers[0].get_features() # list of GeoJSON Feature objects
# Run a processing algorithm; result layers are added to the map
m.list_algorithms()
m.run_algorithm("buffer", {"layer": layer_id, "distance": 1000})
# Read back what the user selected or drew on the map
m.get_selected_features() # the clicked feature(s) as Feature objects
m.get_drawn_features() # features sketched with the Geo Editor
m.user_rois # the drawn ROIs as a GeoJSON FeatureCollection
m.get_drawn_features(as_gdf=True) # the same, as a GeoDataFrame (needs GeoPandas)
png = m.to_image() # PNG bytes (or m.to_image("map.png"))
m.to_html("map.html") # standalone HTML embedding the live project
React to user interaction with event callbacks:
m.on_click(lambda e: print("clicked", e["lngLat"]))
m.on_selection_change(lambda e: print("selected", e))
m.on_layer_change(lambda e: print("layers", e["layerIds"]))
Blocking queries
Interactive queries block the kernel until the app replies (via
jupyter_ui_poll, installed automatically). Pass timeout= for slow calls,
e.g. m.run_algorithm(..., timeout=300).
API reference¶
Interactive queries, events & processing¶
| Method | Description |
|---|---|
get_view() / get_center() / get_bounds() |
Read the live camera / center / viewport bounds. |
fly_to(lng, lat, zoom=, bearing=, pitch=, duration=) |
Animate the camera. |
fit_bounds([w, s, e, n]) |
Fit the camera to a bounding box. |
zoom_to_bounds([w, s, e, n]) / zoom_to_layer(layer) |
Leafmap-style view helpers; layers may be addressed by id, name, or handle. |
identify(lng, lat, layer_id=None) |
Query rendered features at a point. |
get_features(layer_id) |
A layer's features as Feature objects. |
get_selected_features(as_gdf=False) |
The feature(s) selected in the app, as Feature objects (or a GeoDataFrame). |
get_drawn_features(as_gdf=False) / user_rois |
Features drawn with the Geo Editor; user_rois returns them as a FeatureCollection. |
layers / get_layer(id) |
Layer handles (read state; set name/visible/opacity, set_style, get_features, zoom_to, remove). |
layer_names / find_layer(name) / find_layer_index(name) |
Inspect layers by display name. |
set_layer_visibility(layer, visible) / set_layer_opacity(layer, opacity) |
Change a layer by id, name, or handle. |
list_algorithms() |
Available processing algorithms (id, parameters, …). |
run_algorithm(id, parameters=None, timeout=) |
Run an algorithm; returns {logs, resultLayerIds}. |
to_image(path=None, timeout=) |
Capture the map as PNG bytes, or write to path. |
to_html(path=None, title=, width=, height=, app_url=) |
Export a standalone HTML page that embeds the current project (credentials redacted); returns the HTML or writes to path. |
on(event, cb) / on_click / on_selection_change / on_layer_change |
Register event callbacks; returns an unsubscribe function. |
request(method, params=None, timeout=) |
Low-level command primitive behind the methods above. |
Data, view & projects¶
| 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_gdf(gdf, name=, column=None, **style) |
Add a GeoDataFrame, optionally as a choropleth. |
add_csv(data, x="longitude", y="latitude", name=, **style) / add_xy_data(...) |
Add points from a CSV path, URL, text, DataFrame, or row mappings. |
add_marker(lng, lat, name=, properties=, color=, opacity=, radius=, stroke_color=, stroke_width=, shape=, size=, icon=, **style) |
Add a single point marker (properties appear on click). |
add_markers(points, name=, color=, opacity=, radius=, stroke_color=, stroke_width=, shape=, size=, icon=, **style) |
Add point markers from (lng, lat) pairs, {lng/lon/x, lat/y, …} dicts, GeoJSON, or a GeoDataFrame. |
add_circle_markers(points, name=, radius=, **style) |
Add circle markers with an explicit radius. |
add_marker_cluster(points, name=, cluster_radius=, cluster_max_zoom=, **style) |
Add clustered point markers. |
add_heatmap(points, name=, radius=, intensity=, color_ramp=, weight_field=, **style) |
Add point data using the density heatmap renderer, optionally weighted by a numeric field. |
add_choropleth(data, column, name=, class_count=, colormap=, scheme=, **style) |
Add a GeoJSON layer with graduated symbology computed from a numeric column. |
add_data(data, column=None, name=, **kwargs) |
Add data; a choropleth when column is given, else a plain GeoJSON layer (leafmap parity). |
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_kml(data, name=, **style) / add_gpkg(data, name=, layer=None, **style) |
Add KML/KMZ or GeoPackage data. |
add_polyline(polyline, name="Polyline", precision=5, **style) |
Add an Encoded Polyline layer from a string or list of strings (precision 5 or 6). |
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_ee_layer(ee_object, vis_params=, name=, shown=, opacity=) |
Add an authenticated Google Earth Engine object as raster tiles (needs earthengine-api). |
add_wms(endpoint, layers, name=, styles=, image_format=, transparent=, tile_size=, version=, bounds=, **style) |
Add a WMS layer (GetMap, tiled raster). bounds is [west, south, east, north], needed for zoom-to-layer. |
add_wmts(url, name=, tile_size=, bounds=, **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=, **style) |
Add a Cloud Optimized GeoTIFF (URL or a kernel-side local GeoTIFF path). |
add_raster(source, name=, bands=, colormap=, rescale=, array_args=, **style) |
Add a COG/GeoTIFF URL or path, or an xarray DataArray/Dataset (xarray needs geolibre[raster]). |
add_3d_tiles(url=None, name=, ion_asset_id=, altitude_offset=, request_headers=, **style) |
Add a 3D Tiles tileset.json URL, or a Cesium Ion tileset by asset id (3D globe only). |
add_cesium_ion(asset_id, name=, kind="3d-tiles", altitude_offset=, **style) |
Add a Cesium Ion asset by id: a 3D Tiles tileset or (kind="imagery") an imagery layer. Renders on the 3D globe, with the app's Ion token. |
add_video(urls, coordinates, name=, **style) |
Add a georeferenced video (four [lng, lat] corners). |
add_basemap(basemap) |
Set the background basemap. |
split_map(left_layers=None, right_layers=None, orientation=, position=, control_position=) |
Add a swipe (split-map) comparison slider between two layer sets. |
add_legend(title=None, legend_dict=, labels=, colors=, builtin=, position=, shape=) |
Add a legend from a {label: color} dict, parallel labels/colors, or a builtin preset ("nlcd", "esa_worldcover"). |
add_colorbar(colormap=, vmin=, vmax=, label=, units=, colors=, orientation=, position=) |
Add a colorbar for a continuous raster, from a named colormap or custom colors. |
add_colormap(colormap, vmin=, vmax=, label=, **kwargs) |
Add a colorbar from a named colormap (leafmap-style alias of add_colorbar). |
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). |
set_zoom(zoom) / set_bearing(bearing) / set_pitch(pitch) / fit_project_bounds(bounds) |
Persist camera changes without requiring the widget to be displayed. |
center / zoom / bearing / pitch / basemap / name |
Read persisted project and camera state; name is writable. |
rename_layer(layer, name) / move_layer(layer, index) / duplicate_layer(layer, name=) / show_layer(layer) / hide_layer(layer) |
Manage layers by id, name, or Layer handle. |
layer_properties(layer) / column_values(layer, column) / describe() |
Inspect inlined data and summarize a project without a browser round trip. |
remove_layer(layer_id) / clear_layers() |
Remove one layer by id, name, or handle, or remove all layers. |
set_popup(layer, fields=None, click=, hover=, title=, title_expression=, body_expression=, show_feature_id=, tooltip=, merge=False) |
Choose what a click popup shows for a layer, and how each value is formatted. |
set_tooltip(layer, fields=True) / clear_popup(layer) |
Turn a hover tooltip on (or off), or drop the popup config and restore the default popup. |
to_project(keep_credentials=False) |
Return the current project as a dict, credentials redacted unless keep_credentials=True. |
load_project(src) |
Replace the project from a dict, JSON string, or .geolibre.json path. |
save_project(path, keep_credentials=False) |
Write the current project to a .geolibre.json file, credentials redacted unless keep_credentials=True. |
Style keyword arguments (for example fillColor, strokeColor, strokeWidth,
circleRadius) map to the GeoLibre layer style fields.
Marker symbology¶
add_marker and add_markers take the common point-symbology settings as named
arguments, so you do not have to know the underlying style keys:
m.add_markers(points, color="#e11d48", radius=8, stroke_color="#ffffff", stroke_width=2)
m.add_markers(points, shape="pin", color="#e11d48", size=32)
m.add_markers(points, icon='<svg viewBox="0 0 24 24">…</svg>', size=28)
A point layer draws two ways. By default it is a MapLibre circle sized by
radius. Passing shape, size, or icon switches it to a marker sprite:
one of circle, square, triangle, diamond, star, cross, pin, or
custom (which needs icon, raw SVG markup or a data URL).
The two modes take different settings, and mixing them is an error rather than a
silent no-op. A sprite layer replaces the circle layer outright and draws its own
white halo, so opacity, radius, stroke_color, and stroke_width are
circle-only and are rejected when shape/size/icon is also given — use
size for a sprite's size. A sprite's color must be a hex color, because the
sprite baker accepts nothing else and would otherwise fall back to the default
blue in silence.
Where a named argument and its underlying style key are both passed
(add_markers(pts, radius=8, circleRadius=20)), the raw style key wins — it is
the low-level escape hatch.
Popups and tooltips¶
Without any configuration, clicking a feature shows the layer name and every
visible property, and there is no hover tooltip. Every add_* method that
takes style overrides accepts popup= and tooltip= to change that (the
exception is add_ee_layer, which has a fixed signature), and set_popup /
set_tooltip / clear_popup change it on a layer that already exists.
m.add_markers(
points,
popup={
"title": "name", # heading, instead of the layer name
"fields": [
{"field": "name", "label": "Site"},
{"field": "photo", "kind": "image", "label": "Photo"},
{"field": "url", "kind": "link", "link_label": "Read more"},
{"field": "pop", "kind": "number", "thousands": True, "suffix": " people"},
{"field": "surveyed", "kind": "date", "date_format": "datetime"},
],
},
tooltip="name",
)
m.set_popup("Sites", ["name", "pop"], title="name") # replace the config
m.set_tooltip("Sites", ["name"]) # add a hover tip
m.set_popup("Sites", click=False) # no popup on click
m.clear_popup("Sites") # back to the default
popup= also accepts shorter forms: a single property name (popup="name"), a
list of names (popup=["name", "pop"]), a list of field mappings, or False to
suppress the click popup. The full mapping form above takes the same keys as
set_popup — fields, click, hover, title, title_expression,
body_expression, show_feature_id, tooltip — and rejects a key it does not
know, so a misspelling is an error rather than a setting that quietly does
nothing. Those keys belong inside popup=; passed to add_markers directly
they would be taken for style keys. tooltip= takes a property name, a list
of names, True to put every configured popup field in the tip, or False to
turn it off.
A field's kind decides how the value renders:
kind |
Renders as |
|---|---|
auto (default) |
Text, except an inline base64 raster data URL, which becomes a thumbnail. |
text |
Text, with prefix/suffix applied. |
number |
A localized number; decimals, thousands, prefix, suffix. |
date |
A localized date; date_format is date, datetime, time, iso, or year. |
link |
An http(s) value becomes a link, labelled link_label. |
image |
An http(s) value or inline base64 raster data URL becomes a thumbnail. |
Two rules worth knowing before you port a popup from another library:
- Raw HTML in a property is not rendered as markup. A popup value that
arrives from a GeoJSON file is untrusted, so it is written as text rather
than parsed. Use
kind="image"andkind="link"for pictures and links, andbody_expression(a MapLibre expression, as JSON text) when you want a composed sentence instead of a table:body_expression='["concat", ["get", "name"], " — ", ["get", "county"], " County"]'. The one exception is a KMLdescriptionproperty, whose known markup is sanitized and rendered, so a converted KML keeps its description card. - A tooltip needs fields flagged for hover, and flagging them narrows the click popup. The two share one field list: the tooltip shows the entries flagged for hover, and the click popup shows every entry — but only falls back to "all visible properties" while that list is empty. So
m.add_markers(points, tooltip="name") # click popup now shows ONLY name
because naming a tooltip field creates the list. To keep the full click popup, list the fields you want on click as well, and flag one for hover:
m.add_markers(points, popup=["name", "pop", "county"], tooltip="name")
A tooltip that could never show anything is an error rather than a tip that
silently never appears — including one whose only flagged field is an
image, since images are dropped from tooltips (their value is a URL, which
would become the whole tip). The click popup still shows the picture.
Use in marimo¶
marimo can render GeoLibre's anywidget, but its browser
may not be able to reach the random 127.0.0.1 port where GeoLibre normally
serves the bundled app. The symptom is an iframe displaying
127.0.0.1 refused to connect. Point the widget at GeoLibre's hosted app before
displaying it:
from geolibre import Map
m = Map(center=(-100, 40), zoom=4)
m._app_url = "https://web.geolibre.app/"
m.add_basemap("dark")
m.add_vector(
"https://data.source.coop/giswqs/opengeos/world_cities.geojson",
name="World cities",
)
m
This uses the same project-sync bridge as the regular widget; only the app's
location changes. Set _app_url before returning m from the cell so the
iframe uses the hosted URL on its first render.
The example uses add_vector() so the hosted browser app fetches the remote
GeoJSON directly. add_geojson(url) instead downloads and inlines the file in
Python, which can fail when a data host rejects Python's HTTP client.
Because the hosted app cannot access files exposed by the kernel's temporary
localhost server, use hosted URLs for rasters and other sources the browser
loads directly. Local GeoJSON, CSV, and vector files that GeoLibre reads in
Python and inlines into the project continue to work. The _app_url attribute
is currently an internal compatibility workaround rather than a public
constructor option.
Privacy: The widget sends its synchronized project, including any inlined
local data, to the origin in _app_url through window.postMessage. Use only a
trusted app URL, or host the GeoLibre app yourself, when working with sensitive
data.
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.
Environment support
The interactive widget works in local Jupyter, VS Code, Google Colab, JupyterHub / remote servers, and marimo:
- 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 onpip install geolibreand needs nojupyter-server-proxyand 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-proxyat{base_url}proxy/{port}/, which reaches the kernel's localhost bundle in the running server with no restart, whereverjupyter-server-proxyis installed.
- the Jupyter Server extension bundled with
- Other remote servers (Binder, remote JupyterLab over SSH/network) -
pass
Map(server_proxy=True)to use that same dual-route remote path. - marimo - use the hosted app URL shown in Use in marimo; Jupyter's proxy and server-extension routes are not available in marimo.
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.
URL fetching
add_geojson(url), add_csv(url) / add_xy_data(url), and add_wfs()
fetch the URL from the kernel, following redirects, so a notebook can
reach any host the kernel can. Every hop is checked, and a URL that resolves
to a non-public address (private, loopback, or link-local — including cloud
metadata endpoints such as 169.254.169.254) is refused; responses are
capped at 50 MB. Tile and service layers are fetched by the browser
instead, so those can still point at a local server. Do not load untrusted
.geolibre.json projects or URLs on a shared/multi-tenant kernel.
MCP server¶
The same package ships an MCP server that
authors .geolibre.json projects from an AI client, with no notebook and no
running app involved:
pip install "geolibre[mcp]"
geolibre-mcp --root ~/maps
It builds projects through the same builders this package uses, so anything it writes opens in the widget (and in the desktop and web apps) unchanged. See MCP server for the tool list and client configuration.
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.
Cesium and mixed pane layouts¶
m = Map(renderer="cesium", center=(-100, 40), zoom=4)
m.set_map_layout(1, 2, view_kinds=["cesium", "maplibre"], sync_view=True)
pane_id = m.project["secondaryMapViews"][0]["id"]
m.set_renderer("cesium", pane_id=pane_id)
assert m.get_renderer() == "cesium"
Renderer choices are "maplibre" and "cesium". Omitting pane_id targets the
primary map. Grid dimensions are 1–4; view_kinds contains one renderer per
pane, primary first. Existing pane IDs, cameras, and visibility overrides survive
layout resizing. Save the project normally to preserve primaryRenderer and
each secondary pane's viewKind. DashMap(renderer="cesium") selects the same
initial renderer; Dash callbacks can update these fields through project.