Skip to main content

Animate Point on Route

Animate a point along a route using Turf.js

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="https://maps-sdk.trimblemaps.com/v3/trimblemaps-3.20.0.css" />
        <script src="https://maps-sdk.trimblemaps.com/v3/trimblemaps-3.20.0.js"></script>
        <script src="https://unpkg.com/@turf/turf/turf.min.js"></script>
        <style>
            body { margin: 0; padding: 0; }
            #map {
                position: absolute;
                top: 0;
                bottom: 0;
                width: 100%;
          }
          .overlay {
              position: absolute;
              top: 10px;
              left: 10px;
          }
          .overlay button {
              font: 600 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
              background-color: #3386C0;
              color: #fff;
              display: inline-block;
              margin: 0;
              padding: 10px 20px;
              border: none;
              cursor: pointer;
              border-radius: 3px;
          }
          .overlay button:hover {
              background-color: #4EA0DA;
          }
        </style>
    </head>
      <body>
      <div id="map"></div>
      <div class= "overlay">
      <button id="replay">Replay</button>
      </div>
      <script>
        TrimbleMaps.APIKey = 'YOUR_API_KEY_HERE';
        const map = window.map = new TrimbleMaps.Map({
            container: 'map', // container id
            style: TrimbleMaps.Common.Style.TRANSPORTATION, //hosted style id
            center: [-96, 37.8], // starting position
            zoom: 3 // starting zoom
        });
        // Los Angeles
        var origin = [-118.2437, 34.0522];
        // NYC
        var destination = [-73.99906, 40.72185 ];
            var route = {
                type: 'FeatureCollection',
                features:[
                  {
                    type: 'Feature',
                    geometry: {
                      type: 'LineString',
                      coordinates: [origin, destination]
                    }
                  }
                ]
              };
            var point = {
                  type: 'FeatureCollection',
                  features:[
                    {
                      type: 'Feature',
                      properties: {},
                      geometry: {
                        type: 'Point',
                        coordinates: origin
                      }
                    }
                  ]
               };
            // Getting distance in miles between LA (origin) and NYC (destination)
            var length = turf.length(route.features[0], {units: 'miles'});
            var arc = [];
            var steps = 500;
            for (var i = 0; i < length; i += length / steps) {
                var segment = turf.along(route.features[0], i, {units: 'miles'});
                arc.push(segment.geometry.coordinates);
              }
            // Update route with calculated arc coord.
            route.features[0].geometry.coordinates = arc;
            // Used to increment the value of the point measurement against the route.
            var counter = 0;
            map.on('load', function() {
              map.addSource('route', {
                type: 'geojson',
                data: route
              });
              map.addSource('point', {
                type: 'geojson',
                data: point
              });
              map.addLayer({
                'id': 'route',
                'source': 'route',
                'type': 'line',
                'paint': {
                'line-width': 2,
                'line-color': '#005F9E'
                }
              });
              map.addLayer({
                'id': 'point',
                'source': 'point',
                'type': 'symbol',
                'layout': {
                  'icon-image': 'poi_airport',
                  'icon-rotate': ['get', 'bearing'],
                  'icon-rotation-alignment': 'map',
                  'icon-allow-overlap': true,
                  'icon-ignore-placement': true
                  }
                });
                // Update point coordinates based on counter denoting.
                 function animate() {
                  point.features[0].geometry.coordinates =
                    route.features[0].geometry.coordinates[counter];
                // Calculate the bearing to ensure the icon is rotated to match the route arc
                // The bearing is calculate between the current point and the next point, except
                // at the end of the arc use the previous point and the current point.
                  point.features[0].properties.bearing =
                  turf.bearing(
                    turf.point(
                      route.features[0].geometry.coordinates[
                          counter >= steps ?
                          counter - 1 : counter
                      ]
                    ),
                    turf.point(
                      route.features[0].geometry.coordinates[
                          counter >= steps ? counter : counter +1
                      ]
                    )
                  ) -53;
                map.getSource('point').setData(point);
                if (counter < steps) {
                  window.requestAnimationFrame(animate);
                  }
                  counter = counter + 1;
                  }
                  document.getElementById('replay').addEventListener('click', function() {
                    point.features[0].geometry.coordinates = origin;
                    map.getSource('point').setData(point);
                    counter = 0;
                    animate(counter);
                  });
                  animate(counter);
              });
        </script>
    </body>
</html>
Last updated June 15, 2023.