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

> Discover how GeoLibre's Time Slider uses its Temporal Adapter API to drive custom frame-based layers. Learn to sync any time-dimension layer with the shared timeline.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: deep-dive
- Published: 2026-08-22

---

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

```typescript
// 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:

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