Skip to content

opengeos/maplibre-gl-time-slider

Repository files navigation

maplibre-gl-time-slider

A MapLibre GL JS plugin for visualizing time series vector and raster data with an interactive time slider control.

npm version License: MIT Open in CodeSandbox Open in StackBlitz

Features

  • Interactive time slider with play/pause controls
  • Support for both vector and raster time series data
  • Built-in TiTiler integration for Cloud Optimized GeoTIFFs (COGs)
  • Add Layer button for persisting time periods as permanent layers for comparison
  • Customizable playback speed and loop settings
  • Control layer ordering with beforeId parameter
  • React component and hooks support
  • TypeScript support with full type definitions
  • Lightweight and easy to integrate

Installation

npm install maplibre-gl-time-slider

Quick Start

Basic Usage (Vanilla JavaScript/TypeScript)

import maplibregl from 'maplibre-gl';
import { TimeSliderControl } from 'maplibre-gl-time-slider';
import 'maplibre-gl-time-slider/style.css';

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  center: [0, 0],
  zoom: 2,
});

map.on('load', () => {
  const timeSlider = new TimeSliderControl({
    title: 'Time Slider',
    labels: ['2024-01', '2024-02', '2024-03', '2024-04'],
    speed: 1000,
    loop: true,
    onChange: (index, label) => {
      console.log(`Current: ${label} (index: ${index})`);
      // Update your map layers here
    },
  });

  map.addControl(timeSlider, 'top-right');
});

React Usage

