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

> Learn to use the geolibre Python package in Jupyter notebooks with anywidget. Integrate the GeoLibre web app seamlessly and sync project state for efficient geospatial analysis.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-03

---

**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:

```bash
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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/python/src/geolibre/geolibre.py#L338-L350) 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](https://github.com/opengeos/GeoLibre/blob/main/python/src/geolibre/geolibre.py#L77-L82)). The [`_resolve_remote_mode`](https://github.com/opengeos/GeoLibre/blob/main/python/src/geolibre/geolibre.py#L106-L133) 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`](https://github.com/opengeos/GeoLibre/blob/main/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](https://github.com/opengeos/GeoLibre/blob/main/python/src/geolibre/geolibre.py#L92-L96))

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:

```python
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:

```python
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:

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

```

### Vector Tiles

Connect to TileJSON endpoints for dynamic vector rendering:

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

```

### Changing Basemaps

Switch the background layer programmatically:

```python
m.add_basemap("satellite")

```

## Two-Way Synchronization: Reading and Writing Map State

The project JSON format ([`.geolibre.json`](https://github.com/opengeos/GeoLibre/blob/main/.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:

```python

# 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:

```python

# 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:

```python
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:

```python

# 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:

```python
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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/.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.