Bản đồ theo dõi là màn hình có mức độ tương tác cao nhất trong bất kỳ ứng dụng giao thực phẩm hay chia sẻ xe nào. Đó là những gì mà khách hàng nhìn vào trong khi chờ đợi. Làm tốt, chuyển động mượt mà, thời gian giao hàng chính xác, đường định tuyến rõ ràng, là sự khác biệt giữa một ứng dụng cảm thấy chuyên nghiệp và một ứng dụng cảm thấy không đáng tin cậy.
Hướng dẫn này xây dựng bản đồ theo dõi gặp mặt khách hàng từ các nguyên tắc đầu tiên: một điểm cuối sau cung cấp các vị trí GPS qua WebSocket, một điểm cuối phía trước nhận chúng và di chuyển một đánh dấu tài xế mà không nhảy, một dòng tuyến đường từ MapAtlas Routing API và hiển thị ETA trực tiếp. Việc triển khai hoàn chỉnh dưới 70 dòng JavaScript phía máy khách, được thiết kế để tích hợp với bất kỳ phụt trợ nào có thể đẩy tin nhắn WebSocket.
Kiến trúc hoạt động cho giao thực phẩm, tạp hóa, chia sẻ xe, dịch vụ trên địa bàn và bất kỳ trường hợp sử dụng nào khác trong đó một phương tiện di chuyển đến một điểm đến cố định và khách hàng xem nó xảy ra trong thời gian thực.
Architecture Overview
Before writing code, it helps to understand the data flow:
- Driver app (mobile, GPS hardware) sends latitude/longitude to your backend every 3–5 seconds.
- Backend (Node.js, Python, Go, your choice) persists the last known position and broadcasts it via WebSocket to all connected order subscribers.
- Customer browser receives WebSocket messages and moves a marker on the map using interpolated animation.
- Routing API is called once when the order is created to fetch the planned route. The decoded polyline is displayed as a line layer.
- ETA is recalculated by comparing distance remaining to average speed, or by re-calling the Routing API from the driver's current position.
The backend implementation is outside the scope of this tutorial, but any WebSocket server that sends messages in this format works with the frontend code below:
{
"type": "position_update",
"orderId": "order-8821",
"lat": 52.3741,
"lng": 4.8952,
"heading": 92,
"speed": 28,
"timestamp": 1738234521000
}
Step 1: Map Initialisation
Set up the map centred on the delivery origin. The Routing API call happens in Step 4, so for now just initialise the canvas.
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const map = new mapmetricsgl.Map({
container: 'tracking-map',
style: 'https://tiles.mapatlas.eu/styles/basic/style.json?key=YOUR_API_KEY',
center: [4.9041, 52.3676],
zoom: 14
});
// Destination marker (restaurant or pickup point)
const destination = [4.9001, 52.3791];
new mapmetricsgl.Marker({ color: '#EF4444' })
.setLngLat(destination)
.setPopup(new mapmetricsgl.Popup().setHTML('<strong>Pickup point</strong>'))
.addTo(map);
Step 2: Driver Marker with Heading Rotation
Create the driver marker separately so you can update its position on each GPS ping. A custom HTML element lets you rotate the marker icon to reflect the driver's heading, a small detail that makes the tracking feel much more realistic.
// Custom element so we can rotate it
const driverEl = document.createElement('div');
driverEl.innerHTML = '🚗';
driverEl.style.cssText = 'font-size:28px;transform-origin:center;transition:transform 0.3s';
const driverMarker = new mapmetricsgl.Marker({ element: driverEl, anchor: 'center' })
.setLngLat([4.9041, 52.3676])
.addTo(map);
function setDriverHeading(heading) {
driverEl.style.transform = `rotate(${heading}deg)`;
}
Step 3: WebSocket Connection and Smooth Interpolation
This is the core of the tracking map. Connecting to the WebSocket is one line; the interesting part is interpolating the marker position between GPS pings so it slides smoothly rather than teleporting.
let prevPos = null; // { lat, lng }
let animFrame = null;
function interpolateMarker(fromLat, fromLng, toLat, toLng, durationMs) {
const startTime = performance.now();
function step(now) {
const elapsed = now - startTime;
const t = Math.min(elapsed / durationMs, 1); // 0 → 1
const lat = fromLat + (toLat - fromLat) * t;
const lng = fromLng + (toLng - fromLng) * t;
driverMarker.setLngLat([lng, lat]);
if (t < 1) {
animFrame = requestAnimationFrame(step);
}
}
if (animFrame) cancelAnimationFrame(animFrame);
animFrame = requestAnimationFrame(step);
}
const ws = new WebSocket('wss://your-backend.example.com/track/order-8821');
ws.addEventListener('message', (event) => {
const msg = JSON.parse(event.data);
if (msg.type !== 'position_update') return;
const { lat, lng, heading } = msg;
if (prevPos) {
// Smooth animation from previous to new position
interpolateMarker(prevPos.lat, prevPos.lng, lat, lng, 400);
} else {
// First ping, place marker immediately
driverMarker.setLngLat([lng, lat]);
map.flyTo({ center: [lng, lat], zoom: 15 });
}
setDriverHeading(heading);
updateETA(lat, lng);
prevPos = { lat, lng };
});
ws.addEventListener('close', () => {
console.log('Driver has arrived or connection closed.');
});
The 400ms interpolation window matches a typical GPS ping interval of 3–5 seconds reasonably well, the marker is always slightly behind reality but never jumps noticeably.
Step 4: Draw the Planned Route from the Routing API
Fetch the full route when the order is assigned. Store the polyline coordinates and draw them as a GeoJSON line layer. The Route Optimization API tutorial covers multi-stop scenarios; for a simple A-to-B delivery the request is straightforward.
async function fetchAndDrawRoute(originLat, originLng, destLat, destLng) {
const url = new URL('https://api.mapatlas.eu/v1/routing/route');
url.searchParams.set('origin', `${originLat},${originLng}`);
url.searchParams.set('destination', `${destLat},${destLng}`);
url.searchParams.set('profile', 'driving');
url.searchParams.set('key', 'YOUR_API_KEY');
const res = await fetch(url);
const data = await res.json();
if (!data.routes?.length) return;
const route = data.routes[0];
map.on('load', () => {
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
geometry: route.geometry // GeoJSON LineString
}
});
map.addLayer({
id: 'route-line',
type: 'line',
source: 'route',
layout: { 'line-join': 'round', 'line-cap': 'round' },
paint: {
'line-color': '#3B82F6',
'line-width': 4,
'line-opacity': 0.8
}
});
});
return route.duration; // seconds
}
Step 5: ETA Calculation and Display
Calculate ETA by comparing the driver's current position to the destination. For high accuracy, re-call the Routing API from the driver's current position every 30 seconds to get a fresh travel time estimate.
function haversineKm(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLon = ((lon2 - lon1) * Math.PI) / 180;
const a = Math.sin(dLat / 2) ** 2 +
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLon / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
let lastRouteFetch = 0;
async function updateETA(driverLat, driverLng) {
const [destLng, destLat] = destination;
const distKm = haversineKm(driverLat, driverLng, destLat, destLng);
// Fast client-side estimate (assume 30 km/h urban average)
const minutesEstimate = Math.ceil((distKm / 30) * 60);
document.getElementById('eta').textContent =
distKm < 0.1
? 'Arriving now'
: `ETA: ${minutesEstimate} min (${distKm.toFixed(1)} km)`;
// Re-fetch from Routing API every 30 seconds for accuracy
const now = Date.now();
if (now - lastRouteFetch > 30_000) {
lastRouteFetch = now;
const url = new URL('https://api.mapatlas.eu/v1/routing/route');
url.searchParams.set('origin', `${driverLat},${driverLng}`);
url.searchParams.set('destination', `${destLat},${destLng}`);
url.searchParams.set('profile', 'driving');
url.searchParams.set('key', 'YOUR_API_KEY');
const res = await fetch(url);
const data = await res.json();
if (data.routes?.length) {
const mins = Math.ceil(data.routes[0].duration / 60);
document.getElementById('eta').textContent = `ETA: ${mins} min`;
}
}
}
GDPR Considerations for Driver Tracking
Driver GPS coordinates are personal data under GDPR Article 4(1). The regulations that govern EU food delivery and rideshare platforms on this point are not ambiguous:
Data minimisation: Track only the fields you need for dispatch, position, heading, speed. Do not log raw GPS history beyond what is operationally necessary.
Retention limits: Granular trip tracking data should be deleted or irreversibly anonymised once the order is complete. Aggregated route data (without linking back to an individual driver) may be retained longer for network optimisation.
Legal basis: Legitimate interest under Article 6(1)(f) covers real-time dispatch tracking. For any secondary use of tracking data (analytics, benchmarking), you need to document a separate basis.
Driver transparency: Include clear tracking disclosure in driver onboarding. Drivers must be told what is collected, how long it is retained, and who can access it.
Data residency: MapAtlas processes all API requests within the EU. This eliminates the third-country transfer concern that arises with US-based mapping providers. See the EU Developer's Guide to GDPR-Compliant Map APIs for the full compliance picture.
For the ridesharing and mobility industry use case specifically, MapAtlas includes DPA documentation and EU server guarantees as standard. The logistics and delivery industry page covers fleet and multi-driver scenarios.
Production Hardening
Before shipping a tracking feature to customers, check these items:
- WebSocket reconnection: Add
ws.addEventListener('close', reconnect)with exponential backoff. Mobile networks drop connections frequently. - Stale position handling: If no update arrives for 15 seconds, show a "locating driver" state rather than leaving the last position visible.
- Arrival detection: When
distKm < 0.1, trigger an "arrived" state, close the WebSocket, and show a confirmation screen. - Camera follows driver: Call
map.panTo([lng, lat])on each position update to keep the driver centred. Give users a "lock" toggle to disable follow mode if they want to explore the map.
Next Steps
- Sign up for your free MapAtlas API key and start building
- Read the Route Optimization API tutorial to add multi-stop dispatch to your delivery app
- Explore the Real Estate Property Map tutorial for another example of dynamic, data-driven map layers
Frequently Asked Questions
How do I show a driver's location moving smoothly on a map?
GPS pings arrive every few seconds, creating visible jumps if you update the marker position directly. Smooth interpolation animates the marker between the previous position and the new one over a short duration (300–500ms), using requestAnimationFrame to move the marker in small increments. This gives the appearance of continuous movement even with infrequent GPS updates.
Is driver location data subject to GDPR?
Yes. A driver's real-time GPS coordinates are personal data under GDPR Article 4. EU food delivery and rideshare platforms must minimise retention, tracking data should be deleted or anonymised once the trip is complete. Processing requires a legal basis and must be disclosed in the driver's privacy notice.
Can I use the MapAtlas Routing API to show the planned route on the tracking map?
Yes. Fetch the route from the Routing API when a trip is created, decode the polyline, and add it as a GeoJSON line layer on the map. As the driver moves, you can optionally re-fetch the route from the current position to recalculate the ETA dynamically.
