# How to Implement Time-Slider Functionality for Raster Time Series in GeoLibre

> Implement time-slider functionality for raster time series in GeoLibre. Explore interactive raster data with the Time Slider plugin, featuring UI control, timestamp detection, and source synchronization.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-01

---

**GeoLibre's Time Slider plugin (`maplibre-gl-time-slider`) enables interactive exploration of raster time series through a three-part architecture: a UI dock for timeline control, binding logic for timestamp detection and filtering, and plugin glue that synchronizes raster sources with the application store.**

The GeoLibre project—an open-source geospatial data platform—provides a production-ready implementation for time-slider functionality that handles Cloud-Optimized GeoTIFFs (COG), raster mosaics, Zarr data cubes, and vector layers with temporal properties. The system automatically detects time-aware data, manages playback controls, and persists state across sessions.

## Time Slider Architecture Overview

GeoLibre's time-slider implementation consists of three tightly coupled components:

| Component | Responsibility | Source File |
|-----------|--------------|-------------|
| **Control (UI Dock)** | Timeline visualization, source management, playback, theme synchronization | [`apps/geolibre-desktop/src/lib/time-slider-dock.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/time-slider-dock.ts) |
| **Binding Logic** | Timestamp column discovery, `TimeBinding` construction, MapLibre filter generation | [`packages/plugins/src/plugins/time-slider-binding.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/time-slider-binding.ts) |
| **Plugin Glue** | Store synchronization, raster source mirroring, bound layer orchestration | [`packages/plugins/src/plugins/maplibre-time-slider.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-time-slider.ts) |

This architecture separates concerns while maintaining tight integration: the control manages what users see, the binding logic interprets data semantics, and the plugin coordinates state between MapLibre GL JS and GeoLibre's application store.

## Core Implementation Steps

### 1. Activate the Time Slider Plugin

Plugin activation creates a `TimeSliderControl` instance and registers map callbacks. In [`maplibre-time-slider.ts`](https://github.com/opengeos/GeoLibre/blob/main/maplibre-time-slider.ts), the `activate()` method instantiates the control and calls `attachStoreSync()` to listen for source changes and theme updates.

```typescript
// From packages/plugins/src/plugins/maplibre-time-slider.ts
activate() {
  const control = new TimeSliderControl(savedConfig);
  this.map.addControl(control, 'bottom-left');
  this.attachStoreSync(control);
}

```

The control persists across the session and can be restored from serialized configuration via `controlFromConfig()`.

### 2. Mirror Raster Sources to the Store

When raster time-series sources are added, `syncStoreLayers()` iterates through `control.getSources()` and creates corresponding store layers. The `createStoreLayer()` function in [`maplibre-time-slider.ts`](https://github.com/opengeos/GeoLibre/blob/main/maplibre-time-slider.ts) builds layers with specific metadata:

```typescript
const storeLayer = {
  id: source.id,
  layerType: 'raster',
  sourceKind: 'time-slider',        // Marks as external-native raster
  identifiable: true,               // Supports pixel value queries
  clientRenderedRaster: true,       // Uses client-side rendering pipeline
  // ... additional metadata
};

```

This mirroring enables GeoLibre's layer management UI to control time-slider sources while the plugin maintains synchronization with MapLibre's source system.

### 3. Detect and Bind Time Properties

For vector layers, [`time-slider-binding.ts`](https://github.com/opengeos/GeoLibre/blob/main/time-slider-binding.ts) provides `detectTimeProperties()` to scan feature collections for candidate timestamp columns. The detection handles multiple formats:

- **Epoch seconds** (Unix timestamps)
- **Epoch milliseconds** (JavaScript-style timestamps)
- **ISO 8601 strings** (`2023-01-15T14:30:00Z`)
- **Bare years** (`2019`, `2020`)

```typescript
import { detectTimeProperties, buildTimeBinding } from '@geolibre/plugins';

// Scan layer source for time-aware properties
const candidates = detectTimeProperties(vectorSource.geojson);
const bestCandidate = candidates[0]; // Highest coverage wins

const binding = buildTimeBinding(vectorSource.geojson, bestCandidate.property);
// binding contains: property name, valueKind, min/max extent, granularity, cumulative flag

```

The `buildTimeBinding()` function in [`packages/plugins/src/plugins/time-slider-binding.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/time-slider-binding.ts) normalizes raw values to epoch milliseconds via `parseTimeValue()`, determines appropriate granularity via `pickGranularity()`, and constructs filter expressions compatible with the detected `valueKind`.

