El mapa de seguimiento es la pantalla con más interacción de cualquier app de reparto de comida o de transporte con conductor. Es lo que los clientes miran mientras esperan. Hacerlo bien (movimiento suave, ETA precisas y una línea de ruta clara) marca la diferencia entre una app que transmite profesionalidad y otra que parece poco fiable.
Este tutorial construye desde cero el mapa de seguimiento que ve el cliente: un backend que emite posiciones GPS por WebSocket, un frontend que las recibe y mueve el marcador del conductor sin saltos, una línea de ruta obtenida con la Routing API de MapAtlas y un indicador de ETA en vivo. La implementación completa ocupa menos de 70 líneas de JavaScript en el cliente y está pensada para integrarse con cualquier backend capaz de enviar mensajes por WebSocket.
La arquitectura sirve para reparto de comida, compra a domicilio, transporte con conductor, servicios técnicos y cualquier otro caso en el que un vehículo avanza hacia un destino fijo y un cliente lo sigue en tiempo real.
Visión general de la arquitectura
Antes de escribir código, conviene entender el flujo de datos:
- La app del conductor (móvil, hardware GPS) envía la latitud y la longitud a tu backend cada 3 a 5 segundos.
- El backend (Node.js, Python, Go, el que prefieras) guarda la última posición conocida y la emite por WebSocket a todos los suscriptores conectados a ese pedido.
- El navegador del cliente recibe los mensajes de WebSocket y mueve un marcador en el mapa con una animación interpolada.
- La Routing API se llama una sola vez, al crear el pedido, para obtener la ruta planificada. La polilínea decodificada se muestra como capa de línea.
- La ETA se recalcula comparando la distancia restante con la velocidad media, o volviendo a llamar a la Routing API desde la posición actual del conductor.
La implementación del backend queda fuera del alcance de este tutorial, pero cualquier servidor WebSocket que envíe mensajes con este formato funciona con el código de frontend de más abajo:
{
"type": "position_update",
"orderId": "order-8821",
"lat": 52.3741,
"lng": 4.8952,
"heading": 92,
"speed": 28,
"timestamp": 1738234521000
}
Paso 1: inicializar el mapa
Configura el mapa centrado en el origen del reparto. La llamada a la Routing API llega en el paso 4, así que por ahora basta con inicializar 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://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/style.json&token=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 del conductor con rotación según el rumbo
Crea el marcador del conductor por separado para poder actualizar su posición con cada ping GPS. Un elemento HTML personalizado te permite girar el icono del marcador según el rumbo del conductor, un pequeño detalle que hace que el seguimiento resulte mucho más 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: conexión WebSocket e interpolación suave
Este es el núcleo del mapa de seguimiento. Conectarse al WebSocket ocupa una línea; lo interesante es interpolar la posición del marcador entre pings GPS para que se deslice con suavidad 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 interpolación de 400 ms encaja razonablemente bien con un intervalo típico de ping GPS de 3 a 5 segundos: el marcador siempre va ligeramente por detrás de la realidad, pero nunca da saltos perceptibles.
Paso 4: dibujar la ruta planificada con la Routing API
Obtén la ruta completa cuando se asigna el pedido. Guarda las coordenadas de la polilínea y dibújalas como capa de línea GeoJSON. El tutorial de la Route Optimization API cubre escenarios con varias paradas; para un reparto sencillo de A a B, la petición es directa.
async function fetchAndDrawRoute(originLat, originLng, destLat, destLng) {
const url = new URL('https://gateway.mapmetrics-atlas.net/directions/');
url.searchParams.set('token', 'YOUR_API_KEY');
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
locations: [
{ lat: originLat, lon: originLng },
{ lat: destLat, lon: destLng },
],
costing: 'auto',
format: 'osrm',
shape_format: 'geojson',
}),
});
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: calcular y mostrar la ETA
Calcula la ETA comparando la posición actual del conductor con el destino. Si necesitas más precisión, vuelve a llamar a la Routing API desde la posición actual del conductor cada 30 segundos para obtener una estimación actualizada del tiempo de viaje.
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://gateway.mapmetrics-atlas.net/directions/');
url.searchParams.set('token', 'YOUR_API_KEY');
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
locations: [
{ lat: driverLat, lon: driverLng },
{ lat: destLat, lon: destLng },
],
costing: 'auto',
format: 'osrm',
shape_format: 'geojson',
}),
});
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 del RGPD para el seguimiento de conductores
Las coordenadas GPS de un conductor son datos personales según el artículo 4.1 del RGPD. Los principios del RGPD que se aplican aquí son la minimización de datos y la limitación del plazo de conservación del artículo 5:
Minimización de datos: registra solo los campos que necesitas para la gestión de envíos, es decir, posición, rumbo y velocidad. No guardes un historial GPS en bruto más allá de lo operativamente necesario.
Límites de conservación: los datos detallados de seguimiento de cada viaje deben borrarse o anonimizarse de forma irreversible cuando se completa el pedido. Los datos de rutas agregados (sin vínculo con un conductor concreto) pueden conservarse más tiempo para optimizar la red.
Base jurídica: el interés legítimo del artículo 6.1.f del RGPD, respaldado por una ponderación de intereses documentada, o la ejecución de un contrato del artículo 6.1.b suele cubrir el seguimiento en tiempo real para la gestión de envíos. Para cualquier uso secundario de los datos de seguimiento (analítica, benchmarking), tienes que documentar una base jurídica distinta.
Transparencia con los conductores: informa con claridad sobre el seguimiento durante el alta de cada conductor. Los conductores deben saber qué datos se recopilan, cuánto tiempo se conservan y quién puede acceder a ellos.
Residencia de los datos: MapAtlas procesa todas las peticiones a la API dentro de la UE. Así desaparece el problema de las transferencias a terceros países que surge con los proveedores de mapas con sede en EE. UU. Consulta la guía para desarrolladores en la UE sobre APIs de mapas conformes con el RGPD para ver el panorama completo de cumplimiento.
Para el caso de uso del sector de transporte con conductor y movilidad en concreto, MapAtlas incluye de serie la documentación del contrato de encargo del tratamiento (DPA) y la garantía de servidores en la UE. La página del sector de logística y reparto cubre escenarios con flotas y varios conductores.
Preparación para producción
Antes de lanzar una función de seguimiento a tus clientes, revisa estos puntos:
- Reconexión del WebSocket: añade
ws.addEventListener('close', reconnect)con backoff exponencial. Las redes móviles pierden la conexión con frecuencia. - Posiciones desactualizadas: si no llega ninguna actualización en 15 segundos, muestra un estado de "localizando al conductor" en lugar de dejar visible la última posición.
- Detección de llegada: cuando
distKm < 0.1, activa un estado de "ha llegado", cierra el WebSocket y muestra una pantalla de confirmación. - Cámara que sigue al conductor: llama a
map.panTo([lng, lat])en cada actualización de posición para mantener centrado al conductor. Ofrece a los usuarios un botón de bloqueo para desactivar el seguimiento si quieren explorar el mapa.
Próximos pasos
- Regístrate para obtener tu clave de API gratuita de MapAtlas y empieza a construir
- Lee el tutorial de la Route Optimization API para añadir despacho con varias paradas a tu app de reparto
- Explora el tutorial de mapa inmobiliario para ver otro ejemplo de capas de mapa dinámicas basadas en datos
Preguntas frecuentes
¿Cómo muestro la ubicación de un conductor moviéndose con suavidad en el mapa?
Los pings GPS llegan cada pocos segundos y, si actualizas la posición del marcador directamente, se ven saltos. La interpolación suave anima el marcador entre la posición anterior y la nueva durante un intervalo corto (300 a 500 ms), usando requestAnimationFrame para moverlo en pequeños incrementos. Así el movimiento parece continuo aunque las actualizaciones GPS sean poco frecuentes.
¿Los datos de ubicación de los conductores están sujetos al RGPD?
Sí. Las coordenadas GPS en tiempo real de un conductor son datos personales según el artículo 4 del RGPD. Las plataformas deberían conservar los datos de seguimiento solo el tiempo necesario, y borrarlos o anonimizarlos cuando termina el viaje es la opción más segura por defecto. El tratamiento necesita una base jurídica y debe figurar en el aviso de privacidad del conductor.
¿Puedo usar la Routing API de MapAtlas para mostrar la ruta planificada en el mapa de seguimiento?
Sí. Obtén la ruta de la Routing API al crear el viaje, decodifica la polilínea y añádela al mapa como capa de línea GeoJSON. Mientras el conductor avanza, puedes volver a pedir la ruta desde la posición actual para recalcular la ETA de forma dinámica.
Preguntas frecuentes
¿Cómo muestro la ubicación de un conductor moviéndose con suavidad en el mapa?
Los pings GPS llegan cada pocos segundos y, si actualizas la posición del marcador directamente, se ven saltos. La interpolación suave anima el marcador entre la posición anterior y la nueva durante un intervalo corto (300 a 500 ms), usando requestAnimationFrame para moverlo en pequeños incrementos. Así el movimiento parece continuo aunque las actualizaciones GPS sean poco frecuentes.
¿Los datos de ubicación de los conductores están sujetos al RGPD?
Sí. Las coordenadas GPS en tiempo real de un conductor son datos personales según el artículo 4 del RGPD. Las plataformas deberían conservar los datos de seguimiento solo el tiempo necesario, y borrarlos o anonimizarlos cuando termina el viaje es la opción más segura por defecto. El tratamiento necesita una base jurídica (normalmente interés legítimo para la gestión de envíos o la ejecución de un contrato) y debe figurar en el aviso de privacidad del conductor.
¿Puedo usar la Routing API de MapAtlas para mostrar la ruta planificada en el mapa de seguimiento?
Sí. Obtén la ruta de la Routing API al crear el viaje, decodifica la polilínea y añádela al mapa como capa de línea GeoJSON. Mientras el conductor avanza, puedes volver a pedir la ruta desde la posición actual para recalcular la ETA de forma dinámica.

