Animated traveling map with Leaflet
Tags: leaflet, javascript
Published: 2024-03-10 17:47:03

In this tutorial, we'll learn how to animate a marker with [Leaflet](https://leafletjs.com/), without other 3rd party library or any Leaflet plugin.

[Leaflet](https://leafletjs.com/) is the most famous open-source map library, with lots of plugins. 2 of them are used to animate a marker on the map: 
- [Hallahan Leaflet Playback](https://github.com/hallahan/LeafletPlayback)
- [Linghuam Track Playback](https://github.com/linghuam/Leaflet.TrackPlayBack)
- [Leaflet Motion](https://github.com/Igor-Vladyka/leaflet.motion)

Unfortunately, they're not maintained anymore and have lots of issue to work with the recent Leaflet versions and the modern Javascript frameworks.

However, they're not essentials to animate our own markers or tracks. Here's how to do it.

## Project Initialization

Let's create a Leaflet container in our `index.html` file:

```html
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
  </head>

  <body>
    <div id="leaflet-container" style="width: 70vw; height: 70vh; margin: 50px auto"></div>
  </body>
  <script src="script.js"></script>
</html>
```

Then initialize Leaflet in our `script.js` file:

```js
const leafletMap = L.map(document.getElementById("leaflet-container")).setView([0, 0], 1);
```

Here's our dataset: an array of random coordinates with timestamps, generated with a loop:

```js
const dataset = [{
  lng: -100,
  lat: -80,
  timestamp: new Date().getTime()
}];
let timestamp = 1710067901850;

for (let i = 1; i < 300; i++) {
  dataset.push({
    lng: dataset[i - 1].lng + Math.random(),
    lat: dataset[i - 1].lat + Math.random(),
    timestamp: dataset[i - 1].timestamp + 500
  });
};
```

Finally, we'll create the marker from the origin position and add it to the map:

```js
let marker = L.marker(dataset[0], { icon: new L.Icon({ iconUrl: '/marker.svg', iconSize: [20, 20], iconAnchor: [10, 20] }) });
marker.addTo(leafletMap);
```

## Animate The Marker

Now we'll create the `start()` function which use an interval to tick the clock every second. 

The tick function will move the marker position during the period between the first and the last timestamp:

```js
let tick = dataset[0].timestamp;
const endTime = dataset[dataset.length - 1].timestamp;
let interval;
function start() {
  interval = setInterval(() => {
    if (tick > endTime) {
      // End of animation: leave tracks and markers where they are but stop intervals
      stop();
    } else {
      // Create or update the marker
      moveMarker();

      // Update tick including speed
      tick += 500;
    }
  }, 100);
}
```

The `stop()` function removes the interval, moves the marker back to its origin, and reset the tick:

```js
function stop() {
  clearInterval(interval);
  tick = dataset[0].timestamp;
  marker.setLatLng(dataset[0]);
}
```

Let's create our `moveMarker()` function: first it loops on our dataset to find the timestamp closest to our tick. Then it moves the marker by updating the marker's coordinates «_latlng_» property:

```js
function moveMarker() {
  // Look for the timestamp just before tick
  let closestPointIndex = -1;
  for (let i = 0; i < dataset.length && closestPointIndex < 0; i += 1) {
    const timestamp = dataset[i].timestamp;
    if (timestamp > tick) {
      closestPointIndex = i === 0 ? 0 : i - 1;
    }
  }
  marker.setLatLng(dataset[closestPointIndex]);
}
```

## Draw the path

Let's improve our animation by drawing also the track. First, we create a polyline just after our marker:

```js
let path = L.polyline(dataset[0]);
path.addTo(leafletMap);
```

And update the coordinates just like we did for the marker:

```js
  marker.setLatLng(dataset[closestPointIndex]);
  path.setLatLngs(dataset.slice(0, closestPointIndex + 1));
```

Here's the result:


![a marker in motion and its path is drawn](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe60uppnnxb7d8y1eyol.gif)

## Add playback controls

We already have the `start()` and `stop()` functions.
Now let's add some controls functions, like `pause()`: it's simple as it's the same than `stop()` but doesn't reset the tick:

```js
function pause() {
  clearInterval(interval);
}
```

Like this, the `start()` function will continue from where it was paused.

We can add our playback buttons, which are bound to their corresponding functions:

![play stop and pause buttons](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsgvlur891yd19vm08l9.jpg)

Here's the HTML code:

```html
  <style>
    button {
      background-color: slateblue;
      border: 1px solid skyblue;
      color: lightblue;
      font-size: 1.25em;
      padding: 10px 20px;
      width: 70px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    button:hover {
      background-color: darkslateblue;
      color: white;
    }
  </style>

  <body>
    <section style="display: flex; width: 200px; margin: auto">
      <button
        onclick="start()"
        style="border-radius: 10px 0 0 10px; font-size: 1.5em"
      >
        ▶
      </button>
      <button
        onclick="pause()"
        style="border-radius: 0"
      >
        ❚❚
      </button>
      <button
        onclick="stop()"
        style="border-radius: 0 10px 10px 0; font-size: 2em; padding-top: 2px"
      >
        ■
      </button>   
    </section>
```

## Conclusion

In this article, we've coded an animated traveling map with Leaflet.
This solution is really simple and doesn't require third-party plugins.

It's also scalable as it was tester with 20 simultaneous travels.

You can find the working website [in this stackblitz](https://stackblitz.com/edit/stackblitz-starters-isdobg?file=index.html).