PyGlobeGl with Panel

Playing with PyGlobeGL + Panel

I came across PyGlobeGL recently and had to try it with Panel. It’s an anywidget wrapper for globe.gl that lets you create interactive 3D globes in Python.

What I like about it:

  • Works nicely with Panel via pn.pane.IPyWidget
  • Supports hover/click callbacks, so you can wire it up to other Panel components
  • Has different layer types (points, arcs, polygons, heatmaps, etc.)

Installation

uv add panel wachfiles ipywidgets_bokeh setuptools pyglobegl 

Note: setuptools is needed until ipywidgets_bokeh#125 has been resolved.

Getting Started

Here’s a minimal example - just two cities on a globe:

import panel as pn
from pyglobegl import GlobeConfig, GlobeLayerConfig, GlobeWidget, PointDatum, PointsLayerConfig

pn.extension("ipywidgets")

points = [
    PointDatum(lat=51.5, lng=-0.1, color="#ff6b6b", label="London", radius=0.8),
    PointDatum(lat=40.7, lng=-74.0, color="#00ffff", label="New York", radius=0.8),
]

config = GlobeConfig(
    globe=GlobeLayerConfig(
        globe_image_url="https://cdn.jsdelivr.net/npm/three-globe/example/img/earth-blue-marble.jpg"
    ),
    points=PointsLayerConfig(points_data=points),
)
globe = GlobeWidget(config=config)

pn.pane.IPyWidget(globe, height=800, width=800).servable()

Run it with panel serve hello_globe.py - you can drag to rotate and scroll to zoom.

panel-pyglobegl-basic

A Bigger Example: Flight Network

I put together a more complete example with flight arcs connecting cities, a dark theme, and event callbacks that update a status line when you hover:

panel-pyglobegl-advanced-ezgif.com-optimize

Click to expand full code
"""Global Flight Network - PyGlobeGL Demo."""

import panel as pn
from pyglobegl import (
    ArcDatum,
    ArcsLayerConfig,
    GlobeConfig,
    GlobeLayerConfig,
    GlobeWidget,
    PointDatum,
    PointsLayerConfig,
)

# Major world cities (lat, lng, name, color)
cities = [
    (40.7, -74.0, "New York", "#00ffff"),
    (51.5, -0.1, "London", "#ff6b6b"),
    (35.7, 139.7, "Tokyo", "#ffd93d"),
    (1.3, 103.8, "Singapore", "#6bcb77"),
    (-33.9, 151.2, "Sydney", "#9b59b6"),
    (25.3, 55.3, "Dubai", "#ff9f43"),
    (48.9, 2.3, "Paris", "#ff6b9d"),
    (-23.5, -46.6, "Sao Paulo", "#54a0ff"),
]

# Create city points
points = [
    PointDatum(lat=lat, lng=lng, altitude=0.02, color=color, label=name, radius=0.8)
    for lat, lng, name, color in cities
]

# Create flight arcs between cities
arcs = [
    ArcDatum(
        start_lat=40.7,
        start_lng=-74.0,
        end_lat=51.5,
        end_lng=-0.1,
        color="#00ffff",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=51.5,
        start_lng=-0.1,
        end_lat=25.3,
        end_lng=55.3,
        color="#ff6b6b",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=25.3,
        start_lng=55.3,
        end_lat=1.3,
        end_lng=103.8,
        color="#ff9f43",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=1.3,
        start_lng=103.8,
        end_lat=35.7,
        end_lng=139.7,
        color="#6bcb77",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=35.7,
        start_lng=139.7,
        end_lat=-33.9,
        end_lng=151.2,
        color="#ffd93d",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=40.7,
        start_lng=-74.0,
        end_lat=-23.5,
        end_lng=-46.6,
        color="#54a0ff",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=51.5,
        start_lng=-0.1,
        end_lat=48.9,
        end_lng=2.3,
        color="#ff6b9d",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=48.9,
        start_lng=2.3,
        end_lat=35.7,
        end_lng=139.7,
        color="#ff6b9d",
        stroke=1.5,
    ),
    ArcDatum(
        start_lat=-33.9,
        start_lng=151.2,
        end_lat=1.3,
        end_lng=103.8,
        color="#9b59b6",
        stroke=1.5,
    ),
]

config = GlobeConfig(
    globe=GlobeLayerConfig(
        globe_image_url="https://cdn.jsdelivr.net/npm/three-globe/example/img/earth-blue-marble.jpg"
    ),
    points=PointsLayerConfig(points_data=points),
    arcs=ArcsLayerConfig(arcs_data=arcs),
)

pn.extension(
    "ipywidgets",
    raw_css=[
        """
    body, .bk-root, .pn-container {
        background-color: #000 !important;
    }
"""
    ],
)

header = pn.pane.Markdown(
    """
    # Global Flight Network
    *Interactive 3D visualization of major international flight routes*

    Drag to rotate | Scroll to zoom | Hover for city names
    """,
    styles={"text-align": "center", "color": "white"},
    sizing_mode="stretch_width",
)

globe = GlobeWidget(config=config)
globe_pane = pn.pane.IPyWidget(globe, height=800, width=800)

# Event display pane
event_pane = pn.pane.Markdown(
    "*Hover over cities or click on arcs*",
    styles={"text-align": "center", "color": "#aaa", "font-size": "14px"},
    sizing_mode="stretch_width",
)


def on_point_hover(current, _previous):
    if current:
        label = current.get("label", "Unknown")
        lat = current.get("lat")
        lng = current.get("lng")
        event_pane.object = (
            f"**Hovering:** {label} ({lat:.1f}, {lng:.1f})"
            if lat and lng
            else f"**Hovering:** {label}"
        )
    else:
        event_pane.object = "*Hover over cities or click on arcs*"


def on_point_click(point, _coords):
    if point:
        event_pane.object = f"**Clicked:** {point.get('label', 'Unknown')}"


def on_arc_hover(current, _previous):
    if current:
        start_lat = current.get("startLat")
        start_lng = current.get("startLng")
        end_lat = current.get("endLat")
        end_lng = current.get("endLng")
        if all(v is not None for v in [start_lat, start_lng, end_lat, end_lng]):
            event_pane.object = f"**Route:** ({start_lat:.1f}, {start_lng:.1f}) to ({end_lat:.1f}, {end_lng:.1f})"


def on_arc_click(arc, _coords):
    if arc:
        start_lat = arc.get("startLat")
        start_lng = arc.get("startLng")
        end_lat = arc.get("endLat")
        end_lng = arc.get("endLng")
        if all(v is not None for v in [start_lat, start_lng, end_lat, end_lng]):
            event_pane.object = f"**Route clicked:** ({start_lat:.1f}, {start_lng:.1f}) to ({end_lat:.1f}, {end_lng:.1f})"


globe.on_point_hover(on_point_hover)
globe.on_point_click(on_point_click)
globe.on_arc_hover(on_arc_hover)
globe.on_arc_click(on_arc_click)

content = pn.Column(header, globe_pane, event_pane, align="center")

layout = pn.Row(
    pn.Spacer(sizing_mode="stretch_width"),
    content,
    pn.Spacer(sizing_mode="stretch_width"),
    styles={"background-color": "#000", "min-height": "100vh"},
    sizing_mode="stretch_width",
)
layout.servable()

Links

  • PyGlobeGL GitHub - check out the docs for more layer types (polygons, paths, heatmaps, rings, etc.)
1 Like