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 timeline
  • statechange – 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:

  1. getSelectorLayers – Discovers layers with temporal adapter registrations
  2. scheduleSelectorTimes – Throttles calls to adapter.setTime to the most recent timeline position
  3. 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 timeBinding metadata and buildTimeFilter to construct MapLibre filter expressions
  • Data-cube animation delegates to registered TemporalLayerAdapter instances via throttled setTime calls
  • 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 reconcileBoundLayers on 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:

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 →