import { useState, useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import { TimeSliderControlReact } from 'maplibre-gl-time-slider/react';
import 'maplibre-gl-time-slider/style.css';

function MyMap() {
  const mapContainer = useRef<HTMLDivElement>(null);
  const [map, setMap] = useState<maplibregl.Map | null>(null);

  useEffect(() => {
    if (!mapContainer.current) return;

    const mapInstance = new maplibregl.Map({
      container: mapContainer.current,
      style: 'https://demotiles.maplibre.org/style.json',
      center: [0, 0],
      zoom: 2,
    });

    mapInstance.on('load', () => setMap(mapInstance));

    return () => mapInstance.remove();
  }, []);

  return (
    <>
      <div ref={mapContainer} style={{ width: '100%', height: '100vh' }} />
      {map && (
        <TimeSliderControlReact
          map={map}
          title="Time Slider"
          labels={['2024-01', '2024-02', '2024-03']}
          onChange={(index, label) => {
            // Update map layers
          }}
        />
      )}
    </>
  );
}

Examples

Vector Data (Filtering by Time)

Filter vector features by a time property:

const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

const timeSlider = new TimeSliderControl({
  labels: months,
  onChange: (index, label) => {
    // Filter vector layer by month
    map.setFilter('my-layer', ['==', ['get', 'month'], index]);
  },
});

Raster Data (TiTiler Integration)

Display time series raster data using TiTiler:

import { TimeSliderControl, buildTiTilerTileUrl } from 'maplibre-gl-time-slider';

const rasterData = {
  '2024-01': 'https://example.com/cog_2024_01.tif',
  '2024-02': 'https://example.com/cog_2024_02.tif',
  '2024-03': 'https://example.com/cog_2024_03.tif',
};

const labels = Object.keys(rasterData);
const urls = Object.values(rasterData);

// Add initial raster source
map.addSource('raster-data', {
  type: 'raster',
  tiles: [
    buildTiTilerTileUrl({
      url: urls[0],
      colormap: 'viridis',
    }),
  ],
  tileSize: 256,
});

map.addLayer({
  id: 'raster-layer',
  type: 'raster',
  source: 'raster-data',
});

// Create time slider
const timeSlider = new TimeSliderControl({
  labels: labels,
  onChange: (index, label) => {
    const source = map.getSource('raster-data');
    source.setTiles([
      buildTiTilerTileUrl({
        url: urls[index],
        colormap: 'viridis',
      }),
    ]);
  },
});

Adding Persistent Layers for Comparison

Enable the "Add Layer" button to allow users to persist specific time periods as permanent layers:

let layerCounter = 0;

const timeSlider = new TimeSliderControl({
  labels: ['2020', '2021', '2022', '2023'],
  onChange: (index, label) => {
    // Update the main time slider layer
    const source = map.getSource('main-raster');
    source.setTiles([getTileUrlForYear(label)]);
  },
  onAddLayer: (index, label, beforeId) => {
    // Create a persistent layer for the selected time period
    layerCounter++;
    const sourceId = `persistent-source-${label}`;
    const layerId = `Persistent Layer ${label}`;

    // Add source
    map.addSource(sourceId, {
      type: 'raster',
      tiles: [getTileUrlForYear(label)],
      tileSize: 256,
    });

    // Add layer before the main time slider layer
    map.addLayer({
      id: layerId,
      type: 'raster',
      source: sourceId,
      paint: {
        'raster-opacity': 0.7,
      },
    }, beforeId);

    console.log(`Added persistent layer for ${label}`);
  },
  beforeId: 'main-layer-id', // Insert persistent layers before this layer
});

This feature is useful for:

  • Comparing data across multiple time periods
  • Creating side-by-side visualizations
  • Highlighting specific time periods for analysis
  • Building temporal comparisons in the layer control

API Reference

TimeSliderControl

Main control class that implements MapLibre's IControl interface.

Constructor Options

Option Type Default Description
labels string[] [] Array of labels to display (required)
title string 'Time Slider' Title displayed in the panel header
collapsed boolean true Whether to start with panel collapsed
position string 'top-right' Control position on the map
panelWidth number 300 Width of the panel in pixels
initialIndex number 0 Initial index to start at
speed number 1000 Playback speed in milliseconds
loop boolean true Whether to loop playback
className string '' Custom CSS class name
onChange function - Callback when index changes: (index: number, label: string) => void
onAddLayer function - Callback when "Add Layer" button is clicked: (index: number, label: string, beforeId?: string) => void
beforeId string - Layer ID to insert new persistent layers before (ensures proper layer ordering)

Methods

Method Description
play() Start playback
pause() Pause playback
togglePlayback() Toggle play/pause state
next() Move to next label
prev() Move to previous label
goTo(index) Navigate to specific index
setSpeed(ms) Set playback speed
setLoop(enabled) Set loop state
setLabels(labels, resetIndex?) Update labels
getCurrentIndex() Get current index
getCurrentLabel() Get current label
getLabels() Get all labels
getState() Get full state object
toggle() Toggle panel collapsed state
expand() Expand panel
collapse() Collapse panel
on(event, handler) Register event listener
off(event, handler) Remove event listener

Events

Event Description
change Fired when the current index changes
play Fired when playback starts
pause Fired when playback pauses
collapse Fired when panel collapses
expand Fired when panel expands
statechange Fired on any state change

TiTiler Utilities

buildTiTilerTileUrl(options)

Builds a TiTiler XYZ tile URL for MapLibre raster sources.

const tileUrl = buildTiTilerTileUrl({
  url: 'https://example.com/my-cog.tif',
  endpoint: 'https://giswqs-titiler-endpoint.hf.space',  // optional, default
  colormap: 'viridis',              // optional, default
  rescale: [-10, 10],               // optional
  bidx: [1],                        // optional, band indexes
});

getTiTilerBounds(url, endpoint?)

Fetches the bounds of a COG file.

const bounds = await getTiTilerBounds('https://example.com/my-cog.tif');
map.fitBounds(bounds);

getTiTilerInfo(url, endpoint?)

Fetches metadata about a COG file.

getTiTilerStatistics(url, endpoint?)

Fetches statistics (min, max, mean, std) for each band.

Development

# Install dependencies
npm install

# Start development server
npm run dev

# Build library
npm run build

# Build examples
npm run build:examples

# Run tests
npm test

# Lint code
npm run lint

# Format code
npm run format

Docker

The examples can be run using Docker. The image is automatically built and published to GitHub Container Registry.

Pull and Run

# Pull the latest image
docker pull ghcr.io/opengeos/maplibre-gl-time-slider:latest

# Run the container
docker run -p 8080:80 ghcr.io/opengeos/maplibre-gl-time-slider:latest

Then open http://localhost:8080/maplibre-gl-time-slider/ in your browser to view the examples.

Build Locally

# Build the image
docker build -t maplibre-gl-time-slider .

# Run the container
docker run -p 8080:80 maplibre-gl-time-slider

Available Tags

Tag Description
latest Latest release
x.y.z Specific version (e.g., 1.0.0)
x.y Minor version (e.g., 1.0)

License

MIT License - see LICENSE for details.

About

A MapLibre GL JS plugin for visualizing time series vector and raster data

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors