El mapa de seguimiento es la pantalla de mayor participacion en cualquier aplicacion de entrega de comida o transporte compartido. Es en lo que los clientes se quedan mirando mientras esperan. Hacerlo bien, movimiento suave, ETAs precisas, una linea de ruta clara, es la diferencia entre una aplicacion que se siente profesional y una que se siente no confiable.
Este tutorial construye el mapa de seguimiento orientado al cliente desde los primeros principios: un backend que transmite posiciones GPS a traves de WebSocket, un frontend que las recibe y mueve un marcador de conductor sin saltar, una linea de ruta del API de enrutamiento de MapAtlas y una pantalla de ETA en vivo. La implementacion completa es menos de 70 lineas de JavaScript del lado del cliente, disenada para integrarse con cualquier backend que pueda enviar mensajes de WebSocket.
La arquitectura funciona para entrega de alimentos, tienda de abarrotes, transporte compartido, servicio de campo y cualquier otro caso de uso donde un vehiculo se mueve hacia un destino fijo y un cliente lo ve ocurrir en tiempo real.
Vista general de la arquitectura
Antes de escribir codigo, ayuda entender el flujo de datos:
- Aplicacion de conductor (movil, hardware de GPS) envia latitud/longitud a tu backend cada 3-5 segundos.
- Backend (Node.js, Python, Go, tu eleccion) persiste la ultima posicion conocida y la transmite a traves de WebSocket a todos los suscriptores de ordenes conectadas.
- Navegador del cliente recibe mensajes de WebSocket y mueve un marcador en el mapa usando animacion interpolada.
- API de enrutamiento se llama una vez cuando se crea la orden para obtener la ruta planificada. La polilina decodificada se muestra como una capa de linea.
- ETA se recalcula comparando la distancia restante con la velocidad promedio, o llamando de nuevo el API de enrutamiento desde la posicion actual del conductor.
La implementacion del backend esta fuera del alcance de este tutorial, pero cualquier servidor de WebSocket que envie mensajes en este formato funciona con el codigo frontend a continuacion:
{
"type": "position_update",
"orderId": "order-8821",
"lat": 52.3741,
"lng": 4.8952,
"heading": 92,
"speed": 28,
"timestamp": 1738234521000
}
Paso 1: inicializacion del mapa
Configura el mapa centrado en el origen de entrega. La llamada del API de enrutamiento ocurre en el paso 4, por ahora solo inicializa el lienzo.
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);
Paso 2: marcador de conductor con rotacion de rumbo
Crea el marcador del conductor por separado para que puedas actualizar su posicion en cada ping de GPS. Un elemento HTML personalizado te permite girar el icono del marcador para reflejar el rumbo del conductor, un pequeno detalle que hace que el seguimiento se sienta mucho mas realista.
// 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)`;
}
Paso 3: conexion de WebSocket e interpolacion suave
Este es el nucleo del mapa de seguimiento. Conectarse a WebSocket es una linea; la parte interesante es interpolar la posicion del marcador entre pings de GPS para que se deslice suavemente en lugar de teletransportarse.
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.');
});
La ventana de interpolacion de 400ms coincide bien con un intervalo tipico de ping de GPS de 3-5 segundos, el marcador siempre esta ligeramente detras de la realidad pero nunca salta notablemente.
Paso 4: dibujar la ruta planificada del API de enrutamiento
Obtén la ruta completa cuando se asigna la orden. Almacena las coordenadas de la polilina y dib ujala como una capa de linea GeoJSON. El tutorial de API de optimizacion de rutas cubre escenarios de multiples paradas; para una entrega simple de A a B la solicitud es sencilla.
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
}
Paso 5: calculo de ETA y visualizacion
Calcula ETA comparando la posicion actual del conductor con el destino. Para alta precision, llama de nuevo el API de enrutamiento desde la posicion actual del conductor cada 30 segundos para obtener una estimacion de tiempo de viaje fresca.
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`;
}
}
}
Consideraciones de GDPR para seguimiento de conductores
Las coordenadas GPS del conductor son datos personales bajo el articulo 4(1) de GDPR. Las regulaciones que rigen las plataformas de entrega de alimentos y transporte compartido de la UE en este punto no son ambiguas:
Minimizacion de datos: rastrea solo los campos que necesitas para el despacho, posicion, rumbo, velocidad. No registres el historial GPS sin procesar mas alla de lo operacionalmente necesario.
Limites de retencion: los datos granulares de seguimiento de viajes deben eliminarse o anonimizarse irreversiblemente una vez que se completa la orden. Los datos de ruta agregados (sin vincular de nuevo a un conductor individual) pueden retenerse mas tiempo para optimizacion de red.
Base legal: el interes legitimo bajo el articulo 6(1)(f) cubre el seguimiento del despacho en tiempo real. Para cualquier uso secundario de datos de seguimiento (analiticamente, evaluacion comparativa), necesitas documentar una base separada.
Transparencia del conductor: incluye divulgacion clara de seguimiento en la incorporacion del conductor. Los conductores deben ser informados de que se recopila, cuanto tiempo se retiene y quien puede acceder a ella.
Residencia de datos: MapAtlas procesa todas las solicitudes de API dentro de la UE. Esto elimina la preocupacion de transferencia a paises terceros que surge con proveedores de mapeo basados en EE.UU. Consulta la guia del desarrollador de la UE para APIs de mapas conformes con GDPR para obtener el panorama completo de cumplimiento.
Para el caso de uso industria de transporte compartido y movilidad especificamente, MapAtlas incluye documentacion de DPA y garantias de servidor de la UE como estandar. La pagina de la industria de logistica y entrega cubre escenarios de flota y multiples conductores.
Endurecimiento de produccion
Antes de enviar una funcion de seguimiento a los clientes, verifica estos elementos:
- Reconexion de WebSocket: agrega
ws.addEventListener('close', reconnect)con backoff exponencial. Las redes moviles pierden conexiones frecuentemente. - Manejo de posicion obsoleta: si no llega una actualizacion en 15 segundos, muestra un estado "localizando conductor" en lugar de dejar visible la ultima posicion.
- Deteccion de llegada: cuando
distKm < 0.1, activa un estado "llegado", cierra el WebSocket y muestra una pantalla de confirmacion. - La camara sigue al conductor: llama
map.panTo([lng, lat])en cada actualizacion de posicion para mantener centrado el conductor. Dale a los usuarios un toggle de "bloqueo" para desactivar el modo de seguimiento si desean explorar el mapa.
Pasos siguientes
- Registrate para obtener tu clave de API de MapAtlas gratuita y comienza a construir
- Lee el tutorial de API de optimizacion de rutas para agregar despacho de multiples paradas a tu aplicacion de entrega
- Explora el tutorial de mapa de propiedades inmobiliarias para otro ejemplo de capas de mapa dinamicas y orientadas a datos
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.
