Pencari penyedia layanan kesehatan adalah salah satu peta paling penting yang dapat dibangun oleh pengembang. Pasien yang menggunakannya sering merasa khawatir, kesakitan, atau menavigasi sistem yang tidak familiar. Peta yang dimuat lambat, mengembalikan hasil yang tidak relevan, atau secara diam-diam mengirim kode pos mereka ke layanan pihak ketiga berbasis AS bukan hanya pengalaman pengguna yang buruk, ini adalah potensi pelanggaran GDPR.
Tutorial ini membangun pencari penyedia yang siap produksi: filter spesialisasi, pencarian kode pos menggunakan API Geocoding, overlay isokron menampilkan "dokter dalam 20 menit", dan peta yang berfungsi menggunakan API Maps MapAtlas, semua dengan kepatuhan GDPR tertanam dalam arsitektur sejak awal, bukan difungsikan ulang kemudian.
Implementasi lengkap adalah JavaScript vanilla, tidak ada kerangka yang diperlukan. Ia bekerja di stack apa pun: React, Vue, HTML biasa, WordPress, atau CMS warisan rumah sakit. Tutorial ini juga mencakup konteks regulasi EU, data CQC UK, Kassenärztliche Vereinigung Jerman, dan CNAM Prancis, jadi Anda tahu di mana harus mencari data penyedia secara sah.
Mengapa Aplikasi Lokasi Kesehatan Memiliki Kewajiban GDPR Tambahan
Aplikasi standar mengumpulkan data pribadi (nama, email, penggunaan). Aplikasi kesehatan memiliki risiko kedua: menyimpulkan informasi kesehatan dari data lokasi.
Ketika pengguna mencari "kardiolog dekat SE1 7PB", kode pos mereka adalah data pribadi. Kombinasi kode pos dan spesialisasi medis menjadi potensi data kategori khusus menurut GDPR Pasal 9(1), data yang mengungkapkan kondisi kesehatan. Ini benar bahkan jika Anda tidak pernah membuat akun pengguna atau menyimpan profil.
Risikonya konkret:
- Panggilan API geocoding sisi klien yang mencakup kode pos yang diketik pengguna dikirim ke server pihak ketiga. Server itu melihat kode pos, spesialisasi yang dicari, dan alamat IP. Tanpa DPA dan jaminan residensi data EU, itu adalah transfer yang bermasalah.
- Penyelesaian otomatis browser dan riwayat dapat menyimpan kode pos yang diketik dan kueri spesialis di perangkat pengguna, di luar kontrol Anda tetapi patut dicatat dalam informasi privasi Anda.
- Jika Anda mencatat respons API sisi server (umum untuk debugging), Anda mungkin mencatat kombinasi kode pos + spesialisasi tanpa menyadarinya.
Perbaikannya adalah backend proxying: server Anda menerima pencarian, memanggil API Geocoding MapAtlas, dan mengembalikan hanya koordinat, tidak pernah mengirim kueri yang diketik ke API pihak ketiga dari browser pasien. Lebih lanjut tentang ini di bagian implementasi.
Struktur Data Penyedia
Data penyedia harus berasal dari sumber resmi yang berwenang, bukan dataset yang dijambak:
- UK: NHS Choices API NHS Digital dan daftar penyedia terdaftar CQC
- Jerman: data terbuka Kassenärztliche Bundesvereinigung (KBV) dan API Arztsuche
- Prancis: API Ameli CNAM dan répertoire RPPS
Struktur setiap penyedia sebagai fitur GeoJSON:
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"
}
}
]
};
Langkah 1: Inisialisasi Peta dengan Filter Spesialisasi
Mulai dengan peta dan dropdown spesialisasi. Filter memperbarui penanda yang terlihat sisi klien tanpa permintaan jaringan apa pun.
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]);
}
});
Langkah 2: Pencarian Kode Pos Melalui Backend Proxy
Alih-alih memanggil API Geocoding dari browser (yang mengekspos kode pos pasien ke server pihak ketiga), arahkan permintaan geocoding melalui endpoint backend Anda sendiri. Server Anda memanggil MapAtlas dan mengembalikan hanya koordinatnya.
Frontend (browser pasien):
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 });
});
Kunci API Anda tetap berada di server dan kueri kode pos baku pasien tidak pernah dikirim ke pihak ketiga.
Langkah 3: Overlay Isokron, "Dokter Dalam 20 Menit"
Poligon isokron menunjukkan setiap titik yang dapat dijangkau dalam waktu berkendara tertentu. Menampilkannya pada pencari penyedia menjawab pertanyaan pasien yang paling praktis: "berapa banyak dokter yang sebenarnya bisa saya jangkau?"
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
});
Artikel Isochrone Maps Explained menyelam lebih dalam ke kasus penggunaan isokron, termasuk isokron transit publik (berguna untuk pasien tanpa mobil) dan overlay multi-pita waktu menampilkan zona 10, 20, dan 30 menit secara bersamaan.
Langkah 4: Popup Detail Penyedia
Ketika pasien mengklik penanda dokter, tunjukkan detail penting: nama, spesialisasi, status penerimaan, bahasa, dan janji temu berikutnya yang tersedia.
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 = ''; });
Langkah 5: Panel Daftar Disinkronkan dengan Peta
Tampilan daftar di samping peta membantu pengguna dengan pembaca layar dan mereka yang lebih suka menggulir daripada mengklik pin.
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);
});
}
Daftar Periksa Implementasi GDPR
Untuk pengembang kesehatan EU, anggap ini sebagai daftar periksa kepatuhan minimum:
- Backend proxy untuk geocoding: Kode pos pasien tidak boleh dikirim sisi klien ke API pihak ketiga.
- Tidak ada pencatatan istilah pencarian sesi: Jika Anda mencatat permintaan untuk debugging, lepaskan kode pos sebelum menulis ke log.
- Pemberitahuan privasi: Pemberitahuan privasi aplikasi Anda harus mengungkapkan bahwa data lokasi diproses untuk menemukan penyedia terdekat, dasar hukum (kepentingan sah atau persetujuan eksplisit), dan periode retensi.
- Persetujuan untuk akses lokasi: Jika Anda menggunakan API Geolocation browser (
navigator.geolocation.getCurrentPosition), tunjukkan pernyataan tujuan yang jelas sebelum memicu prompt izin browser. - Residensi data: MapAtlas memproses semua permintaan API dalam EU. Referensi MapAtlas DPA saat menyelesaikan Records of Processing Activities Anda.
- Retensi: Jika Anda meng-cache hasil isokron atau koordinat pencarian sisi server untuk kinerja, tentukan periode retensi (misalnya 24 jam) dan otomatisasi penghapusan.
Panduan EU Developer's Guide to GDPR-Compliant Map APIs mencakup perjanjian pemroses, residensi data, dan pola persetujuan secara penuh. Halaman industri Healthcare Services mencantumkan fitur MapAtlas spesifik yang relevan dengan aplikasi kesehatan digital.
Apa yang Harus Dibangun Selanjutnya
- Daftar untuk kunci API MapAtlas gratis, satu kunci untuk peta, geocoding, routing, dan isokron
- Tambahkan pola tutorial Store Locator untuk jaringan multi-klinik dengan lebih dari segelintir lokasi
- Jelajahi halaman kemampuan API Geocoding untuk pencarian kode pos, geocoding terbalik, dan validasi alamat penyedia batch
Pertanyaan yang Sering Diajukan
Mengapa GDPR berlaku pada peta pencari dokter bahkan jika saya tidak menyimpan data pasien?
Lokasi pencarian pasien pada pencari kesehatan mengungkapkan tempat mereka tinggal dan, dengan inferensi, kondisi apa yang mungkin mereka miliki (misalnya mencari ahli onkologi dekat kode pos). Menurut Pasal 9 GDPR, inferensi terkait kesehatan dari data lokasi dapat memenuhi syarat sebagai data kategori khusus. Bahkan tanpa menyimpan profil, permintaan geocoding real-time yang dikirim sisi klien ke API pihak ketiga membuat catatan pemrosesan yang memerlukan dasar hukum dan pengungkapan.
Apa itu isokron dan mengapa berguna untuk pencari penyedia?
Isokron adalah poligon yang menunjukkan setiap titik yang dapat dijangkau dalam waktu perjalanan tertentu dari lokasi awal. Pada pencari kesehatan, overlay isokron menjawab "dokter mana yang bisa saya jangkau dalam 20 menit berkendara?", pertanyaan yang jauh lebih berguna daripada jarak mentah, karena mempertimbangkan jalan, batas kecepatan, dan pola lalu lintas.
Dapatkah saya menggunakan MapAtlas untuk aplikasi NHS atau kesehatan masyarakat yang menghadap pasien?
Ya. MapAtlas dihosting di EU, bersertifikat ISO 27001, dan menyediakan Data Processing Agreement yang sesuai dengan GDPR menurut Pasal 28. Ini memenuhi persyaratan pengadaan sektor publik yang khas. NHS Digital Inggris dan badan setara di Jerman (DSGVO) dan Prancis (CNIL) memerlukan residensi data di dalam EU/UK, yang dipenuhi MapAtlas.

