Skip to content

Animate a Line

Draw a line on the map that animates progressively, revealing a path step by step.

How It Works

Use setData() on a GeoJSON source to progressively add coordinates to a LineString, driven by requestAnimationFrame or setTimeout.

Key Pattern

javascript
// 1. Add source with empty line
map.addSource('route', {
  type: 'geojson',
  data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } }
});

// 2. Add line layer
map.addLayer({
  id: 'route-line',
  type: 'line',
  source: 'route',
  paint: { 'line-color': '#3b82f6', 'line-width': 3 }
});

// 3. Animate by adding one coordinate at a time
let step = 0;
const coordinates = [[lng1, lat1], [lng2, lat2], ...];

function animate() {
  if (step >= coordinates.length) return;
  step++;

  map.getSource('route').setData({
    type: 'Feature',
    geometry: { type: 'LineString', coordinates: coordinates.slice(0, step) }
  });

  setTimeout(() => requestAnimationFrame(animate), 300);
}

animate();

Smooth Animation with Interpolation

For very smooth animation between two points:

javascript
const from = [0, 0];
const to = [10, 10];
const steps = 100;
let i = 0;

function animate() {
  if (i > steps) return;
  const t = i / steps;
  const lng = from[0] + (to[0] - from[0]) * t;
  const lat = from[1] + (to[1] - from[1]) * t;

  map.getSource('route').setData({
    type: 'Feature',
    geometry: { type: 'LineString', coordinates: [from, [lng, lat]] }
  });

  i++;
  requestAnimationFrame(animate);
}

Complete Example

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
    <script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
    <style>
      #map { height: 500px; width: 100%; }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <button onclick="startAnimation()">▶ Animate</button>
    <script>
      const map = new mapmetricsgl.Map({
        container: 'map',
        style: '<StyleFile_URL_with_Token>',
        center: [2.349902, 48.852966],
        zoom: 4
      });

      map.addControl(new mapmetricsgl.NavigationControl(), 'top-right');

      const coordinates = [
        [2.349902, 48.852966],
        [-0.1276, 51.5074],
        [13.405, 52.52],
        [16.3738, 48.2082],
        [12.4964, 41.9028],
      ];

      map.on('load', () => {
        map.addSource('route', {
          type: 'geojson',
          data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } }
        });

        map.addLayer({
          id: 'route-line',
          type: 'line',
          source: 'route',
          paint: { 'line-color': '#3b82f6', 'line-width': 3 }
        });
      });

      let step = 0;
      function startAnimation() {
        step = 0;
        function animate() {
          if (step >= coordinates.length) return;
          step++;
          map.getSource('route').setData({
            type: 'Feature',
            geometry: { type: 'LineString', coordinates: coordinates.slice(0, step) }
          });
          setTimeout(() => requestAnimationFrame(animate), 400);
        }
        animate();
      }
    </script>
  </body>
</html>
jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const coordinates = [
  [2.349902, 48.852966],
  [-0.1276, 51.5074],
  [13.405, 52.52],
  [16.3738, 48.2082],
  [12.4964, 41.9028],
];

const AnimateLine = () => {
  const mapContainer = useRef(null);
  const map = useRef(null);
  const timer = useRef(null);

  useEffect(() => {
    if (map.current) return;

    map.current = new mapmetricsgl.Map({
      container: mapContainer.current,
      style: '<StyleFile_URL_with_Token>',
      center: [2.349902, 48.852966],
      zoom: 4
    });

    map.current.addControl(new mapmetricsgl.NavigationControl(), 'top-right');

    map.current.on('load', () => {
      map.current.addSource('route', {
        type: 'geojson',
        data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } }
      });

      map.current.addLayer({
        id: 'route-line',
        type: 'line',
        source: 'route',
        paint: { 'line-color': '#3b82f6', 'line-width': 3 }
      });
    });

    return () => { map.current?.remove(); map.current = null; };
  }, []);

  const startAnimation = () => {
    let step = 0;
    const animate = () => {
      if (step >= coordinates.length) return;
      step++;
      map.current?.getSource('route')?.setData({
        type: 'Feature',
        geometry: { type: 'LineString', coordinates: coordinates.slice(0, step) }
      });
      timer.current = setTimeout(() => requestAnimationFrame(animate), 400);
    };
    animate();
  };

  return (
    <div>
      <div ref={mapContainer} style={{ height: '500px', width: '100%' }} />
      <button onClick={startAnimation} style={{ marginTop: '8px', padding: '8px 16px', background: '#3b82f6', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>
        ▶ Animate
      </button>
    </div>
  );
};

export default AnimateLine;

For more information, visit the MapMetrics GitHub repository.