Emma
Emma

Reputation: 57

Mapbox javascript : to animate marker on a polyline (points)

I need to add a marker that moves on the points (polyline), I tried with https://docs.mapbox.com/mapbox-gl-js/example/animate-point-along-route but it's for a line, I have many points.. I don't know how to do..I tried this Animate an icon by using serious of Lat/lon Array - Mapbox js but it doesn't work.. thanks for your help !

var marker = new mapboxgl.Marker({
    color: '#EC0868',
    className: 'marker'
  }) 
  .setLngLat([6.161062, 45.36362])
  .addTo(map);
 
  var coords = [
    [6.157903, 45.361839],
    [6.15835, 45.361932],
    [6.161408,45.3634445],
    [6.161558, 45.363508],
    [6.161307, 45.363462],
    [6.1610, 45.363570],
    [6.161057, 45.36362]
];
map.on('load', function () {
    fitMap(map, coords);
    displayJourney(map, coords);
});

function fitMap(map, coords) {
    var bounds = coords.reduce(function (bounds, coord) {
        return bounds.extend(coord);
    }, new mapboxgl.LngLatBounds(coords[0], coords[0]));
    map.fitBounds(bounds, {
        padding: 30
    });
}

function displayJourney(map, coords) {
    map.addLayer({
        "id": "journey",
        "type": "line",
        "source": {
            "type": "geojson",
            "data": {
                "type": "Feature",
                "properties": {},
                "geometry": {
                    "type": "LineString",
                    "coordinates": coords
                }
            }
        },
        "paint": {
            "line-color": "#888", 
            "line-width": 4, 
              'line-dasharray': [2, 2]
        }
    });
}

Upvotes: 1

Views: 2960

Answers (2)

Yoshikage Ochi
Yoshikage Ochi

Reputation: 211

I combined Emma's and Moritz's code below;

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Animate a point along a route</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v2.0.1/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.0.1/mapbox-gl.css" rel="stylesheet" />
<style>
    body { margin: 0; padding: 0; }
    #map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<style>
    .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>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@5/turf.min.js"></script>

<div id="map"></div>
<div class="overlay">
    <button id="replay">Replay</button>
</div>
<script>
    mapboxgl.accessToken = 'pk.eyJ1IjoieW9jaGkiLCJhIjoiY2tjZThvdWExMDV2dDJxcDgxZzBwbzlxYSJ9.M0yRA6SXDMRgXzXGuYnvsg';
    var map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/mapbox/streets-v11',
        center: [6.161062, 45.36362],
        zoom: 9
    });

    var coords = [
        [6.157903, 45.361839],
        [6.15835, 45.361932],
        [6.161408,45.3634445],
        [6.161558, 45.363508],
        [6.161307, 45.363462],
        [6.1610, 45.363570],
        [6.161057, 45.36362]
    ];

    var route = {
        'type': 'FeatureCollection',
        'features': [
            {
                'type': 'Feature',
                'geometry': {
                    'type': 'LineString',
                    'coordinates': coords
                }
            }
        ]
    };

    var marker = new mapboxgl.Marker({
        color: '#EC0868',
        className: 'marker'});

    // Calculate the distance in kilometers between route start/end point.
    var lineDistance = turf.length(route.features[0]);

    var arc = [];

    // Number of steps to use in the arc and animation, more steps means
    // a smoother arc and animation, but too many steps will result in a
    // low frame rate
    var steps = 500;

    // Draw an arc between the `origin` & `destination` of the two points
    for (var i = 0; i < lineDistance; i += lineDistance / steps) {
        var segment = turf.along(route.features[0], i);
        arc.push(segment.geometry.coordinates);
    }

    // Update the route with calculated arc coordinates
    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 () {
        // Add a source and layer displaying a point which will be animated in a circle.
        map.addSource('route', {
            'type': 'geojson',
            'data': route
        });

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

        marker.setLngLat(route.features[0].geometry.coordinates[0]).addTo(map);

        fitMap(map, coords);

        function animate() {
            // Update point geometry to a new position based on counter denoting
            // the index to access the arc
            marker.setLngLat(route.features[0].geometry.coordinates[counter]);

            counter = counter + 1;

            // Request the next frame of animation as long as the end has not been reached
            if (counter < steps) {
                requestAnimationFrame(animate);
            }

        }

        document
            .getElementById('replay')
            .addEventListener('click', function () {
                // Set the coordinates of the original point back to origin
                marker.setLngLat(route.features[0].geometry.coordinates[0])

                // Reset the counter
                counter = 0;

                // Restart the animation
                animate(counter);
            });

        // Start the animation
        animate(counter);
    });

    function fitMap(map, coords) {
        var bounds = coords.reduce(function (bounds, coord) {
            return bounds.extend(coord);
        }, new mapboxgl.LngLatBounds(coords[0], coords[0]));
        map.fitBounds(bounds, {
            padding: 30
        });
    }

