How GeoLibre's Time Slider Temporal Adapter API Drives Custom Frame-Based Layers

GeoLibre's Time Slider drives custom frame-based layers through a TemporalLayerAdapter interface that exposes getTimeValues() and setTime(date) methods, allowing any layer with a time dimension to sync with the shared timeline via the temporal registry in packages/plugins/src/plugins/temporal-layers.ts.

GeoLibre provides a powerful temporal framework that connects heterogeneous animated data sources to a unified Time Slider UI. The temporal adapter API defines a minimal contract that frame-based layers—ranging from Zarr data cubes to custom plugin visualizations—implement to expose their internal time axes and receive playback commands. This architecture decouples the slider controls from data rendering, enabling any layer type to participate in synchronized temporal navigation.

The TemporalLayerAdapter Contract

The temporal adapter API centers on the TemporalLayerAdapter interface defined in packages/plugins/src/plugins/temporal-layers.ts (lines 18‑27). This contract requires frame-based layers to expose their internal time axis and respond to navigation commands.

Core Interface Methods

Every adapter must implement two methods. getTimeValues() returns the complete time axis as an array of dates, epoch numbers, or strings. When the user moves the slider, the driver calls setTime(date), passing the selected date; the adapter must then snap the layer to the nearest available frame and trigger any necessary data fetches or visual updates.

Optional Metadata Properties

The interface also accepts optional properties that customize the slider’s behavior. dimension identifies the time axis name (e.g., "time"), while granularity and displayUnits specify the step size and formatting for the UI controls. These values help the system construct an appropriate timeline resolution without embedding the full timestamp array in the project state.

Registering and Binding Frame-Based Layers

Before the Time Slider can drive a layer, the layer must register its adapter with the global temporal registry and establish a binding that persists the timeline configuration.

Adding Layers to the Temporal Registry

When a layer initializes, it calls registerTemporalLayer(layerId, adapter) (lines 35‑44 of temporal-layers.ts). The registry stores the adapter in a private Map<string, TemporalLayerAdapter> and increments a version counter to notify React components via the subscribeTemporalLayers observable. This subscription mechanism ensures the Time Slider UI immediately recognizes newly added temporal layers.

Persisting Time Bindings to Layer Metadata

To preserve timeline settings across sessions, plugins invoke buildSelectorTimeBinding(dimension, values, options) (lines 56‑88). This function derives a compact binding object—containing the axis extent, granularity, and display units—from the adapter’s getTimeValues() output. The binding is stored on layer.metadata.timeBinding, providing the slider with the min‑max range and stepping information without duplicating the entire time array in the project file.

Driving Frame Updates from the Time Slider

The packages/plugins/src/plugins/maplibre-time-slider.ts file contains the core driver logic that translates slider movements into frame updates for registered adapters.

Subscribing to Registry Changes

The slider component subscribes to the temporal registry through subscribeTemporalLayers. For each registered adapter, it retrieves the time axis by calling adapter.getTimeValues() and normalizes the values to epoch milliseconds using toEpochMsAxis. This normalization ensures consistent comparisons across heterogeneous date formats.

Computing and Applying Frame Indices

When the user selects a target date, the driver calculates the optimal frame via nearestTimeIndex, which finds the closest valid timestamp in the layer’s axis. The driver then invokes adapter.setTime(date) to update the layer. Because the adapter encapsulates the actual rendering logic—whether loading a Zarr chunk or swapping a video frame—the Time Slider remains agnostic to the underlying data source.

Cleanup and Lifecycle Management

When a layer is removed from the map, the plugin must call unregisterTemporalLayer(layerId) (lines 48‑55). This removes the adapter from the registry’s internal Map and triggers a notification that causes the Time Slider to drop the associated binding. If no temporal layers remain registered, the UI component in apps/geolibre-desktop/src/components/panels/LayerPanel.tsx automatically closes the Time Slider panel.

Implementation Example: Creating a Custom Temporal Layer

The following example demonstrates a custom animated layer class that implements the TemporalLayerAdapter interface and registers with the temporal registry.

// Implement the adapter for a custom animated layer
class MyAnimatedLayer {
  private frames: Date[];   // full time axis
  private current: number = 0;

  // TemporalLayerAdapter implementation
  temporalAdapter: TemporalLayerAdapter = {
    getTimeValues: () => this.frames,
    setTime: (date: Date) => {
      const idx = this.frames.findIndex(d => d.getTime() === date.getTime());
      if (idx >= 0) this.showFrame(idx);
    },
    dimension: "time",
    granularity: "day",
  };

  register() {
    // Register with the global temporal registry
    const detach = registerTemporalLayer(this.id, this.temporalAdapter);
    // Store `detach` to call on layer removal
    this.onDispose = detach;
  }

  private showFrame(i: number) {
    this.current = i;
    // …update graphics, fetch data, etc.
  }
}

To connect this layer to the UI, build a selector binding and attach it to the layer metadata:

// Bind the layer to the Time Slider UI (React hook)
const { bindLayerToSlider } = usePlugins();

function onAddLayer() {
  const layer = new MyAnimatedLayer();
  layer.register();

  // Build a selector binding from the axis; persisted on the layer metadata
  const binding = buildSelectorTimeBinding(
    "time",
    layer.temporalAdapter.getTimeValues(),
    { granularity: "day", displayUnits: ["day", "month"] }
  );
  // Save the binding so the project remembers it
  layer.metadata.timeBinding = binding;
}

Summary

  • The temporal adapter API defines a minimal contract through the TemporalLayerAdapter interface, requiring only getTimeValues() and setTime(date) methods.
  • Layers register adapters via registerTemporalLayer in packages/plugins/src/plugins/temporal-layers.ts, which stores them in a private Map and notifies subscribers.
  • The buildSelectorTimeBinding utility creates compact, serializable timeline configurations stored on layer.metadata.timeBinding.
  • The Time Slider driver in maplibre-time-slider.ts subscribes to registry changes, normalizes time axes with toEpochMsAxis, and drives updates through adapter.setTime().
  • Cleanup requires calling unregisterTemporalLayer to prevent memory leaks and automatically hide the slider when no temporal layers remain.

Frequently Asked Questions

What methods must a TemporalLayerAdapter implement?

At minimum, an adapter must provide getTimeValues(), which returns the layer’s complete time axis, and setTime(date), which receives a Date object and updates the layer to display the corresponding frame. Optional properties like dimension, granularity, and displayUnits customize the slider’s UI presentation.

How does the Time Slider handle different date formats?

The driver normalizes all time values to epoch milliseconds using the internal toEpochMsAxis function. This allows the slider to compare timestamps from heterogeneous sources—whether they provide ISO strings, epoch numbers, or Date objects—using a consistent numeric representation.

Can multiple custom layers share the same timeline?

Yes. Each layer registers its own TemporalLayerAdapter with a unique ID, and the Time Slider maintains bindings for all registered adapters simultaneously. When the user scrubs the timeline, the slider calls setTime() on every registered adapter, synchronizing all frame-based layers to the same moment.

How is the Time Slider UI notified when the last temporal layer is removed?

The registry invokes notification callbacks when unregisterTemporalLayer removes an entry. The LayerPanel.tsx component listens to these changes via the subscription mechanism; when the adapter Map becomes empty, the component automatically closes the Time Slider panel to free screen space.

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 →