یابندهی خدماتدهندهی بهداشتی یکی از مهمترین نقشههایی است که یک توسعهدهنده میتواند بسازد. بیمارانی که از آن استفاده میکنند اغلب نگران، دچار درد یا در حال پیمایش یک سیستم نامألوف هستند. نقشهای که آرام بار میشود، نتایج غیرمرتبط برمیگرداند یا بهخاموشی کدپستال آنها را به سرویسی طرفثالث مستقر در آمریکا میفرستد، نه تنها تجربهی کاربری بد است، بلکه نقض احتمالی GDPR است.
این آموزش یابندهی خدماتدهندهای آماده برای تولید میسازد: فیلتر تخصص، جستجوی کدپستال با استفاده از Geocoding API، پوشش ایزوکرون نشاندهندهی "پزشکان در ۲۰ دقیقه"، و نقشهی کاری با استفاده از MapAtlas Maps API، همهی اینها با انطباق GDPR از ابتدا درون معماری، نه پس از افزودن. پیادهسازی کامل JavaScript خالص است، هیچ فریمورکی موردنیاز نیست. در هر پشته کار میکند: React، Vue، HTML ساده، WordPress یا CMS قدیمی بیمارستان. آموزش همچنین زمینههای نظارتی اروپا، دادههای CQC بریتانیا، Kassenärztliche Vereinigung آلمان و CNAM فرانسه را پوشش میدهد تا بدانید کجا باید دادههای خدماتدهنده را بدست آورید.
چرا برنامههای مکانیابی بهداشتی الزامات GDPR اضافی دارند
برنامههای استاندارد دادههای شخصی (نام، ایمیل، استفاده) جمعآوری میکنند. برنامههای بهداشتی ریسک دوم دارند: استخراج اطلاعات سلامت از دادههای مکانی.
هنگامی که کاربر "کاردیولوژیستهای نزدیک SE1 7PB" را جستجو میکند، کدپستال آنها دادهی شخصی است. ترکیب کدپستال و تخصص پزشکی بهطور بالقوه دادههای دستهبندی خصوصی تحت مادهی 9(1) GDPR میشود، دادههایی که وضعیت سلامت را نشان میدهند. این حتی اگر هرگز حساب کاربری ایجاد نکنید یا پروفایل ذخیره نکنید درست است.
ریسکها عملی هستند:
- تماس API geocoding سمت کلاینت که شامل کدپستال تایپشدهی کاربر است به سرور طرفثالث فرستاده میشود. آن سرور کدپستال، تخصصی که جستجو میشود و آدرس IP را میبیند. بدون DPA و تضمین محل سکونت دادههای EU، این انتقال مشکلساز است.
- خودتکمیل مرورگر و تاریخچه ممکن است کدپستال تایپشده و جستجوی متخصص را در دستگاه کاربر باقی بگذارد، خارج از کنترل شما اما شایستهی اشاره در اطلاعات حریمخصوصی شما.
- اگر پاسخهای API را سمت سرور وارسی کنید (عمومی برای اشکالزدایی)، ممکن است ترکیبهای کدپستال + تخصص را بدون درک ثبت کنید.
راهحل proxy سمت سرور است: سرور شما جستجو را دریافت میکند، Geocoding API را فراخوانی میکند و فقط مختصات برمیگرداند، هرگز جستجوی تایپشده را به API طرفثالث از مرورگر بیمار نمیفرستد. بیشتر در بخش پیادهسازی.
ساختار دادهی خدماتدهنده
دادههای خدماتدهنده باید از منابع رسمی معتبر بیایند، نه مجموعههای اسکریپشده:
- بریتانیا: NHS Choices API NHS Digital و فهرستهای ثبتشدهی CQC
- آلمان: دادههای باز Kassenärztliche Bundesvereinigung (KBV) و Arztsuche API
- فرانسه: Ameli API CNAM و répertoire RPPS
هر خدماتدهنده را بهعنوان ویژگی 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"
}
}
]
};
مرحلهی ۱: اولیهسازی نقشه با فیلتر تخصص
با نقشه و فهرست کشویی تخصص شروع کنید. فیلتر علامتهای دیدهشده را از سمت کلاینت بدون هیچ درخواست شبکهای بهروز میکند.
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
});
// علامتهای خدماتدهنده، سبز = پذیرش، خاکستری = کامل
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);
});
// فیلتر تخصص
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]);
}
});
مرحلهی ۲: جستجوی کدپستال از طریق Proxy سرویس
به جای فراخوانی Geocoding API از مرورگر (که کدپستال بیمار را به سرور طرفثالث نمایش میدهد)، درخواست geocoding را از طریق نقطهی پایانی backend خود هدایت کنید. سرور شما MapAtlas را فراخوانی میکند و فقط مختصات برمیگرداند.
Frontend (مرورگر بیمار):
async function geocodePostcode(postcode) {
// سرویس backend خود را فراخوانی کنید، نه API geocoding بهطور مستقیم
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);
});
Proxy سمت سرور (Node.js/Express):
// /api/geocode، فقط سمت سرور، کدپستال هرگز تحتالحفاظ شما را ترک نمیکند
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); // کلید سمت سرور
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;
// فقط مختصات برگردانید، کدپستال ثبت نشده، بدون تخصص در پاسخ
res.json({ lat, lng });
});
کلید API شما سمت سرور باقی میماند و جستجوی خام کدپستال بیمار هرگز به طرفثالث فرستاده نمیشود.
مرحلهی ۳: پوشش ایزوکرون، "پزشکان در ۲۰ دقیقه"
چندضلعی ایزوکرون هر نقطهای را نشان میدهد که در زمان رانندگی معین قابل دسترسی است. نمایش آن در یابندهی خدماتدهنده به مهمترین سؤال بیمار پاسخ میدهد: "در واقع چند پزشک میتوانم به آنها برسم؟"
async function fetchIsochrone(lat, lng) {
// از طریق proxy backend خود برای علل حریمخصوصی یکسان فراخوانی کنید
const res = await fetch(
`/api/isochrone?lat=${lat}&lng=${lng}&minutes=20&profile=driving`
);
const geojson = await res.json();
// لایهی ایزوکرون موجود را بردارید
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'); // در زیر علامتها وارد کنید تا پینها رندر شوند
map.addLayer({
id: 'isochrone-border',
type: 'line',
source: 'isochrone',
paint: {
'line-color': '#16A34A',
'line-width': 2,
'line-dasharray': [3, 2]
}
}, 'provider-markers');
}
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); // ثانیه
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
});
مقاله Isochrone Maps Explained بیشتر در مورد موارد استفاده ایزوکرون، شامل ایزوکرون حملونقل عمومی (مفید برای بیمارانی بدون اتومبیل) و پوششهای چندبندزمانی نشاندهندهی مناطق ۱۰، ۲۰ و ۳۰ دقیقهای بهطور همزمان است.
مرحلهی ۴: بالای جزئیات خدماتدهنده
هنگامی که بیمار بر روی علامت پزشک کلیک میکند، جزئیات ضروری را نشان دهید: نام، تخصص، وضعیت پذیرش، زبانها و قرار ملاقات بعدی.
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 = ''; });
مرحلهی ۵: پانل فهرست همگام با نقشه
نمای فهرست در کنار نقشه به کاربرانی که صفحهخوان استفاده میکنند و آنانی که ترجیح میدهند بلغزانند به جای کلیک بر روی پینها کمک میکند.
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
برای توسعهدهندگان بهداشتی اروپا، این را بهعنوان فهرست انطباق حداقلی درنظر بگیرید:
- Proxy backend برای geocoding: کدپستالهای بیمار نباید به APIهای طرفثالث از سمت کلاینت فرستاده شوند.
- بدون ثبت جستجوی جلسه: اگر درخواستها را برای اشکالزدایی وارسی میکنید، کدپستال را قبل از نوشتن در logها خالی کنید.
- اطلاع حریمخصوصی: اطلاع حریمخصوصی برنامه شما باید افشا کند که دادههای مکانی برای یافتن خدماتدهندگان نزدیک پردازش میشوند، مبنای قانونی (منفعت مشروع یا رضایت صریح) و دورهی حفظ.
- رضایت برای دسترسی مکان: اگر Geolocation API مرورگر (
navigator.geolocation.getCurrentPosition) را استفاده میکنید، قبل از فعالکردن پرامپت اجازهی مرورگر اظهار مقصد واضحی نشان دهید. - حلابی دادهها: MapAtlas تمام درخواستهای API را در اروپا پردازش میکند. هنگام تکمیل سوابق فعالیتهای پردازش خود به MapAtlas DPA ارجاع دهید.
- حفظدوری: اگر نتایج ایزوکرون یا مختصات جستجو را سمت سرور برای عملکرد ذخیره میکنید، دورهی حفظ (مثلا ۲۴ ساعت) تعریف کنید و حذف را خودکار کنید.
راهنمای EU Developer's Guide to GDPR-Compliant Map APIs توافقهای پردازشگر، حلابی دادهها و الگوهای رضایت را بهطور کامل پوشش میدهد. صفحهی صنعت Healthcare Services ویژگیهای MapAtlas مرتبط با برنامههای بهداشتی دیجیتالی را فهرست میکند.
آنچه بعد باید بسازید
- برای کلید MapAtlas رایگان ثبتنام کنید، یک کلید برای نقشهها، geocoding، مسیریابی و ایزوکرونها
- الگوی Store Locator tutorial را برای شبکههای چندمرکزیای با بیش از تعدادی مکان اضافه کنید
- Geocoding API capabilities page را برای جستجوی کدپستال، geocoding معکوس و اعتبارسنجی آدرس خدماتدهنده دستهای کاوش کنید
سؤالات متكرر
چرا GDPR برای نقشهی یابندهی پزشک حتی اگر دادههای بیمار ذخیره نکنم اعمال میشود؟
محل جستجوی بیمار در یابندهی خدماتدهندهی بهداشتی نشان میدهد کجا زندگی میکند و براساس استنتاج، چه شرایطی ممکن است داشته باشد (مثلا جستجوی آنکولوژیستها نزدیک کدپستال). طبق مادهی 9 GDPR، استنتاجهای مرتبط با سلامت از دادههای مکانی میتواند دادههای دستهبندی خصوصی باشد. حتی بدون ذخیره پروفایل، درخواستهای geocoding زمانواقعی فرستادهشده از سمت کلاینت به APIهای طرفثالث، رکورد پردازش ایجاد میکند که مبنای قانونی و افشاء را نیاز دارد.
ایزوکرون چیست و چرا برای یابندهی خدماتدهنده مفید است؟
ایزوکرون چندضلعی است که هر نقطهای قابل دسترسی در زمان سفر مشخص از محل مبدا را نشان میدهد. در یابندهی خدماتدهندهی بهداشتی، پوشش ایزوکرون به سؤال عملی بیمار پاسخ میدهد: "کدام پزشکان در ۲۰ دقیقه رانندگی هستند؟"، سؤال بسیار مفیدتر از فاصلهی خام، زیرا جادهها، حدسرعت و الگوهای ترافیکی را درنظر میگیرد.
آیا میتوانم MapAtlas را برای برنامهی روبهبیمار NHS یا بهداشت عمومی استفاده کنم؟
بله. MapAtlas با میزبانی در اروپا، ISO 27001 رسانیشده و Data Processing Agreement سازگار با GDPR تحت مادهی ۲۸ را فراهم میکند. این الزامات معمول procurement بخش عمومی را برآورده میکند. NHS Digital بریتانیا و مجموعههای معادل در آلمان (DSGVO) و فرانسه (CNIL) محل سکونت دادههای EU/UK را الزام میکنند، که MapAtlas آن را فراهم میکند.

