Skip to main content

Animate Point Along Line

Create a beautiful, smooth animation of a moving icon (car, plane, ship, person) traveling along any route — with automatic rotation to follow the path.


Features

  • Smooth 60fps animation using requestAnimationFrame
  • Automatic bearing rotation (icon faces direction of travel)
  • Replay button
  • Works with any route (curved or straight)
  • Powered by Turf.js for precise geodesic calculations

Full Working Example

<!DOCTYPE html>
<html lang="en">
<head>
<title>Animate Point Along Line - Barikoi GL</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

<!-- Barikoi GL + Turf.js -->
<!-- Barikoi GL: Mapping library for rendering maps -->
<!-- Turf.js: Geospatial analysis library for calculating distances, bearings, etc. -->
<link
rel="stylesheet"
href="https://unpkg.com/bkoi-gl@latest/dist/style/bkoi-gl.css"
/>
<script src="https://unpkg.com/bkoi-gl@latest/dist/iife/bkoi-gl.js"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>

<style>
body {
margin: 0;
padding: 0;
}
html,
body,
#map {
height: 100%;
background: #0f172a;
}

.controls {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
}
.btn {
background: #0066ff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 12px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 20px rgba(0, 102, 255, 0.4);
transition: all 0.3s;
}
.btn:hover {
background: #0052cc;
transform: translateY(-2px);
}
</style>
</head>
<body>
<div id="map"></div>
<div class="controls">
<button class="btn" id="replay">Replay Animation</button>
</div>

<script>
// Initialize the Barikoi GL map instance
const map = new bkoigl.Map({
container: "map", // HTML element ID where map will be rendered
style: "https://map.barikoi.com/styles/barikoi-dark-mode/style.json", // Map style URL
center: [90.0, 23.7], // Initial center coordinates [longitude, latitude] - Bangladesh region
zoom: 6.5, // Initial zoom level
accessToken: "YOUR_BARIKOI_API_KEY", // Barikoi API access token
});

// Define route coordinates: Dhaka → Chattogram (simplified waypoints)
// Array of [longitude, latitude] pairs representing the route
const routeCoords = [
[88.59985365588938, 24.373238145585873],
[89.37652034873946, 24.84771512318889],
[89.83676727783558, 24.75445662493459],
[90.40796659162504, 24.739528759994712],
[90.82715945250106, 24.403197355449322],
[91.87344753731958, 24.896285987463315],
];

// Create a GeoJSON LineString from the route coordinates using Turf.js
const line = turf.lineString(routeCoords);

// Calculate total path length in kilometers
const pathLength = turf.length(line, { units: "kilometers" });

// Number of interpolation points for smooth animation
// Higher number = smoother animation but more processing
const steps = 600;

// Array to store interpolated coordinates for smooth animation
const arc = [];

// Generate smooth curved path by interpolating points along the route
// This creates intermediate points between the waypoints for fluid animation
for (let i = 0; i <= pathLength; i += pathLength / steps) {
// Get a point at distance 'i' along the line
const segment = turf.along(line, i, { units: "kilometers" });
arc.push(segment.geometry.coordinates);
}

// Create GeoJSON objects for the complete route and starting point
const route = turf.lineString(arc); // Complete interpolated route
const point = turf.point(arc[0]); // Moving point, starts at first coordinate

// Counter to track current position in the animation sequence
let counter = 0;

// Wait for map to fully load before adding layers and starting animation
map.on("load", async () => {
// Load custom icon image (delivery van) from external URL
// This icon will represent the moving point on the map
const img = await map.loadImage(
"https://cdn-icons-png.flaticon.com/512/7893/7893979.png"
);
// Add the loaded image to the map's sprite with identifier "delivery-van"
map.addImage("delivery-van", img.data, { pixelRatio: 2 });

// Add the route line as a data source
map.addSource("route", {
type: "geojson",
data: route, // GeoJSON LineString containing all interpolated points
});

// Add a layer to visualize the route line
map.addLayer({
id: "route-line",
type: "line",
source: "route",
paint: {
"line-color": "#60a5fa", // Light blue color
"line-width": 5, // Line thickness in pixels
"line-opacity": 0.8, // 80% opacity for subtle appearance
},
});

// Add the moving point as a data source
map.addSource("point", {
type: "geojson",
data: point, // GeoJSON Point that will be updated during animation
});

// Add a layer to display the moving icon
map.addLayer({
id: "moving-point",
type: "symbol", // Symbol layer type for displaying icons/text
source: "point",
layout: {
"icon-image": "delivery-van", // Use the loaded icon
"icon-size": 0.3, // Scale icon to 30% of original size
// "icon-rotate": ["get", "bearing"], // Uncomment to rotate icon based on direction
"icon-rotation-alignment": "map", // Icon rotates with map rotation
"icon-allow-overlap": true, // Allow icon to overlap other symbols
"icon-ignore-placement": true, // Don't hide icon due to collision detection
},
});

// Animation function - moves the point along the route
function animate() {
// Stop animation when we reach the end of the route
if (counter >= arc.length) return;

// Get current coordinate from the interpolated path
const coords = arc[counter];

// Update the point's position to current coordinate
point.geometry.coordinates = coords;

// Calculate bearing (direction angle) for icon rotation
// Bearing is the angle between north and the direction of movement
const bearing =
counter < arc.length - 1
? // Calculate bearing from current point to next point
turf.bearing(turf.point(coords), turf.point(arc[counter + 1]))
: // At last point, use bearing from previous to current point
turf.bearing(turf.point(arc[counter - 1]), turf.point(coords));

// Store bearing in point properties (can be used with icon-rotate expression)
point.properties.bearing = bearing;

// Update the map source with the new point position
map.getSource("point").setData(point);

// Move to next position in the sequence
counter++;

// Request next animation frame for smooth 60fps animation
requestAnimationFrame(animate);
}

// Start the animation sequence
animate();

// Replay button functionality - resets and restarts the animation
document.getElementById("replay").addEventListener("click", () => {
counter = 0; // Reset counter to start
point.geometry.coordinates = arc[0]; // Move point back to beginning
point.properties.bearing = 0; // Reset bearing
map.getSource("point").setData(point); // Update map with reset point
animate(); // Start animation again
});
});
</script>
</body>
</html>

IconUse Case
CarLive delivery tracking
PlaneFlight path animation
ShipMaritime route visualization
PersonWalking tour / pilgrimage
BusPublic transit simulation

Customization Tips

Want to change...How to do it
SpeedReduce steps (e.g., 300 = faster)
SmoothnessIncrease steps (e.g., 1000 = ultra smooth)
IconReplace image URL with your own
RouteUse real GPS track or routing API
Auto-loopReset counter = 0 when animation ends

Advanced: Real-time Vehicle Tracking

// Simulate live GPS updates
setInterval(() => {
const newCoord = getLiveGPS(); // your API
arc.push(newCoord);
route.geometry.coordinates = arc;
map.getSource("route").setData(route);
}, 5000);

Bring your routes to life — beautifully.