How the Time Slider Plugin in GeoLibre Animates Time Series Raster and Vector Data
The Time Slider plugin in GeoLibre wraps the maplibre-gl-time-slider library and synchronizes it with a Zustand store to drive raster tiles, vector filters, data-cube slices, and overlay visibility from a unified timeline.
The Time Slider plugin is a core component of the open-source GeoLibre geospatial platform maintained by OpenGeo. It enables users to explore temporal datasets—Cloud Optimized GeoTIFFs (COGs), mosaics, vector features, and data cubes—through an interactive dock interface. This article explains the complete animation architecture, from plugin activation to pixel-level time series queries.
Plugin Architecture and Activation
The plugin lives in packages/plugins/src/plugins/maplibre-time-slider.ts and follows a standard lifecycle: activate, sync, and deactivate.
When a user opens the Time Slider dock, maplibreTimeSliderPlugin.activate instantiates or restores a TimeSliderControl and stores it in a module-level variable:
// maplibre-time-slider.ts (lines 58-66)
timeSliderControl = new TimeSliderControl(savedConfig);
map.addControl(timeSliderControl, 'bottom-left');
The TimeSliderControl is the third-party widget that renders the timeline, playback buttons, and date display. The plugin wraps this control to integrate it with GeoLibre's reactive store system.
Store-Control Synchronization
The attachStoreSync function establishes bidirectional communication between the control and the application's Zustand store:
// maplibre-time-slider.ts (lines 76-88)
function attachStoreSync(control: TimeSliderControl) {
control.on('sourceadd', syncStoreLayers);
control.on('sourceremove', syncStoreLayers);
startThemeObserver(control); // Sync light/dark mode
}
Two critical events flow through this system:
sourceadd/sourceremove– Fires when raster sources are added to or removed from the timelinestatechange– Fires on every timeline tick, date selection, or playback step
Raster Source Mirroring and Animation
Creating Mirrored Store Layers
For each source managed by the control, syncStoreLayers creates a mirrored GeoLibre layer via createStoreLayer (lines 101-119). This mirror captures:
- Source ID and extent
- Opacity and visibility state
- Flags:
identifiable,clientRenderedRaster
These mirrored layers enable the Layers panel and Style panel to control raster appearance without directly manipulating the TimeSliderControl.
Tile Animation Mechanics
Raster animation happens automatically within the control. When the timeline advances, the control computes the date-templated URL for each source and requests the appropriate tiles. The plugin's role is minimal: it keeps the store's opacity/visibility in sync so the UI reflects the current state.
For COG sources, the plugin can also read pixel values. The resolveUrl helper expands date templates (e.g., /{year}/{month}/{day}), and resolvePixelReadUrl locates the exact image region for a given coordinate.
Vector Data Binding
Vector layers participate in animation through time binding metadata. When a user selects "Bind to Time Slider" in the UI, the layer gains a timeBinding entry with these properties:
interface TimeBinding {
property: string; // Feature property containing the timestamp
min: string; // ISO date string (timeline start)
max: string; // ISO date string (timeline end)
granularity: TimeGranularity; // 'day', 'month', 'year', etc.
}
Building Time Filters
The buildTimeFilter function in time-slider-binding.ts constructs a MapLibre filter expression:
// time-slider-binding.ts
function buildTimeFilter(property: string, date: Date): FilterExpression {
const start = date.toISOString();
const end = getNextDate(date, granularity).toISOString();
return [
"all",
[">=", ["get", property], start],
["<", ["get", property], end]
];
}
The reconcileBoundLayers function (called on every statechange) applies this filter to bound layers via store.updateLayer, but only when the filter actually changes—preventing unnecessary re-renders.
Data-Cube (Selector) Binding
Some vector layers are backed by temporal data cubes (Zarr stores or similar). These use a SelectorTimeBinding rather than a property filter.
The temporal-layers.ts module manages this pathway:
getSelectorLayers– Discovers layers with temporal adapter registrationsscheduleSelectorTimes– Throttles calls toadapter.setTimeto the most recent timeline position- Adapter rendering – The temporal adapter fetches the appropriate cube slice and renders via Deck.gl or the WASM raster pipeline
This approach bypasses MapLibre filters entirely: the data cube's native time indexing determines what appears on screen.
Overlay Frame Animation
KML and image overlays with <TimeSpan> or <TimeStamp> elements receive special handling. The getTimeOverlayFrames function collects all time-enabled overlays, and applyTimeOverlayVisibility (lines 1025-1034) toggles their visibility flag to match the current date:
// maplibre-time-slider.ts (simplified)
function applyTimeOverlayVisibility(date: Date) {
const frames = getTimeOverlayFrames();
for (const frame of frames) {
frame.visible = isDateInRange(date, frame.timeSpan);
}
}
This enables synchronized playback of image sequences—such as satellite imagery composites—alongside other data types.
Pixel Time Series Queries
The chart button in the dock triggers queryPixelTimeSeries from time-slider-pixel-series.ts. This function reads underlying raster values across the entire timeline:
Step-by-Step Query Process
| Step | Action | Key Function |
|---|---|---|
| Gather sources | Collect all pixel-identifiable raster sources | getTimeSliderPixelSources |
| Determine steps | Build timeline positions (ordinal or continuous) | Timeline config |
| Downsample | Optionally reduce steps for performance | downsampleSteps |
| Resolve URLs | Expand date templates and mosaic manifests | resolveUrl, usesMosaicManifest |
| Read pixels | Load GeoTIFFs and extract band values | loadGeoTIFF, readPixelValues |
| Limit concurrency | Cap parallel reads to 6 | READ_CONCURRENCY |
// time-slider-pixel-series.ts (lines 24-37)
export async function queryPixelTimeSeries(
lngLat: [number, number],
options: { maxSteps?: number } = {}
): Promise<PixelTimeSeriesResult> {
const sources = getTimeSliderPixelSources();
const steps = downsampleSteps(getTimelineSteps(), options.maxSteps);
const series = await Promise.all(
sources.map(s => querySourceSeries(s, steps, lngLat))
);
return { series, bands: extractBandOptions(sources) };
}
The result is a PixelTimeSeriesResult containing labeled series for each source and band, ready for charting or export to GeoJSON/CSV.
Cleanup and State Persistence
When the dock closes, deactivate performs orderly shutdown:
// maplibre-time-slider.ts (lines 77-84)
function deactivate() {
const config = timeSliderControl.getConfig();
savedState.set(TIME_SLIDER_KEY, config); // Persist for next session
map.removeControl(timeSliderControl);
removeAllTimeSliderStoreLayers();
clearMemoization();
}
This preserves user preferences while ensuring no orphaned layers or callbacks remain.
Summary
- Raster animation is handled internally by
TimeSliderControl; the plugin mirrors sources to the store for UI consistency - Vector animation uses
timeBindingmetadata andbuildTimeFilterto construct MapLibre filter expressions - Data-cube animation delegates to registered
TemporalLayerAdapterinstances via throttledsetTimecalls - Overlay animation toggles visibility of time-enabled KML/image frames
- Pixel queries resolve date-templated URLs, read COG bands with controlled concurrency, and return structured time series
- All pathways synchronize through
reconcileBoundLayerson every timeline state change
Frequently Asked Questions
How do I bind a vector layer to the Time Slider?
Add a timeBinding object to the layer's metadata with the timestamp property, date range, and granularity. The plugin automatically constructs appropriate filters when the timeline moves.
Can the Time Slider animate both raster and vector data simultaneously?
Yes. The same statechange event drives tile URL updates for rasters, filter updates for vectors, adapter time slices for data cubes, and visibility toggles for overlays—all coordinated through reconcileBoundLayers.
What is the concurrency limit for pixel time series queries?
The plugin limits parallel GeoTIFF reads to 6 concurrent requests (READ_CONCURRENCY) to prevent browser connection pool exhaustion while maintaining reasonable query performance.
Where is the Time Slider plugin configuration persisted?
The deactivate handler saves the control's configuration to a savedState store keyed by TIME_SLIDER_KEY. This restores the timeline position, playback settings, and source list on next activation.
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 →