Een kaart voor het zoeken naar zorgverleners is een van de meest consequente kaarten die een ontwikkelaar kan bouwen. Patiënten die het gebruiken, zijn vaak angstig, hebben pijn of navigeren door een onbekend systeem. Een kaart die traag laadt, irrelevante resultaten retourneert of stilzwijgend hun postcode naar een in de VS gebaseerde externe dienst stuurt, is niet alleen een slechte gebruikerservaring, het is een potentiële GDPR-schending.
Deze tutorial bouwt een productieklare zorgverlenervinder: specialiteitsfilter, postcodeszoekfunctie via de Geocoding API, isochroon-overlay met "artsen binnen 20 minuten" en een werkende kaart via de MapAtlas Maps API, allemaal met GDPR-naleving ingebouwd in de architectuur vanaf het begin, niet achteraf toegevoegd.
De volledige implementatie is vanilla JavaScript, geen framework vereist. Het werkt op elke stack: React, Vue, plain HTML, WordPress of het verouderde CMS van een ziekenhuis. De tutorial behandelt ook de EU-regelgevingscontext, VK CQC-data, de Duitse Kassenärztliche Vereinigung en het Franse CNAM, zodat u weet waar u rechtmatig aanbiedersdata kunt vinden.
Waarom gezondheidslocatieapps extra GDPR-verplichtingen hebben
Standaard-apps verzamelen persoonsgegevens (naam, e-mail, gebruik). Gezondheidsapps hebben een tweede risico: het afleiden van gezondheidsinformatie uit locatiegegevens.
Wanneer een gebruiker zoekt op "cardiologen bij SE1 7PB", is zijn postcode een persoonsgegeven. De combinatie van postcode en medische specialiteit wordt potentieel bijzondere categorie-gegevens onder GDPR Artikel 9(1), gegevens die een gezondheidstoestand onthullen. Dit geldt zelfs als u nooit een gebruikersaccount aanmaakt of een profiel opslaat.
De risico's zijn concreet:
- Een client-side geocoding-API-aanroep die de getypte postcode van de gebruiker bevat, wordt naar een externe server gestuurd. Die server ziet de postcode, de gezochte specialiteit en het IP-adres. Zonder een DPA en een EU-datalocatiegarantie is dat een problematische overdracht.
- Browserautocorrectie en geschiedenis kunnen de getypte postcode en specialistenzoekopdracht opslaan op het apparaat van de gebruiker, buiten uw controle maar het vermelden waard in uw privacyinformatie.
- Als u API-reacties server-side logt (gebruikelijk voor foutopsporing), kunt u postcode-specialiteitscombinatieslogging zonder het te beseffen.
De oplossing is backend-proxying: uw server ontvangt de zoekopdracht, roept de MapAtlas Geocoding API aan en retourneert alleen de coördinaten, zonder de getypte zoekopdracht van de browser van de patiënt naar een externe API te sturen. Meer hierover in het implementatiegedeelte.
Structuur van aanbiedersdata
Aanbiedersdata moet afkomstig zijn van gezaghebbende officiële bronnen, niet van gescrapete datasets:
- VK: NHS Digital's NHS Choices API en CQC-geregistreerde aanbiederslijsten
- Duitsland: Kassenärztliche Bundesvereinigung (KBV) open data en de Arztsuche API
- Frankrijk: CNAM's Ameli API en het répertoire RPPS
Structureer elke aanbieder als een GeoJSON-feature:
const providers = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9051, 52.3680] },
properties: {
id: "prov-001",
name: "Dr. Maria van den Berg",
specialty: "general-practitioner",
accepting: true,
address: "Prinsengracht 263, 1016 GV Amsterdam",
phone: "+31 20 423 5678",
languages: ["Dutch", "English"],
nextAvailable: "2026-02-18"
}
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.8890, 52.3610] },
properties: {
id: "prov-002",
name: "Dr. Jan Smits",
specialty: "cardiologist",
accepting: false,
address: "Leidseplein 15, 1017 PS Amsterdam",
phone: "+31 20 612 3456",
languages: ["Dutch"],
nextAvailable: null
}
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [4.9200, 52.3750] },
properties: {
id: "prov-003",
name: "Anna Fischer, MD",
specialty: "dermatologist",
accepting: true,
address: "Plantage Middenlaan 14, 1018 DD Amsterdam",
phone: "+31 20 789 0123",
languages: ["Dutch", "English", "German"],
nextAvailable: "2026-02-20"
}
}
]
};
Stap 1: Kaartinitialisatie met specialiteitsfilter
Begin met de kaart en een specialiteitskeuzelijst. Het filter werkt de zichtbare markers client-side bij zonder enig netwerkverzoek.
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const map = new mapmetricsgl.Map({
container: 'provider-map',
style: 'https://tiles.mapatlas.eu/styles/basic/style.json?key=YOUR_API_KEY',
center: [4.9041, 52.3676],
zoom: 13
});
map.on('load', () => {
map.addSource('providers', {
type: 'geojson',
data: providers
});
// Provider markers, green = accepting, grey = full
map.addLayer({
id: 'provider-markers',
type: 'circle',
source: 'providers',
paint: {
'circle-radius': 10,
'circle-color': [
'case',
['==', ['get', 'accepting'], true], '#16A34A',
'#9CA3AF'
],
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff'
}
});
buildList(providers.features);
});
// Specialty filter
document.getElementById('specialty-select').addEventListener('change', (e) => {
const specialty = e.target.value;
if (specialty === 'all') {
map.setFilter('provider-markers', null);
} else {
map.setFilter('provider-markers', ['==', ['get', 'specialty'], specialty]);
}
});
Stap 2: Postcodeszoekfunctie via backend-proxy
In plaats van de Geocoding API vanuit de browser aan te roepen (waardoor de postcode van de patiënt naar een externe server wordt blootgesteld), routeert u het geocodingverzoek via uw eigen backend-eindpunt. Uw server roept MapAtlas aan en retourneert alleen de coördinaten.
Frontend (browser van de patiënt):
async function geocodePostcode(postcode) {
// Call YOUR backend, not the geocoding API directly
const res = await fetch(`/api/geocode?q=${encodeURIComponent(postcode)}`);
const { lat, lng } = await res.json();
return { lat, lng };
}
document.getElementById('postcode-form').addEventListener('submit', async (e) => {
e.preventDefault();
const postcode = document.getElementById('postcode-input').value.trim();
const { lat, lng } = await geocodePostcode(postcode);
map.flyTo({ center: [lng, lat], zoom: 13 });
fetchIsochrone(lat, lng);
});
Backend-proxy (Node.js/Express):
// /api/geocode, server-side only, postcode never leaves your infrastructure
app.get('/api/geocode', async (req, res) => {
const url = new URL('https://api.mapatlas.eu/geocoding/v1/search');
url.searchParams.set('text', req.query.q);
url.searchParams.set('key', process.env.MAPATLAS_API_KEY); // server-side key
url.searchParams.set('size', '1');
const upstream = await fetch(url.toString());
const data = await upstream.json();
if (!data.features?.length) {
return res.status(404).json({ error: 'Postcode not found' });
}
const [lng, lat] = data.features[0].geometry.coordinates;
// Return ONLY coordinates, no postcode logged, no specialty in response
res.json({ lat, lng });
});
Uw API-sleutel blijft op de server en de ruwe postcodequery van de patiënt wordt nooit naar een externe partij gestuurd.
Stap 3: Isochroon-overlay, "artsen binnen 20 minuten"
Een isochroon-polygoon toont elk punt dat bereikbaar is binnen een bepaalde rijtijd. Het weergeven ervan op een zorgverlenervinder beantwoordt de meest praktische vraag van de patiënt: "hoeveel artsen kan ik daadwerkelijk bereiken?"
async function fetchIsochrone(lat, lng) {
// Call through your backend proxy for the same privacy reasons
const res = await fetch(
`/api/isochrone?lat=${lat}&lng=${lng}&minutes=20&profile=driving`
);
const geojson = await res.json();
// Remove existing isochrone layer if present
if (map.getLayer('isochrone-fill')) map.removeLayer('isochrone-fill');
if (map.getLayer('isochrone-border')) map.removeLayer('isochrone-border');
if (map.getSource('isochrone')) map.removeSource('isochrone');
map.addSource('isochrone', { type: 'geojson', data: geojson });
map.addLayer({
id: 'isochrone-fill',
type: 'fill',
source: 'isochrone',
paint: {
'fill-color': '#16A34A',
'fill-opacity': 0.12
}
}, 'provider-markers'); // Insert below markers so pins render on top
map.addLayer({
id: 'isochrone-border',
type: 'line',
source: 'isochrone',
paint: {
'line-color': '#16A34A',
'line-width': 2,
'line-dasharray': [3, 2]
}
}, 'provider-markers');
}
Backend isochroon-proxy:
app.get('/api/isochrone', async (req, res) => {
const { lat, lng, minutes, profile } = req.query;
const url = new URL('https://api.mapatlas.eu/v1/isochrone');
url.searchParams.set('lat', lat);
url.searchParams.set('lng', lng);
url.searchParams.set('time', minutes * 60); // seconds
url.searchParams.set('profile', profile || 'driving');
url.searchParams.set('key', process.env.MAPATLAS_API_KEY);
const upstream = await fetch(url.toString());
const data = await upstream.json();
res.json(data); // GeoJSON polygon
});
Het artikel Isochronenkaarten uitgelegd gaat dieper in op gebruiksscenario's van isochronen, inclusief isochronen voor openbaar vervoer (nuttig voor patiënten zonder auto) en overlays met meerdere tijdsbanden die gelijktijdig zones van 10, 20 en 30 minuten tonen.
Stap 4: Aanbiedersdetail-popup
Wanneer een patiënt op een artsmarker klikt, toont u de essentiële gegevens: naam, specialiteit, acceptatiestatus, talen en de volgende beschikbare afspraak.
map.on('click', 'provider-markers', (e) => {
const {
name, specialty, accepting, address, phone, languages, nextAvailable
} = e.features[0].properties;
const coords = e.features[0].geometry.coordinates.slice();
const statusBadge = accepting
? '<span style="color:#16A34A;font-weight:600">Accepting patients</span>'
: '<span style="color:#9CA3AF">Not accepting new patients</span>';
const apptLine = accepting && nextAvailable
? `<p style="margin:4px 0">Next available: <strong>${nextAvailable}</strong></p>`
: '';
new mapmetricsgl.Popup({ maxWidth: '280px' })
.setLngLat(coords)
.setHTML(`
<strong style="font-size:15px">${name}</strong>
<p style="margin:4px 0;text-transform:capitalize">${specialty.replace('-', ' ')}</p>
${statusBadge}
${apptLine}
<p style="margin:6px 0;font-size:13px;color:#64748b">${address}</p>
<p style="margin:4px 0;font-size:13px">
Languages: ${Array.isArray(languages) ? languages.join(', ') : languages}
</p>
<a href="tel:${phone}" style="color:#2563EB">${phone}</a>
`)
.addTo(map);
});
map.on('mouseenter', 'provider-markers', () => { map.getCanvas().style.cursor = 'pointer'; });
map.on('mouseleave', 'provider-markers', () => { map.getCanvas().style.cursor = ''; });
Stap 5: Lijstpaneel gesynchroniseerd met de kaart
Een lijstweergave naast de kaart helpt gebruikers met schermlezers en gebruikers die liever scrollen dan op spelden klikken.
function buildList(features) {
const list = document.getElementById('provider-list');
list.innerHTML = '';
features.forEach(({ properties, geometry }) => {
const { name, specialty, accepting, address, nextAvailable } = properties;
const card = document.createElement('div');
card.className = `provider-card ${accepting ? 'accepting' : 'full'}`;
card.innerHTML = `
<strong>${name}</strong>
<p>${specialty.replace('-', ' ')}</p>
<p style="font-size:12px;color:#64748b">${address}</p>
<small>${accepting ? `Next: ${nextAvailable || 'call to confirm'}` : 'Not accepting'}</small>
`;
card.addEventListener('click', () => {
map.flyTo({ center: geometry.coordinates, zoom: 15 });
});
list.appendChild(card);
});
}
GDPR-implementatiechecklist
Voor EU-gezondheidszorkontwikkelaars, behandel dit als een minimale nalevingschecklist:
- Backend-proxy voor geocodering: Postcodes van patiënten mogen niet client-side naar externe APIs worden gestuurd.
- Geen sessielogboekregistratie van zoektermen: Als u verzoeken logt voor foutopsporing, verwijder de postcode voordat u naar logs schrijft.
- Privacyverklaring: De privacyverklaring van uw app moet vermelden dat locatiedata wordt verwerkt om nabijgelegen zorgverleners te vinden, de rechtsgrondslag (gerechtvaardigd belang of uitdrukkelijke toestemming), en de bewaartermijn.
- Toestemming voor locatietoegang: Als u de browser Geolocation API gebruikt (
navigator.geolocation.getCurrentPosition), toon een duidelijk doelstatement voordat u de browsermachtigingsprompt activeert. - Datalocatie: MapAtlas verwerkt alle API-verzoeken binnen de EU. Raadpleeg de MapAtlas DPA bij het invullen van uw Verwerkingsactiviteitenregister.
- Bewaring: Als u isochronenresultaten of zoekcoördinaten server-side opslaat voor prestaties, definieer een bewaartermijn (bijv. 24 uur) en automatiseer verwijdering.
De Gids voor EU-ontwikkelaars over GDPR-conforme kaart-APIs behandelt verwerkersovereenkomsten, datalocatie en toestemmingspatronen volledig. De pagina Gezondheidszorgdiensten vermeldt de specifieke MapAtlas-functies die relevant zijn voor digitale gezondheidstoepassingen.
Wat u daarna kunt bouwen
- Meld u aan voor een gratis MapAtlas API-sleutel, één sleutel voor kaarten, geocodering, routering en isochronen
- Voeg het Store Locator-tutorialpatroon toe voor multi-klinieknetwerken met meer dan een handvol locaties
- Verken de Geocoding API-mogelijkhedenpagina voor postcodesopzoekingen, omgekeerde geocodering en batchvalidatie van aanbiedersadressen
Veelgestelde vragen
Waarom is de GDPR van toepassing op een doktersvinderskaart, ook als ik geen patiëntgegevens opsla?
De zoeklocatie van een patiënt op een zorgtoepassingsvinder onthult waar hij woont en, bij inferentie, welke aandoeningen hij mogelijk heeft (bijv. zoeken naar oncologen bij een postcode). Onder GDPR Artikel 9 kunnen gezondheidsgerelateerde inferenties uit locatiedata kwalificeren als bijzondere categorie-gegevens. Zelfs zonder profielen op te slaan, creëren realtime geocodingverzoeken die client-side naar externe APIs worden gestuurd een verwerkingsrecord dat een rechtsgrondslag en openbaarmaking vereist.
Wat is een isochroon en waarom is het nuttig voor een zorgverlenervinder?
Een isochroon is een polygoon die elk punt toont dat binnen een bepaalde reistijd bereikbaar is vanaf een startlocatie. Op een zorgvinderskaart beantwoordt een isochroon-overlay de vraag 'welke artsen zijn binnen 20 minuten met de auto?', een veel nuttiger vraag dan ruwe afstand, omdat het rekening houdt met wegen, snelheidslimieten en verkeerspatronen.
Kan ik MapAtlas gebruiken voor een patiëntgerichte NHS of volksgezondheidsapplicatie?
Ja. MapAtlas wordt gehost in de EU, is ISO 27001-gecertificeerd en biedt een GDPR-conforme verwerkersovereenkomst onder Artikel 28. Dit voldoet aan de typische inkoopvereisten van de publieke sector. UK NHS Digital en equivalente instanties in Duitsland (DSGVO) en Frankrijk (CNIL) vereisen datalocatie binnen de EU/UK, waaraan MapAtlas voldoet.

