بیشتر محصولات کشف محلی روی یک صفحه زنده یا مردهاند: نقشه. کاربر اپلیکیشن شما را باز میکند تا به یک سوال ساده پاسخ دهد، همین حالا نزدیک من چه خبر است، و نقشه همان راهی است که با آن پاسخ میگیرد. درست کردن آن نقشه، با pinهای درست، فیلتر سریع و لیستهایی که موتورهای جستجو واقعا بتوانند بخوانند، هسته کار مهندسی پشت هر راهنمای رویداد است.
این راهنما ساخت یک نقشه رویدادهای شهری را از پایه توضیح میدهد: مدلسازی داده رویداد، geocode کردن مکانها به مختصات، رسم و cluster کردن markerها، فیلتر بر اساس دسته و محله، و قابل کشف کردن لیست رویدادها. فرض بر این است که شما از قبل میدانید چطور یک نقشه پایه را رندر کنید. اگر نمیدانید، با آموزش ما درباره چطور نقشههای تعاملی را به وبسایت خود اضافه کنیم شروع کنید و برای بخشهای مخصوص رویداد به اینجا برگردید.
رویداد را حول مکان آن مدل کنید
یک رویداد چیزی زمانمند است که در یک مکان اتفاق میافتد. آن مکان همان چیزی است که روی نقشه میرود، پس مکان لنگرگاه مدل داده شماست. یک رکورد رویداد حداقلی این شکلی است:
const event = {
id: 'evt_8471',
title: 'Late Night Jazz at The Vortex',
category: 'live-music',
venue: 'The Vortex Jazz Club, 11 Gillett Square, London N16 8AZ',
coords: null, // فقط یک بار توسط geocoding پر میشود
neighbourhood: null, // فقط یک بار توسط reverse geocoding پر میشود
startsAt: '2026-07-09T20:30:00Z',
url: 'https://example.com/tickets/8471',
};
آن دو فیلد null مهماند. شما نمیخواهید هر بار که نقشه بارگذاری میشود یک مکان را geocode کنید. یک بار geocode کنید، وقتی رویداد ساخته یا import میشود، بعد مختصات را ذخیره کنید و آنها را دائمی در نظر بگیرید.
قدم ۱: هر مکان را به مختصات geocode کنید
Geocoding یک آدرس انسانی را به طول و عرض جغرافیایی قابل رسم تبدیل میکند. رشته مکان را به Geocoding API بفرستید و مختصات را از نتیجه اول بخوانید:
async function geocodeVenue(address) {
const url = new URL('https://api.mapatlas.eu/geocoding/v1/search');
url.searchParams.set('text', address);
url.searchParams.set('size', '1');
const res = await fetch(url, { headers: { Authorization: `Bearer ${API_KEY}` } });
const data = await res.json();
const top = data.features?.[0];
if (!top) return null;
const [lon, lat] = top.geometry.coordinates;
return { lat, lon, label: top.properties.label };
}
این را در زمان نوشتن اجرا کنید، وقتی یک رویداد وارد سیستم شما میشود، و مختصات برگشتی را روی رکورد persist کنید. برای توضیح کامل اینکه چرا مکان دقیق pin اهمیت دارد، و اینکه مختصات پشتبام چطور با دری که آدمها واقعا از آن رد میشوند فرق میکند، به geocoding ورودی ساختمان مراجعه کنید.
قدم ۲: رویدادها را بهصورت marker رسم کنید
با داشتن مختصات روی هر رویداد، رسم کردن یک حلقه است. به هر دسته رنگ مخصوص خودش را بدهید تا نقشه در یک نگاه خوانا باشد:
const CATEGORY_COLORS = {
'live-music': '#E75480',
'theatre': '#006BA6',
'comedy': '#EE7C0E',
'food': '#16A34A',
'nightlife': '#7C3AED',
};
function plotEvents(map, events) {
events.forEach((evt) => {
if (!evt.coords) return;
const popup = new mapmetricsgl.Popup().setHTML(
`<strong>${evt.title}</strong><br>${new Date(evt.startsAt).toLocaleString()}` +
`<br><a href="${evt.url}">بلیطها</a>`,
);
new mapmetricsgl.Marker({ color: CATEGORY_COLORS[evt.category] || '#0c3456' })
.setLngLat([evt.coords.lon, evt.coords.lat])
.setPopup(popup)
.addTo(map);
});
}
این الگوی پشت محصولات واقعی است. یک نمونه زنده OnlyHere است، یک راهنمای روزانه برای کارهایی که میتوان در London انجام داد که هر مکان را geocode میکند و رویدادهای روز را روی یک نقشه تعاملی MapAtlas رسم میکند، تا کاربر بتواند بهجای اسکرول کردن یک لیست تخت، بر اساس مکان و محله ببیند چه خبر است.
قدم ۳: وقتی شهر شلوغ میشود cluster کنید
markerهای جداگانه برای تعداد کمی رویداد خوباند. یک فید در سطح شهر تعداد کمی نیست. بهمحض اینکه از حدود ۱۰۰ تا ۲۰۰ pin عبور کردید، رویدادها را به یک source از نوع GeoJSON منتقل کنید و بگذارید نقشه آنها را cluster کند:
map.addSource('events', {
type: 'geojson',
cluster: true,
clusterRadius: 50,
data: {
type: 'FeatureCollection',
features: events.filter((e) => e.coords).map((e) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [e.coords.lon, e.coords.lat] },
properties: { title: e.title, category: e.category },
})),
},
});
حالا یک شب شلوغ در مرکز شهر بهجای صد pin رویهم افتاده، یک حباب تنها با یک شمارش نشان میدهد و cluster با zoom کردن کاربر از هم باز میشود.
قدم ۴: بر اساس دسته و محله فیلتر کنید
فیلتر کردن جایی است که یک نقشه رویداد به یک محصول رویداد تبدیل میشود. کل لیست را در حافظه نگه دارید و array را فیلتر کنید، بعد مجموعه فیلترشده را به source نقشه بدهید بهجای اینکه markerها را دستی دوباره بسازید:
function applyFilters(map, allEvents, { category, neighbourhood }) {
const filtered = allEvents.filter((e) =>
(!category || e.category === category) &&
(!neighbourhood || e.neighbourhood === neighbourhood),
);
map.getSource('events').setData({
type: 'FeatureCollection',
features: filtered.filter((e) => e.coords).map((e) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [e.coords.lon, e.coords.lat] },
properties: { title: e.title },
})),
});
return filtered;
}
برای پر کردن فیلد neighbourhood، مختصات هر مکان را یک بار reverse geocode کنید و نام ناحیه admin را ذخیره کنید. آن یک مقدار، یک dropdown محله، یک نمای "در این ناحیه چه خبر است" و یک صفحه لیست تمیزتر را قدرت میدهد، همه بدون یک lookup دیگر در زمان اجرا.
قدم ۵: لیست رویدادها را قابل کشف کنید
یک نقشه برای کسی که از قبل داخل اپلیکیشن شماست به سوال پاسخ میدهد. برای مخاطب بسیار بزرگتری که از یک موتور جستجو یا یک دستیار AI میپرسد "امشب در Shoreditch چه خبر است" هیچ کاری نمیکند. آن سیستمها داده ساختیافته را میخوانند، نه پیکسل را.
هر رویداد را با فیلدهای Event از schema.org markup کنید و از مختصاتی که از قبل دارید دوباره استفاده کنید:
{
"@context": "https://schema.org",
"@type": "Event",
"name": "Late Night Jazz at The Vortex",
"startDate": "2026-07-09T20:30:00Z",
"location": {
"@type": "Place",
"name": "The Vortex Jazz Club",
"geo": { "@type": "GeoCoordinates", "latitude": 51.5462, "longitude": -0.0753 }
}
}
مختصاتی که pin را میکشد، داده ساختیافته را هم تغذیه میکند، پس نمایش و کشف هر دو از یک منبع حقیقت میآیند. این تفاوت بین یک نقشه است که خوب به نظر میرسد و یک محصول که پیدا میشود. برای اطلاعات بیشتر درباره اینکه چرا داده مکانی قابلخواندن توسط ماشین باعث دیده شدن در جستجوی AI میشود، به راهنمای ما درباره پرسشهای متداول مخصوص مکان برای جستجوی AI مراجعه کنید.
کنار هم گذاشتن همه چیز
یک نقشه رویدادهای شهری پنج قطعه متحرک است: یک مدل داده لنگرانداخته روی مکان، geocoding در زمان نوشتن، markerهای رنگی، clustering در مقیاس، و فیلتر مبتنی بر array، همه روی داده ساختیافتهای که لیست رویدادها را قابل کشف نگه میدارد. این قطعات را روی یک پلتفرم جغرافیایی واحد بسازید و مختصات بهطور تمیز از geocoder به نقشه و به schema جریان پیدا میکند، بدون lookupهای تکراری و بدون دادهای که drift میکند.
MapAtlas به شما geocoding، reverse geocoding، جستجوی مکان و رندر نقشه را میدهد تا همه اینها را روی زیرساخت میزبانیشده در EU و سازگار با GDPR بسازید. Geocoding API و Search API را کاوش کنید تا شروع به رسم شهر خودتان کنید.
سوالات متداول
برای ساخت یک نقشه رویدادهای شهری به چه دادههایی نیاز دارم؟
در حداقلیترین حالت، هر رویداد به آدرس مکان، یک دستهبندی و زمان شروع نیاز دارد. آدرس همان چیزی است که یک رویداد را به یک pin روی نقشه تبدیل میکند: یک بار آن را geocode میکنید تا طول و عرض جغرافیایی را به دست آورید، این مختصات را ذخیره میکنید و هر بار که رویداد نمایش داده میشود دوباره از آنها استفاده میکنید. بقیه چیزها مثل لینک بلیط، تصاویر و توضیحات، متادیتایی هستند که به popup مربوط به marker وصل میکنید.
چطور یک آدرس مکان را به مختصات نقشه تبدیل کنم؟
یک geocoding API را با رشته آدرس مکان صدا بزنید و طول و عرض جغرافیایی را از پاسخ بخوانید. با MapAtlas Geocoding API آدرس را بهصورت یک query متنی به https://api.mapatlas.eu/geocoding/v1/search میفرستید و از مختصات نتیجه اول استفاده میکنید. هر مکان را فقط یک بار و هنگام ساخته شدن رویداد geocode کنید، نه در هر بار بارگذاری صفحه، تا نقشه سریع بماند و مصرف API شما پایین بماند.
یک نقشه قبل از کند شدن میتواند چند marker رویداد را نمایش دهد؟
رندر کردن markerهای جداگانه تا حدود ۱۰۰ تا ۲۰۰ pin در یک نمای zoom-out شده روان میماند. فراتر از آن، به clustering با source از نوع GeoJSON سوییچ کنید که در zoom پایین رویدادهای نزدیک را در یک حباب شمارشی جمع میکند و با zoom کردن کاربر آنها را از هم جدا میکند. یک فید رویداد در سطح شهر با صدها لیست باید از همان ابتدا از clustering استفاده کند.
چطور بگذارم کاربران رویدادها را بر اساس دسته یا محله فیلتر کنند؟
کل لیست رویدادها را بهصورت یک array در حافظه نگه دارید، به هر آیتم یک دسته و یک محله وصل کنید و آن array را در پاسخ به انتخاب کاربر فیلتر کنید. بعد بهجای نابود کردن و ساختن دوباره markerها، source نقشه را با مجموعه فیلترشده بهروز کنید. مرور بر اساس محله هم به همین شکل کار میکند: به رویدادهایی فیلتر کنید که مختصاتشان داخل ناحیه انتخابشده میافتد، یا نام admin مشترکی از reverse geocoding دارند.
چرا یک نقشه رویداد به داده ساختیافته نیاز دارد؟
یک نقشه بصری به انسانها کمک میکند، اما موتورهای جستجو و دستیارهای AI داده ساختیافته را میخوانند. markup کردن هر رویداد با فیلدهای Event از schema.org، شامل نام مکان، مختصات و زمان شروع، همان چیزی است که باعث میشود یک لیست در پاسخ به سوالاتی مثل 'امشب در Shoreditch چه خبر است' ظاهر شود. همان مختصاتی که روی نقشه رسم میکنید داده ساختیافته را هم تغذیه میکند، پس کشف و نمایش هر دو از یک منبع حقیقت میآیند.