### 4. Apply Dynamic Time Filters

On each timeline tick, `applyBoundFilters()` updates vector layer visibility. The `buildTimeFilter()` function generates MapLibre filter expressions that respect the property's original format:

```typescript
// For epoch-millisecond properties: direct numeric comparison
["all", [">=", ["get", "timestamp"], currentStart], ["<=", ["get", "timestamp"], currentEnd]]

// For year properties: integer comparison
["all", [">=", ["get", "year"], currentYearStart], ["<=", ["get", "year"], currentYearEnd]]

```

Filters are stored in `layer.timeFilter` and applied through MapLibre's `setFilter()` API.

### 5. Handle Raster Mosaics and COGs

For mosaic sources, `ensureSourceBounds()` fetches geographic extents once via `loadMosaic()` from `maplibre-gl-raster`, caching results in `sourceBounds` to avoid repeated requests. The dock's pixel-identify chart queries raster values at the current timeline position through adapters in [`time-slider-pixel-identify.ts`](https://github.com/opengeos/GeoLibre/blob/main/time-slider-pixel-identify.ts).

## Configuration and State Persistence

### Saving Project State

The plugin serializes complete time-slider configuration via `getProjectState()`:

```typescript
getProjectState() {
  return {
    timeSlider: {
      sources: this.control.getSources(),
      dateRange: this.control.getDateRange(),
      granularity: this.control.getGranularity(),
      // undefined values stripped for clean JSON
    }.filter(([_, v]) => v !== undefined)
  };
}

```

### Restoring from Configuration

On project load, `applyProjectState()` validates and normalizes configuration before reconstruction:

```typescript
applyProjectState(state) {
  const config = normalizeConfig(state.timeSlider);
  const control = controlFromConfig(config);
  this.activate(control);
}

```

## Auto-Close Behavior and Idle Detection

