Animate a Point in Maptoolkit Maps JS
Animating a GeoJSON point layer requires recalculating the feature’s coordinates on each frame and passing a new FeatureCollection to setData on the source. This example computes a circular orbit using trigonometry and updates the source at 60fps via requestAnimationFrame. Use this pattern as a base for any animation where a point needs to follow a computed path rather than a recorded one.
const API_KEY = 'YOUR_API_KEY';
const center = [11.39085, 47.27574];
const radius = 0.05;
const point = {
type: 'Feature',
geometry: { type: 'Point', coordinates: [...center] }
};
const map = new maptoolkit.Map({
container: 'map',
apiKey: API_KEY,
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center,
zoom: 11,
attributionControl: { compact: false }
});
map.addControl(new maptoolkit.NavigationControl(), 'top-right');
map.on('load', () => {
map.addSource('point', { type: 'geojson', data: point });
map.addLayer({
id: 'point',
type: 'circle',
source: 'point',
paint: { 'circle-radius': 10, 'circle-color': '#ee8a65', 'circle-stroke-width': 2, 'circle-stroke-color': '#fff' }
});
function animate(timestamp) {
const t = (timestamp / 4000) % 1;
const angle = t * 2 * Math.PI;
point.geometry.coordinates = [
center[0] + radius * Math.cos(angle) / Math.cos(center[1] * Math.PI / 180),
center[1] + radius * Math.sin(angle)
];
map.getSource('point').setData(point);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
});<!DOCTYPE html>
<html lang="en">
<head>
<title>Animate a Point - Maptoolkit Maps JS</title>
<meta property="og:description" content="Animate a point in a circle using requestAnimationFrame and setData." />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
<style>
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
#map { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const API_KEY = 'YOUR_API_KEY';
const center = [11.39085, 47.27574];
const radius = 0.05;
const point = {
type: 'Feature',
geometry: { type: 'Point', coordinates: [...center] }
};
const map = new maptoolkit.Map({
container: 'map',
apiKey: API_KEY,
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center,
zoom: 11,
attributionControl: { compact: false }
});
map.addControl(new maptoolkit.NavigationControl(), 'top-right');
map.on('load', () => {
map.addSource('point', { type: 'geojson', data: point });
map.addLayer({
id: 'point',
type: 'circle',
source: 'point',
paint: { 'circle-radius': 10, 'circle-color': '#ee8a65', 'circle-stroke-width': 2, 'circle-stroke-color': '#fff' }
});
function animate(timestamp) {
const t = (timestamp / 4000) % 1;
const angle = t * 2 * Math.PI;
point.geometry.coordinates = [
center[0] + radius * Math.cos(angle) / Math.cos(center[1] * Math.PI / 180),
center[1] + radius * Math.sin(angle)
];
map.getSource('point').setData(point);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
});
</script>
</body>
</html>Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.
How it works
Each frame recalculates the point’s coordinates, rebuilds the FeatureCollection and calls
setData on the source. A GeoJSON source has no partial update, so the whole payload is
replaced and the renderer diffs it.
requestAnimationFrame is the right timer here, not setInterval. It runs in step
with the browser’s paint cycle, so frames are not computed and then thrown away, and it pauses
automatically when the tab is hidden.
Replacing a one-feature collection per frame is cheap. Doing the same with thousands of features is not, and that is where this pattern stops scaling.
Store the animation state in your own variables rather than reading it back off the source. Reading the rendered data each frame to work out where you were is both slower and prone to drift.
Next steps
An animated point is most useful when it represents something real, so the next step is driving the position from actual data rather than a formula: a vehicle, a delivery, a tracked device.
Once it moves along a defined path rather than freely, the position should be interpolated along that line so it follows roads instead of cutting across them, and the Routing API is where the line comes from.