Eine Arztfinderkarte ist eine der wichtigsten Karten, die ein Entwickler erstellen kann. Patienten, die sie nutzen, sind oft ängstlich, haben Schmerzen oder navigieren durch ein unbekanntes System. Eine Karte, die langsam lädt, irrelevante Ergebnisse liefert oder still und leise ihre Postleitzahl an einen US-amerikanischen Dienst sendet, ist nicht nur eine schlechte Benutzererfahrung, sondern auch ein möglicher DSGVO-Verstoß.
Dieses Tutorial erstellt eine produktionsreife Arztfinderkarte: Fachgebiet-Filter, Postleitzahlsuche mit der Geocoding API, Isochronen-Overlay, das "Ärzte erreichbar in 20 Minuten" zeigt, und eine funktionsfähige Karte mit der MapAtlas Maps API, alles mit DSGVO-Compliance, die von Anfang an in die Architektur eingebaut ist, nicht nachträglich hinzugefügt.
Die vollständige Implementierung ist Vanilla JavaScript, kein Framework erforderlich. Sie funktioniert auf jedem Stack: React, Vue, plain HTML, WordPress oder ein Klinik-Legacy-CMS. Das Tutorial behandelt auch den EU-Regulierungskontext, UK CQC-Daten, die deutsche Kassenärztliche Vereinigung und die französische CNAM, damit Sie wissen, wie Sie Anbieter-Daten legitim beziehen.
Warum Gesundheits-Standort-Apps zusätzliche DSGVO-Verpflichtungen haben
Standard-Apps sammeln persönliche Daten (Name, E-Mail, Nutzung). Gesundheits-Apps haben ein zweites Risiko: Rückschlüsse auf Gesundheitsinformationen aus Standortdaten.
Wenn ein Benutzer nach "Kardiologen in der Nähe von SE1 7PB" sucht, ist seine Postleitzahl persönliche Daten. Die Kombination von Postleitzahl und medizinischem Fachgebiet wird möglicherweise zu besonderen Kategorien von Daten unter DSGVO Artikel 9(1), Daten, die Gesundheitszustand offenbaren. Dies gilt auch, wenn Sie nie ein Benutzerkonto erstellen oder ein Profil speichern.
Die Risiken sind konkret:
- Ein client-seitiger Geocoding-API-Aufruf, der die eingegebene Postleitzahl des Benutzers enthält, wird an einen Server eines Dritten gesendet. Dieser Server sieht die Postleitzahl, das gesuchte Fachgebiet und die IP-Adresse. Ohne eine DPA und eine EU-Datenspeichergarantie ist dies eine problematische Übertragung.
- Browser-Autovervollständigung und Verlauf können die eingegebene Postleitzahl und Spezialistenabfrage auf dem Gerät des Benutzers speichern, außerhalb Ihrer Kontrolle, aber erwähnenswert in Ihren Datenschutzinformationen.
- Wenn Sie API-Antworten server-seitig protokollieren (üblich zum Debuggen), können Sie versehentlich Postleitzahl + Fachgebiet-Kombinationen protokollieren.
Die Lösung ist Backend-Proxying: Ihr Server empfängt die Suche, ruft die MapAtlas Geocoding API auf und gibt nur die Koordinaten zurück, sendet die eingegebene Abfrage niemals von vom Browser des Patienten zu einem API-Dienst eines Dritten. Mehr dazu im Implementierungsabschnitt.
Anbieter-Datenstruktur
Anbieter-Daten sollten aus autorisierten offiziellen Quellen stammen, nicht aus gekratzten Datensätzen:
- UK: NHS Digital's NHS Choices API und CQC registrierte Anbieterlisten
- Deutschland: Kassenärztliche Bundesvereinigung (KBV) Open Data und die Arztsuche API
- Frankreich: CNAM's Ameli API und das Répertoire RPPS
Structure each provider as a 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"
}
}
]
};
Step 1: Map Initialisation with Specialty Filter
Start with the map and a specialty dropdown. The filter updates the visible markers client-side without any network request.
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]);
}
});
Step 2: Postcode Search via Backend Proxy
Rather than calling the Geocoding API from the browser (which exposes the patient's postcode to a third-party server), route the geocoding request through your own backend endpoint. Your server calls MapAtlas and returns only the coordinates.
Frontend (patient's browser):
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 });
});
Your API key stays on the server and the patient's raw postcode query is never sent to a third party.
Step 3: Isochrone Overlay, "Doctors Within 20 Minutes"
An isochrone polygon shows every point reachable within a given drive time. Displaying one on a provider finder answers the most practical patient question: "how many doctors can I actually reach?"
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 isochrone 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
});
The Isochrone Maps Explained article goes deeper on isochrone use cases, including public transit isochrones (useful for patients without a car) and multi-time-band overlays showing 10, 20, and 30-minute zones simultaneously.
Step 4: Provider Detail Popup
When a patient clicks a doctor marker, show the essential details: name, specialty, accepting status, languages, and next available appointment.
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 = ''; });
Step 5: List Panel Synced with Map
A list view alongside the map helps users with screen readers and those who prefer scrolling to clicking pins.
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);
});
}
DSGVO-Implementierungs-Checkliste
Für EU-Gesundheitsentwickler gilt dies als minimale Compliance-Checkliste:
- Backend-Proxy für Geocoding: Patienten-Postleitzahlen dürfen nicht client-seitig an APIs von Dritten gesendet werden.
- Keine Sitzungsprotokollierung von Suchbegriffen: Wenn Sie Anfragen zu Debuggingzwecken protokollieren, entfernen Sie die Postleitzahl vor dem Schreiben in die Protokolle.
- Datenschutzerklärung: Ihre Datenschutzerklärung der App muss offenlegen, dass Standortdaten verarbeitet werden, um nahegelegene Anbieter zu finden, die Rechtsgrundlage (berechtigtes Interesse oder ausdrückliche Zustimmung) und die Aufbewahrungsdauer.
- Zustimmung für Standortzugriff: Wenn Sie die Browser Geolocation API (
navigator.geolocation.getCurrentPosition) verwenden, zeigen Sie eine klare Absichtserklärung an, bevor Sie die Browser-Berechtigungsaufforderung auslösen. - Datenspeicherung: MapAtlas verarbeitet alle API-Anfragen innerhalb der EU. Verweisen Sie auf die MapAtlas DPA, wenn Sie Ihre Verarbeitungstätigkeitsverzeichnisse ausfüllen.
- Aufbewahrung: Wenn Sie Isochronen-Ergebnisse oder Such-Koordinaten server-seitig zur Leistungsverbesserung zwischenspeichern, definieren Sie eine Aufbewahrungsdauer (z.B. 24 Stunden) und automatisieren Sie die Löschung.
Der EU Developer's Guide to GDPR-Compliant Map APIs behandelt Verarbeitungsvereinbarungen, Datenspeicherung und Zustimmungsmuster vollständig. Die Healthcare Services Branchenseite listet die spezifischen MapAtlas-Funktionen auf, die für digitale Gesundheitsanwendungen relevant sind.
Nächste Schritte
- Registrieren Sie sich für einen kostenlosen MapAtlas API-Schlüssel, ein Schlüssel für Karten, Geocoding, Routing und Isochrone
- Fügen Sie das Store Locator Tutorial-Muster für Multi-Klinik-Netzwerke mit mehr als einer Handvoll Standorten hinzu
- Erkunden Sie die Geocoding API Capabilities-Seite für Postleitzahlsuche, Reverse Geocoding und Batch-Anbieteradressvalidierung
Frequently Asked Questions
Why does GDPR apply to a doctor finder map even if I don't store patient data?
A patient's search location on a healthcare finder reveals where they live and, by inference, which conditions they may have (e.g. searching for oncologists near a postcode). Under GDPR Article 9, health-related inferences from location data can qualify as special category data. Even without storing profiles, real-time geocoding requests sent client-side to third-party APIs create a processing record that requires a legal basis and disclosure.
What is an isochrone and why is it useful for a provider finder?
An isochrone is a polygon that shows every point reachable within a given travel time from a starting location. On a healthcare finder, an isochrone overlay answers "which doctors are within 20 minutes by car?", a much more useful question than raw distance, because it accounts for roads, speed limits, and traffic patterns.
Can I use MapAtlas for a patient-facing NHS or public health application?
Yes. MapAtlas is EU-hosted, ISO 27001 certified, and provides a GDPR-compliant Data Processing Agreement under Article 28. This satisfies typical public sector procurement requirements. UK NHS Digital and equivalent bodies in Germany (DSGVO) and France (CNIL) require data residency within the EU/UK, which MapAtlas fulfils.