The dock implements automatic cleanup when no temporal data remains active. In [`time-slider-dock.ts`](https://github.com/opengeos/GeoLibre/blob/main/time-slider-dock.ts), `shouldCloseTimeSliderDock()` evaluates three conditions:

```typescript
function shouldCloseTimeSliderDock(): boolean {
  return openedByBinding &&           // Dock was auto-opened by binding
         pluginActive &&              // Plugin is currently active
         isTimeSliderIdle();          // No bound layers, no sources, no KML TimeSpans
}

```

The `isTimeSliderIdle()` helper checks:
- Zero bound vector layers
- Zero dock sources (`control.getSources().length === 0`)
- Zero active KML `<TimeSpan>` overlays

This prevents orphaned UI elements when users remove time-aware layers (addresses GitHub issue #1512).

## Practical Implementation Examples

### Adding a COG Time Series via API

```typescript
import { TimeSliderControl, type SourceSpec } from 'maplibre-gl-time-slider';

const landsatSpec: SourceSpec = {
  id: 'landsat-8-ndvi',
  type: 'cog',
  url: 'https://landsat-data.s3.amazonaws.com/ndvi-timeseries.tif',
  name: 'Landsat 8 NDVI (2013-2023)',
  visible: true,
  opacity: 0.9,
  // Optional: explicit date range override
  dateRange: ['2013-04-11', '2023-04-11']
};

const control = map.getControl('time-slider') as TimeSliderControl;
control.addSource(landsatSpec);

```

The source automatically appears in the dock's source list and the GeoLibre layer tree.

### Programmatic Vector Layer Binding

```typescript
import { useAppStore } from '@geolibre/core';
import { 
  detectTimeProperties, 
  buildTimeBinding,
  buildTimeFilter 
} from '@geolibre/plugins';

async function bindLayerToSlider(layerId: string) {
  const store = useAppStore.getState();
  const layer = store.layers.find(l => l.id === layerId);
  
  if (layer?.type !== 'vector') return;
  
  // Detect candidates from feature sample
  const sampleFeatures = await fetchSampleFeatures(layer.source);
  const candidates = detectTimeProperties(sampleFeatures);
  
  if (candidates.length === 0) {
    console.warn('No time properties detected');
    return;
  }
  
  // Create binding with automatic granularity detection
  const binding = buildTimeBinding(sampleFeatures, candidates[0].property);
  
  // Update layer metadata to enable slider driving
  store.updateLayer(layerId, {
    metadata: { timeBinding: binding },
    // Initial filter for full time range
    timeFilter: buildTimeFilter(binding, binding.min, binding.max)
  });
}

```

### Conditional Dock Closure

```typescript
import { 
  isTimeSliderIdle, 
  setTimeSliderOpenedByBinding,
  shouldCloseTimeSliderDock 
} from './time-slider-dock';

// When user removes a time-bound layer
function onLayerRemove(layerId: string) {
  const wasBound = store.layers.find(l => l.id === layerId)?.metadata?.timeBinding;
  
  if (wasBound && setTimeSliderOpenedByBinding(true)) {
    // Check if any temporal content remains
    if (shouldCloseTimeSliderDock()) {
      geoLibreApp.deactivatePlugin('maplibre-gl-time-slider');
    }
  }
}

```

## Key Implementation Files

| File | Lines | Purpose |
|------|-------|---------|
| [`packages/plugins/src/plugins/maplibre-time-slider.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-time-slider.ts) | 1,160 | Main plugin: activation, store sync, source mirroring, range reconciliation |
| [`packages/plugins/src/plugins/time-slider-binding.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/time-slider-binding.ts) | 490 | Timestamp detection, `TimeBinding` construction, filter generation |
| [`apps/geolibre-desktop/src/lib/time-slider-dock.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/time-slider-dock.ts) | 52 | Dock state management, theme synchronization, auto-close logic |
| [`packages/plugins/src/plugins/time-slider-source-url.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/time-slider-source-url.ts) | — | URL parsing utilities for mosaic manifest detection |
| [`packages/plugins/src/plugins/time-slider-pixel-identify.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/time-slider-pixel-identify.ts) | — | Raster pixel value querying for chart visualization |
| [`packages/plugins/src/plugins/time-slider-pixel-series.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/time-slider-pixel-series.ts) | — | Time-series chart generation for selected pixels |

## Summary

- **Three-component architecture** separates UI control ([`time-slider-dock.ts`](https://github.com/opengeos/GeoLibre/blob/main/time-slider-dock.ts)), data semantics ([`time-slider-binding.ts`](https://github.com/opengeos/GeoLibre/blob/main/time-slider-binding.ts)), and system integration ([`maplibre-time-slider.ts`](https://github.com/opengeos/GeoLibre/blob/main/maplibre-time-slider.ts))

- **Automatic format detection** handles epoch seconds, milliseconds, ISO dates, and bare years through `detectTimeProperties()` and `parseTimeValue()`

- **Source mirroring** creates GeoLibre store layers for every time-slider source, enabling unified layer management with `sourceKind: "time-slider"` metadata

- **Dynamic filtering** generates MapLibre-compatible expressions via `buildTimeFilter()`, applied on every timeline tick through `applyBoundFilters()`

- **State persistence** serializes complete configuration via `getProjectState()` and restores via `normalizeConfig()` and `controlFromConfig()`

- **Auto-cleanup** removes idle docks when `isTimeSliderIdle()` detects no bound layers, sources, or KML TimeSpans

## Frequently Asked Questions

### What raster formats does the GeoLibre time slider support?

The time slider supports **Cloud-Optimized GeoTIFFs (COG)** and **raster mosaics** through the `maplibre-gl-raster` library. COGs are accessed via single-file URLs with internal tiling, while mosaics reference multi-date stacks with external metadata manifests. Zarr data cubes are supported through additional adapters. The source type is specified in the `SourceSpec` as `type: 'cog'` or `type: 'mosaic'`.

### How does the plugin detect which vector property contains timestamps?

The `detectTimeProperties()` function in [`time-slider-binding.ts`](https://github.com/opengeos/GeoLibre/blob/main/time-slider-binding.ts) samples features and scores properties by temporal pattern matching. It evaluates coverage (percentage of non-null values), value distribution, and format consistency. Properties with epoch values, ISO date strings, or year integers receive higher scores. The highest-scoring candidate becomes the default binding target, though users can override this selection through the UI.

### Can the time slider animate through irregular time intervals?

Yes. The `TimeBinding` object includes `min`, `max`, and `granularity` properties derived from actual data extent. For irregular intervals—common in satellite imagery collections—the slider advances to the next available timestamp rather than fixed calendar increments. The `cumulative` flag enables accumulation modes where all data up to the current date remains visible, useful for change-detection visualizations.

### What triggers automatic dock closure, and how do I prevent it?

The dock closes automatically when three conditions align: it was originally opened by a binding (not manually), the plugin is active, and `isTimeSliderIdle()` returns true (no bound layers, no sources, no KML TimeSpan overlays). To prevent closure, ensure at least one time-aware layer remains bound, add a persistent source via `control.addSource()`, or manually open the dock to clear the `openedByBinding` flag.