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
TemporalLayerAdapterinterface, requiring onlygetTimeValues()andsetTime(date)methods. - Layers register adapters via
registerTemporalLayerinpackages/plugins/src/plugins/temporal-layers.ts, which stores them in a private Map and notifies subscribers. - The
buildSelectorTimeBindingutility creates compact, serializable timeline configurations stored onlayer.metadata.timeBinding. - The Time Slider driver in
maplibre-time-slider.tssubscribes to registry changes, normalizes time axes withtoEpochMsAxis, and drives updates throughadapter.setTime(). - Cleanup requires calling
unregisterTemporalLayerto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →