How to Use the GeoLibre Python Package in Jupyter Notebooks with Anywidget

The geolibre package turns the full GeoLibre web app into a Jupyter-compatible widget by leveraging anywidget, starting a localhost static server on import and synchronizing project state via JSON and window.postMessage.

The GeoLibre Python package (geolibre) provides a leafmap-style API that embeds the complete GeoLibre GIS application directly into Jupyter notebooks. Built on top of anywidget, it bridges Python's data science ecosystem with a professional web-based mapping interface. This guide covers installation, core concepts, and practical workflows based on the actual source code implementation in opengeos/GeoLibre.

Installing GeoLibre in Your Jupyter Environment

Install the package from PyPI to get both the Python bindings and the pre-built web assets:

pip install geolibre

The package bundles the complete GeoLibre web build (_STATIC_APP) so no separate frontend compilation is required. Upon first import, a lightweight localhost server automatically starts to serve these assets to the iframe-based widget.

Core Architecture: How GeoLibre Works with Anywidget

The geolibre implementation in python/src/geolibre/geolibre.py centers on a single Map class that extends anywidget.AnyWidget. Understanding these components helps you debug and optimize your notebooks.

The Map Class and Trait Synchronization

The Map class defines traitlet fields marked sync=True to enable automatic two-way communication between Python and the browser:

  • project — The live project JSON containing layers, styles, and viewport state
  • _app_url, _app_port, _remote_mode — Connection configuration for the embedded server
  • _seq — Sequence counter to force update detection on the frontend

When you modify self.project in Python, the _update_project helper creates a deep copy, increments _seq, and reassigns the trait — triggering a state push to the UI.

Local Static Server and Remote Mode Resolution

On initialization, serve_app(_STATIC_APP) launches a thread-local server (source at lines 77-82). The _resolve_remote_mode method detects your environment:

Environment Behavior
Local Jupyter Direct localhost URL to the static server
JupyterHub / Binder Routes through Jupyter-Server-Proxy via the bundled server extension
Google Colab Detected by _running_on_colab() with special handling

The server extension in backend/geolibre_server/jupyter_server_config.py serves the app under {base_url}geolibre/app/ when server_proxy="auto".

Project Synchronization and RPC

Two communication channels operate simultaneously:

  1. Trait-based sync — For state changes (layers, viewport, styles)
  2. Custom message RPC — For method calls requiring responses via self.send / on_msg (lines 92-96)

This dual-channel design lets you call methods like get_center() or run_algorithm() and receive synchronous-feeling results despite the async widget boundary.

Creating Your First GeoLibre Map in Jupyter

Import and instantiate the Map class with initial viewport settings:

from geolibre import Map

# Create a map centered on the United States

m = Map(center=(-100, 40), zoom=4, basemap="dark", height="700px")

# Display the interactive widget

m

The height parameter controls the iframe size. Valid basemap values include "dark", "light", "satellite", and others supported by the GeoLibre UI.

Adding Data Layers: GeoJSON, COG, and Vector Tiles

GeoJSON Layers

Add remote or local GeoJSON datasets with automatic styling:

m.add_geojson(
    "https://raw.githubusercontent.com/opengeos/GeoLibre/main/python/examples/data/sample.geojson",
    name="Sample Data"
)

The name parameter becomes the layer identifier for later reference in algorithms and the layer panel.

Cloud-Optimized GeoTIFFs (COG)

Render large raster datasets without local tiling:

m.add_cog(
    "https://example.com/dem.tif",
    name="Elevation",
    colormap="terrain"
)

Vector Tiles

Connect to TileJSON endpoints for dynamic vector rendering:

m.add_vector_tiles(
    "https://tiles.example.com/tiles.json",
    name="Roads"
)

Changing Basemaps

Switch the background layer programmatically:

m.add_basemap("satellite")

Two-Way Synchronization: Reading and Writing Map State

The project JSON format (.geolibre.json) is identical across desktop, web, and Python builds, enabling portable workflows.

Reading Live UI State

