Большинство продуктов локального поиска живут или умирают на одном экране: карте. Пользователь открывает ваше приложение, чтобы ответить на простой вопрос, что происходит рядом со мной прямо сейчас, и карта это то, как он на него отвечает. Сделать эту карту правильно, с верными точками, быстрой фильтрацией и листингами, которые поисковые системы действительно могут прочитать, вот основная инженерная работа за любым гидом по событиям.
Это руководство проходит через создание карты городских событий с нуля: моделирование данных о событии, геокодирование площадок в координаты, отрисовка и кластеризация маркеров, фильтрация по категории и району и обеспечение видимости листингов. Предполагается, что вы уже умеете отрисовывать базовую карту. Если нет, начните с нашего руководства о том, как добавить интерактивные карты на сайт, и вернитесь сюда за частями, специфичными для событий.
Стройте модель события вокруг его площадки
Событие это ограниченная во времени вещь, которая происходит в месте. Место это то, что попадает на карту, поэтому площадка это якорь вашей модели данных. Минимальная запись о событии выглядит так:
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, // заполняется геокодированием, один раз
neighbourhood: null, // заполняется обратным геокодированием, один раз
startsAt: '2026-07-09T20:30:00Z',
url: 'https://example.com/tickets/8471',
};
Два поля null важны. Вы не хотите геокодировать площадку каждый раз при загрузке карты. Геокодируйте один раз, при создании или импорте события, затем сохраните координаты и считайте их постоянными.
Шаг 1: геокодируйте каждую площадку в координаты
Геокодирование превращает человеческий адрес в широту и долготу, которые можно нанести на карту. Отправьте строку площадки в 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 };
}
Запускайте это на этапе записи, когда событие попадает в систему, и сохраняйте возвращённые координаты в записи. Полное объяснение того, почему точное расположение точки имеет значение и чем координаты крыши отличаются от двери, к которой люди действительно подходят, см. в статье геокодирование входа в здание.
Шаг 2: наносите события как маркеры
Когда у каждого события есть координаты, нанесение это цикл. Дайте каждой категории свой цвет, чтобы карта читалась с первого взгляда:
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, ежедневный гид по тому, чем заняться в Лондоне, который геокодирует каждую площадку и наносит события дня на интерактивную карту MapAtlas, чтобы пользователь мог просматривать происходящее по площадке и району, а не прокручивать плоский список.
Шаг 3: кластеризуйте, когда город заполняется
Отдельные маркеры хороши для горстки событий. Общегородская лента это не горстка. Как только вы переходите примерно за 100–200 точек, переместите события в источник GeoJSON и позвольте карте их кластеризовать:
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 },
})),
},
});
Теперь оживлённый вечер в центре города показывает один пузырёк со счётчиком вместо сотни перекрывающихся точек, а кластер распадается по мере приближения пользователя.
Шаг 4: фильтруйте по категории и району
Фильтрация это то, где карта событий превращается в продукт для событий. Держите полный список в памяти и фильтруйте массив, затем передавайте отфильтрованный набор источнику карты, а не пересобирайте маркеры вручную:
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, один раз выполните обратное геокодирование координат каждой площадки и сохраните название административной области. Это единственное значение питает выпадающий список районов, представление «что происходит в этом районе» и более чистую страницу листинга, и всё это без дополнительного запроса во время выполнения.
Шаг 5: сделайте листинги видимыми
Карта отвечает на вопрос для того, кто уже в вашем приложении. Она ничего не делает для гораздо большей аудитории, которая спрашивает поисковую систему или ИИ-ассистента «что происходит в Shoreditch сегодня вечером». Эти системы читают структурированные данные, а не пиксели.
Размечайте каждое событие полями Event из schema.org и переиспользуйте координаты, которые у вас уже есть:
{
"@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 }
}
}
Координаты, которые рисуют точку, также питают структурированные данные, поэтому отображение и поиск берутся из единого источника истины. В этом разница между картой, которая хорошо выглядит, и продуктом, который находят. Подробнее о том, почему машиночитаемые данные о местоположении определяют видимость в ИИ-поиске, см. в нашем руководстве по локальным FAQ для ИИ-поиска.
Собираем всё вместе
Карта городских событий это пять движущихся частей: модель данных, привязанная к площадке, геокодирование на этапе записи, цветные маркеры, кластеризация при масштабе и фильтрация на основе массива, и всё это поверх структурированных данных, которые держат листинги видимыми. Постройте эти части на одной геопространственной платформе, и координаты будут чисто перетекать от геокодера к карте и к схеме, без дублирующихся запросов и без расхождения данных.
MapAtlas даёт вам геокодирование, обратное геокодирование, поиск мест и отрисовку карт, чтобы построить всё это на инфраструктуре, размещённой в ЕС и соответствующей GDPR. Изучите Geocoding API и Search API, чтобы начать наносить на карту свой собственный город.
Часто задаваемые вопросы
Какие данные нужны для создания карты городских событий?
Как минимум, каждому событию нужны адрес площадки, категория и время начала. Именно адрес превращает событие в точку на карте: вы геокодируете его один раз, чтобы получить широту и долготу, сохраняете эти координаты и переиспользуете их при каждом показе события. Всё остальное, например ссылки на билеты, изображения и описания, это метаданные, которые вы прикрепляете к всплывающему окну маркера.
Как превратить адрес площадки в координаты на карте?
Вызовите API геокодирования, передав строку адреса площадки, и считайте широту и долготу из ответа. С помощью MapAtlas Geocoding API вы отправляете адрес как текстовый запрос на https://api.mapatlas.eu/geocoding/v1/search и используете координаты из верхнего результата. Геокодируйте каждую площадку один раз, при создании события, а не при каждой загрузке страницы, чтобы карта оставалась быстрой, а расход API низким.
Сколько маркеров событий может показать карта, прежде чем начнёт тормозить?
Отрисовка отдельных маркеров остаётся плавной примерно до 100–200 точек на отдалённом виде. Дальше переходите на кластеризацию источника GeoJSON: она объединяет близкие события в один пузырёк со счётчиком на малом зуме и разбивает их по мере приближения. Общегородская лента событий с сотнями листингов должна использовать кластеризацию с самого начала.
Как дать пользователям фильтровать события по категории или району?
Держите полный список событий в памяти как массив, прикрепите к каждому элементу категорию и район и фильтруйте этот массив в ответ на выбор пользователя. Затем обновляйте источник карты отфильтрованным набором, а не уничтожайте и не пересобирайте маркеры. Просмотр по району работает так же: отфильтруйте события, координаты которых попадают в выбранную область, или у которых совпадает административное имя из обратного геокодирования.
Зачем карте событий нужны структурированные данные?
Визуальная карта помогает людям, но поисковые системы и ИИ-ассистенты читают структурированные данные. Разметка каждого события полями Event из schema.org, включая название площадки, координаты и время начала, позволяет листингу появляться в ответах на вопросы вроде «что происходит в Shoreditch сегодня вечером». Те же координаты, которые вы наносите на карту, питают структурированные данные, поэтому поиск и отображение берутся из единого источника истины.

