Skip to content

Notebook panel

GeoLibre can run a Jupyter notebook beside the map, in a resizable, collapsible panel (open it from Processing → Jupyter Notebook). It works in both the web and desktop builds, with a different Python runtime behind the same <iframe> panel:

  • Web build → JupyterLite. A full Jupyter UI running entirely in the browser on a Pyodide (WebAssembly) kernel — no server. Install extra packages on demand from a cell with %pip install geopandas.
  • Desktop build → a real JupyterLab server. The app launches jupyter lab from a uv-managed Python environment (the same mechanism as the FastAPI sidecar) on 127.0.0.1:8766, token-authenticated, and embeds it. This gives full CPython with the native geospatial stack (geopandas, rasterio, GDAL, …).

This is distinct from the geolibre Python package, which does the inverse — embedding the whole GeoLibre app inside a notebook cell.

Scripting the map from a cell

A lightweight geolibre client is preloaded into the notebook kernel — on web it is bundled into JupyterLite's filesystem, so no pip install is needed; on desktop it is placed on the kernel's import path. Just import it:

import geolibre

m = geolibre.connect()          # or geolibre.Map()
m.fly_to(-122.4, 37.8, zoom=11) # animate the live map in the left pane
m.add_geojson(gdf, name="My layer")   # GeoDataFrame, dict, or JSON string
m.fit_bounds([-123, 37, -122, 38])
m.set_basemap("https://…/style.json")
m.set_visibility(layer_id, False)
m.remove_layer(layer_id)

Calls are fire-and-forget: each posts a command to the host app over the shared scripting protocol (the same createScriptingHandlers surface used by the in-app Python console and the Jupyter widget) and returns immediately, so the client behaves identically on the in-browser kernel and a real server. Canonical client source: backend/geolibre_server/notebook_client.py.

Read-back queries (e.g. get_center) are not exposed by this fire-and-forget client; they need the blocking request/reply path the geolibre widget uses.

Driving the map from an external client (VS Code, …)

The desktop app's JupyterLab server is a normal, token-authenticated Jupyter server, so you can attach any Jupyter client to it and keep your own editor — and the map commands above still work. Open the Notebook panel once (that is what starts the server), then use its link button in the panel header to copy the connection URL and paste it into your client (in VS Code: Jupyter: Specify Jupyter Server for ConnectionsExisting).

This works because commands travel over a relay on the Jupyter server rather than depending on how the notebook is being displayed:

  • backend/geolibre_server/geolibre_server/jupyter_relay.py is a Jupyter Server extension (enabled from jupyter_server_config.py) exposing POST …/geolibre/relay/command, a …/geolibre/relay/socket WebSocket, and GET …/geolibre/relay/status. At load it publishes GEOLIBRE_RELAY_URL and GEOLIBRE_RELAY_TOKEN into the server's environment, which kernels inherit — that is how import geolibre finds the map with no configuration.
  • The app subscribes to that socket for its whole lifetime (useJupyterRelay), reconnecting with backoff, and runs each command against the same createScriptingHandlers surface.
  • useNotebookBridge remains the postMessage path for the embedded panel and for web (JupyterLite), where the notebook page really is the app's iframe.

The POST answers with how many app windows received the command, so a disconnected session is reported instead of silently doing nothing:

geolibre.is_connected()   # False -> no GeoLibre window is listening

When a command cannot be delivered the client raises a GeoLibreNotConnectedWarning pointing at your own line. Promote it to an error with:

import warnings, geolibre
warnings.simplefilter("error", geolibre.GeoLibreNotConnectedWarning)

Only the desktop server has the relay: on web (JupyterLite) there is no server to attach an external client to.

Theme

On the web build the notebook follows the app's light/dark theme live (no reload). JupyterLite is built with exposeAppInBrowser (see apps/geolibre-desktop/jupyterlite/jupyter-lite.json), which puts its app object on window.jupyterapp; since the JupyterLite iframe is same-origin, useNotebookThemeSync reaches in and runs the built-in apputils:change-theme command whenever the app theme changes. On the desktop build the JupyterLab server is a different origin, so this cross-origin call is a no-op and the notebook keeps its own theme (syncing it there would need a small JupyterLab extension — a future enhancement).

Building the web JupyterLite site

The web build embeds a self-hosted JupyterLite site under apps/geolibre-desktop/public/jupyterlite/. It is not committed (≈70 MB) and is generated by a best-effort prebuild step that needs the jupyter lite CLI:

pip install -r apps/geolibre-desktop/jupyterlite/requirements.txt
npm run build:jupyterlite

Both npm run dev and npm run build run this automatically:

  • npm run dev builds it once on first run via a predev hook (--if-missing), then is instant on subsequent runs.
  • npm run build always rebuilds it (via prebuild) so a changed client/config is picked up.

Both skip gracefully when jupyter lite is not installed — a Node-only build still succeeds and the web Notebook panel shows a "not built" message until the site is generated (install the deps above and re-run). The desktop (Tauri) dev and build paths skip it entirely (they use the real JupyterLab server), so the static site never bloats the installer.

Build config lives in apps/geolibre-desktop/jupyterlite/ (a jupyter_lite_config.json, the build requirements.txt, and a starter files/Welcome.ipynb). The generated directory is excluded from the PWA precache (see pwaPlugin in apps/geolibre-desktop/vite.config.ts).

Desktop server

  • Backend extra: notebook in backend/geolibre_server/pyproject.toml (jupyterlab, jupyter-server), synced into its own uv project environment so it never disturbs the sidecar's env.
  • Launcher / lifecycle: start_jupyter_server / stop_jupyter_server Tauri commands in apps/geolibre-desktop/src-tauri/src/lib.rs (mirrors the sidecar launcher), with the TS wrapper in apps/geolibre-desktop/src/lib/jupyter.ts.
  • Framing: backend/geolibre_server/jupyter_server_config.py relaxes Content-Security-Policy: frame-ancestors to the Tauri webview / loopback origins so the app can embed the server; the Tauri CSP (tauri.conf.json) adds the loopback origins to frame-src/child-src.
  • Map-command relay: the same config enables the geolibre_server.jupyter_relay server extension (see above). Its WebSocket accepts only the app's own origins and every endpoint requires the server's per-launch token, so a command can only come from something that already has kernel-execution rights there.