Skip to main content
Hoe bouw je een live chauffeursvolgkaart voor bezorg- en ritdeelapps
Tutorials

Hoe bouw je een live chauffeursvolgkaart voor bezorg- en ritdeelapps

Bouw een realtime chauffeursvolgkaart met WebSocket GPS-updates, soepele markerinterpolatie, routeweergave en ETA-berekening met de MapAtlas Maps- en Routing-APIs.

Brent van der Heiden9 min read
#live tracking map#driver tracking#real-time map#websocket map#delivery tracking#rideshare app

De volgkaart is het scherm waar gebruikers het langst naar kijken in elke app voor maaltijdbezorging of ritdelen. Klanten staren ernaar terwijl ze wachten. Soepele beweging, betrouwbare aankomsttijden en een duidelijke routelijn maken het verschil tussen een app die professioneel aanvoelt en een app die onbetrouwbaar overkomt.

In deze tutorial bouw je de volgkaart voor klanten vanaf de basis: een backend die GPS-posities via WebSocket uitzendt, een frontend die ze ontvangt en een chauffeursmarker zonder sprongen verplaatst, een routelijn uit de MapAtlas Routing API en een live weergave van de aankomsttijd (ETA). De complete implementatie telt minder dan 70 regels JavaScript aan de clientkant en werkt samen met elke backend die WebSocket-berichten kan pushen.

De architectuur werkt voor maaltijdbezorging, boodschappen, ritdelen, buitendienst en elke andere situatie waarin een voertuig naar een vaste bestemming rijdt en een klant dat in realtime volgt.

Overzicht van de architectuur

Voordat je code schrijft, helpt het om de datastroom te begrijpen:

  1. De chauffeursapp (mobiel, GPS-hardware) stuurt elke 3 tot 5 seconden de breedte- en lengtegraad naar je backend.
  2. De backend (Node.js, Python, Go, wat je maar wilt) slaat de laatst bekende positie op en zendt die via WebSocket uit naar iedereen die de bestelling volgt.
  3. De browser van de klant ontvangt de WebSocket-berichten en verplaatst een marker op de kaart met een geïnterpoleerde animatie.
  4. De Routing API wordt één keer aangeroepen bij het aanmaken van de bestelling om de geplande route op te halen. De gedecodeerde polyline wordt als lijnlaag getoond.
  5. De ETA wordt opnieuw berekend door de resterende afstand te vergelijken met de gemiddelde snelheid, of door de Routing API opnieuw aan te roepen vanaf de huidige positie van de chauffeur.

De backend valt buiten deze tutorial, maar elke WebSocket-server die berichten in dit formaat verstuurt, werkt met de frontendcode hieronder:

{
  "type": "position_update",
  "orderId": "order-8821",
  "lat": 52.3741,
  "lng": 4.8952,
  "heading": 92,
  "speed": 28,
  "timestamp": 1738234521000
}

Stap 1: de kaart initialiseren

Zet de kaart op met het vertrekpunt van de bezorging in het midden. De aanroep van de Routing API volgt in stap 4, dus initialiseer nu alleen het 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://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/style.json&token=YOUR_API_KEY',
  center: [4.9041, 52.3676],
  zoom: 14
});

// Bestemmingsmarker (restaurant of ophaalpunt)
const destination = [4.9001, 52.3791];

new mapmetricsgl.Marker({ color: '#EF4444' })
  .setLngLat(destination)
  .setPopup(new mapmetricsgl.Popup().setHTML('<strong>Pickup point</strong>'))
  .addTo(map);

Stap 2: chauffeursmarker die meedraait met de rijrichting

Maak de chauffeursmarker apart aan, zodat je de positie bij elke GPS-ping kunt bijwerken. Met een eigen HTML-element kun je het markericoon laten meedraaien met de rijrichting van de chauffeur. Een klein detail waardoor het volgen veel realistischer aanvoelt.

// Eigen element, zodat we het kunnen draaien
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)`;
}

Stap 3: WebSocket-verbinding en soepele interpolatie

Dit is de kern van de volgkaart. Verbinden met de WebSocket kost één regel. Het interessante deel is de markerpositie interpoleren tussen GPS-pings, zodat de marker soepel verschuift in plaats van te teleporteren.

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) {
    // Soepele animatie van de vorige naar de nieuwe positie
    interpolateMarker(prevPos.lat, prevPos.lng, lat, lng, 400);
  } else {
    // Eerste ping, marker direct plaatsen
    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.');
});

Een interpolatievenster van 400 ms past redelijk goed bij een gebruikelijk GPS-interval van 3 tot 5 seconden. De marker loopt altijd iets achter op de werkelijkheid, maar maakt nooit merkbare sprongen.

Stap 4: de geplande route tekenen met de Routing API

Haal de volledige route op zodra de bestelling is toegewezen. Sla de coördinaten van de polyline op en teken ze als GeoJSON-lijnlaag. De tutorial over de Route Optimization API behandelt scenario's met meerdere stops. Voor een eenvoudige bezorging van A naar B is het verzoek overzichtelijk.

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; // seconden
}

Stap 5: de ETA berekenen en tonen

Bereken de ETA door de huidige positie van de chauffeur te vergelijken met de bestemming. Wil je hoge nauwkeurigheid, roep de Routing API dan elke 30 seconden opnieuw aan vanaf de huidige positie van de chauffeur voor een verse schatting van de reistijd.

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);

  // Snelle schatting aan de clientkant (uitgaande van gemiddeld 30 km/u in de stad)
  const minutesEstimate = Math.ceil((distKm / 30) * 60);
  document.getElementById('eta').textContent =
    distKm < 0.1
      ? 'Arriving now'
      : `ETA: ${minutesEstimate} min (${distKm.toFixed(1)} km)`;

  // Elke 30 seconden opnieuw ophalen via de Routing API voor meer nauwkeurigheid
  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`;
    }
  }
}

AVG-aandachtspunten bij het volgen van chauffeurs

GPS-coördinaten van chauffeurs zijn persoonsgegevens in de zin van artikel 4, lid 1 van de AVG. De AVG-beginselen die hier gelden zijn dataminimalisatie en opslagbeperking uit artikel 5:

Dataminimalisatie: leg alleen de velden vast die je nodig hebt voor de dispatch: positie, rijrichting en snelheid. Bewaar geen ruwe GPS-geschiedenis langer dan operationeel nodig is.

Bewaartermijnen: gedetailleerde volgdata per rit hoort te worden verwijderd of onomkeerbaar geanonimiseerd zodra de bestelling is afgerond. Geaggregeerde routedata (zonder koppeling aan een individuele chauffeur) mag je langer bewaren voor netwerkoptimalisatie.

Rechtsgrond: het gerechtvaardigd belang uit artikel 6, lid 1, onder f, onderbouwd met een gedocumenteerde belangenafweging, of de uitvoering van een overeenkomst uit artikel 6, lid 1, onder b dekt realtime volgen voor de dispatch meestal. Voor elk ander gebruik van volgdata (analyses, benchmarking) moet je een aparte rechtsgrond vastleggen.

Transparantie naar chauffeurs: neem een duidelijke uitleg over het volgen op in de onboarding van chauffeurs. Chauffeurs moeten weten wat er wordt verzameld, hoe lang het wordt bewaard en wie er toegang toe heeft.

Dataresidentie: MapAtlas verwerkt alle API-verzoeken binnen de EU. Daarmee vervalt het probleem van doorgifte naar derde landen dat speelt bij kaartaanbieders uit de VS. Zie de EU Developer's Guide to GDPR-Compliant Map APIs voor het volledige beeld.

Specifiek voor de sector ritdelen en mobiliteit levert MapAtlas standaard documentatie voor de verwerkersovereenkomst en de garantie van servers in de EU. De pagina over logistiek en bezorging behandelt scenario's met wagenparken en meerdere chauffeurs.

Klaarmaken voor productie

Controleer deze punten voordat je een volgfunctie aan klanten uitlevert:

  • WebSocket opnieuw verbinden: voeg ws.addEventListener('close', reconnect) toe met exponentiële backoff. Mobiele netwerken verbreken vaak de verbinding.
  • Verouderde posities afhandelen: komt er 15 seconden geen update binnen, toon dan de status "chauffeur wordt gezocht" in plaats van de laatste positie te laten staan.
  • Aankomst detecteren: zodra distKm < 0.1, activeer je de status "aangekomen", sluit je de WebSocket en toon je een bevestigingsscherm.
  • Camera volgt de chauffeur: roep bij elke positie-update map.panTo([lng, lat]) aan om de chauffeur in beeld te houden. Geef gebruikers een vergrendelknop om het volgen uit te zetten als ze zelf de kaart willen verkennen.

Volgende stappen

Veelgestelde vragen

Hoe laat ik de locatie van een chauffeur soepel over de kaart bewegen?

GPS-pings komen om de paar seconden binnen. Werk je de markerpositie direct bij, dan zie je zichtbare sprongen. Met soepele interpolatie animeer je de marker in korte tijd (300 tot 500 ms) van de vorige naar de nieuwe positie, waarbij requestAnimationFrame de marker in kleine stapjes verplaatst. Zo lijkt de beweging doorlopend, ook als GPS-updates maar af en toe binnenkomen.

Valt locatiedata van chauffeurs onder de AVG?

Ja. De realtime GPS-coördinaten van een chauffeur zijn persoonsgegevens in de zin van artikel 4 van de AVG. Platforms horen volgdata alleen zo lang te bewaren als nodig is, en de veiligste standaard is om die te verwijderen of te anonimiseren zodra de rit is afgerond. Voor de verwerking is een rechtsgrond nodig, en die moet in de privacyverklaring voor chauffeurs staan.

Kan ik de MapAtlas Routing API gebruiken om de geplande route op de volgkaart te tonen?

Ja. Haal de route op via de Routing API zodra een rit wordt aangemaakt, decodeer de polyline en voeg die als GeoJSON-lijnlaag toe aan de kaart. Terwijl de chauffeur rijdt, kun je de route desgewenst opnieuw ophalen vanaf de huidige positie om de ETA dynamisch bij te werken.

Veelgestelde vragen

Hoe laat ik de locatie van een chauffeur soepel over de kaart bewegen?

GPS-pings komen om de paar seconden binnen. Werk je de markerpositie direct bij, dan zie je zichtbare sprongen. Met soepele interpolatie animeer je de marker in korte tijd (300 tot 500 ms) van de vorige naar de nieuwe positie, waarbij `requestAnimationFrame` de marker in kleine stapjes verplaatst. Zo lijkt de beweging doorlopend, ook als GPS-updates maar af en toe binnenkomen.

Valt locatiedata van chauffeurs onder de AVG?

Ja. De realtime GPS-coördinaten van een chauffeur zijn persoonsgegevens in de zin van artikel 4 van de AVG. Platforms horen volgdata alleen zo lang te bewaren als nodig is, en de veiligste standaard is om die te verwijderen of te anonimiseren zodra de rit is afgerond. Voor de verwerking is een rechtsgrond nodig (meestal gerechtvaardigd belang voor de ritplanning, of de uitvoering van een overeenkomst), en die moet in de privacyverklaring voor chauffeurs staan.

Kan ik de MapAtlas Routing API gebruiken om de geplande route op de volgkaart te tonen?

Ja. Haal de route op via de Routing API zodra een rit wordt aangemaakt, decodeer de polyline en voeg die als GeoJSON-lijnlaag toe aan de kaart. Terwijl de chauffeur rijdt, kun je de route desgewenst opnieuw ophalen vanaf de huidige positie om de ETA dynamisch bij te werken.

Was dit nuttig? Deel het.

Over de auteur

Brent van der Heiden

Geschreven door

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. Before that he co-founded MapMetrics, a community mapping and navigation app. He writes about geospatial infrastructure, map data, AI search visibility, and how location data powers the products people rely on every day.

Alle artikelen bekijken →
Terug naar blog