https://codepen.io/OttyLab/pen/GRjBjwq

Upvotes: 1

Moritz
Moritz

Reputation: 1790

Hi Emma you can still use the approach. It also works for multiple coordinates given. Please see my example below, which does it for 3 coordinates. (1 coordinate pair extra) You can add as many additional "waypoints" as you like:

(to make it work, replace "YOUR ACCCESS TOKEN" )

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Animate a point along a route</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v2.0.1/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.0.1/mapbox-gl.css" rel="stylesheet" />
<style>
    body { margin: 0; padding: 0; }
    #map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<style>
    .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>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@5/turf.min.js"></script>

<div id="map"></div>
<div class="overlay">
    <button id="replay">Replay</button>
</div>

<script>
    mapboxgl.accessToken = '<YOUR ACCESS TOKEN>';
    var map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/mapbox/streets-v11',
        center: [-96, 37.8],
        zoom: 3
    });

    // San Francisco
    var origin = [-122.414, 37.776];

    var waypoint1 = [-100.032, 38.913];
    // Washington DC
    var destination = [-77.032, 38.913];

    // A simple line from origin to destination.
    var route = {
        'type': 'FeatureCollection',
        'features': [
            {
                'type': 'Feature',
                'geometry': {
                    'type': 'LineString',
                    'coordinates': [origin, waypoint1, destination] // here you can add the additional waypoints
                }
            }
        ]
    };

    // A single point that animates along the route.
    // Coordinates are initially set to origin.
    var point = {
        'type': 'FeatureCollection',
        'features': [
            {
                'type': 'Feature',
                'properties': {},
                'geometry': {
                    'type': 'Point',
                    'coordinates': origin
                }
            }
        ]
    };

    // Calculate the distance in kilometers between route start/end point.
    var lineDistance = turf.length(route.features[0]);

    var arc = [];

    // Number of steps to use in the arc and animation, more steps means
    // a smoother arc and animation, but too many steps will result in a
    // low frame rate
    var steps = 500;

    // Draw an arc between the `origin` & `destination` of the two points
    for (var i = 0; i < lineDistance; i += lineDistance / steps) {
        var segment = turf.along(route.features[0], i);
        arc.push(segment.geometry.coordinates);
    }

    // Update the route with calculated arc coordinates
    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 () {
        // Add a source and layer displaying a point which will be animated in a circle.
        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': '#007cbf'
            }
        });

        map.addLayer({
            'id': 'point',
            'source': 'point',
            'type': 'symbol',
            'layout': {
                'icon-image': 'airport-15',
                'icon-rotate': ['get', 'bearing'],
                'icon-rotation-alignment': 'map',
                'icon-allow-overlap': true,
                'icon-ignore-placement': true
            }
        });

        function animate() {
            var start =
                route.features[0].geometry.coordinates[
                    counter >= steps ? counter - 1 : counter
                ];
            var end =
                route.features[0].geometry.coordinates[
                    counter >= steps ? counter : counter + 1
                ];
            if (!start || !end) return;

            // Update point geometry to a new position based on counter denoting
            // the index to access the arc
            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 calculated between the current point and the next point, except
            // at the end of the arc, which uses the previous point and the current point
            point.features[0].properties.bearing = turf.bearing(
                turf.point(start),
                turf.point(end)
            );

            // Update the source with this new data
            map.getSource('point').setData(point);

            // Request the next frame of animation as long as the end has not been reached
            if (counter < steps) {
                requestAnimationFrame(animate);
            }

            counter = counter + 1;
        }

        document
            .getElementById('replay')
            .addEventListener('click', function () {
                // Set the coordinates of the original point back to origin
                point.features[0].geometry.coordinates = origin;

                // Update the source layer
                map.getSource('point').setData(point);

                // Reset the counter
                counter = 0;

                // Restart the animation
                animate(counter);
            });

        // Start the animation
        animate(counter);
    });
</script>

</body>
</html>

Of course you can also create a list of coordinates, and pass this list to the geoJson route object.

Upvotes: 1

Related Questions