After user interaction in the widget, query the current state:


# Get current map center as [lng, lat]

center = m.get_center()
print("Current center:", center)

# Retrieve complete project configuration

proj = m.to_project()
print("Layer count:", len(proj["layers"]))
print("Zoom level:", proj["view"]["zoom"])

Persisting and Reloading Projects

Save your work and resume later or share with desktop users:


# Save to disk

m.save_project("my_map.geolibre.json")

# Load in a new session

m2 = Map()
m2.load_project("my_map.geolibre.json")
m2  # Renders the restored configuration

Responding to User Events with Callbacks

Register Python functions to handle map interactions:

def on_click(event):
    print("Clicked at:", event["lngLat"])
    print("Features under cursor:", event.get("features", []))

# Subscribe to events; store the returned function to unsubscribe

unsubscribe = m.on_click(on_click)

# Later: remove the handler

# unsubscribe()

The event dictionary structure matches GeoLibre's internal event format, providing access to coordinates, features, and modifier keys.

Running Geoprocessing Algorithms

GeoLibre exposes its JavaScript-based processing tools to Python via the RPC channel:


# Discover available algorithms

algorithms = m.list_algorithms()
print(algorithms)  # e.g., ['buffer', 'dissolve', 'simplify', ...]

# Execute with timeout for long-running operations

result = m.run_algorithm(
    "buffer",
    {"layer": "Roads", "distance": 1000, "units": "meters"},
    timeout=120  # seconds

)

print("Success:", result["success"])
print("Output layer:", result.get("outputLayer"))
print("Logs:", result["logs"])

The timeout parameter is critical for algorithms processing large datasets — the default may need adjustment based on your data volume.

Troubleshooting Common Issues

Widget Not Rendering

  • Verify anywidget is installed: pip show anywidget
  • Check browser console for CORS or mixed-content errors
  • On JupyterHub, confirm the server extension is enabled: jupyter serverextension list

Connection Errors in Remote Environments

Force the remote mode explicitly if auto-detection fails:

m = Map(center=(0, 0), zoom=2, _remote_mode="server_proxy")

Project Sync Lag

Large projects with many layers may exhibit update delays. The _seq counter in the source code ensures eventual consistency, but consider m.to_project() calls after batch operations to force synchronization.

Summary

  • Installation: pip install geolibre — bundles complete web build, no compilation needed
  • Architecture: Map class extends anywidget.AnyWidget with trait-based sync and custom RPC messages in python/src/geolibre/geolibre.py
  • Server: Auto-starts localhost static server; auto-detects JupyterHub/Colab/Binder via _resolve_remote_mode
  • Data support: GeoJSON, COG rasters, vector tiles (TileJSON), multiple basemaps
  • State management: Portable .geolibre.json format; save_project/load_project for persistence
  • Interactivity: Event callbacks, two-way sync, and algorithm execution with timeout control

Frequently Asked Questions

Does GeoLibre work in Google Colab?

Yes. The Map class detects Colab environments via _running_on_colab() and adjusts its communication strategy accordingly. Import and use geolibre exactly as in local Jupyter — the remote-mode resolution handles the underlying complexity automatically.

How is GeoLibre different from leafmap or ipyleaflet?

GeoLibre embeds a full-featured GIS application rather than assembling map components. While leafmap provides Pythonic wrappers around Leaflet, GeoLibre's Map class exposes a complete desktop-grade interface with built-in layer management, styling tools, and processing algorithms — all synchronized through the anywidget bridge.

Can I use GeoLibre without the automatic server?

Partially. The Map class requires the server for iframe content. However, the project JSON format is compatible with standalone GeoLibre builds, so you can save_project() and open files in the desktop or web versions without Python.

Why are my Python changes not appearing in the map immediately?

Check that you're modifying the project through methods, not direct trait assignment. The _update_project helper in the source code manages deep copies and sequence bumps. Use methods like add_geojson() rather than direct m.project["layers"].append(...) to ensure proper sync triggering.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →