Настройка фильтрации точек на карте магазинов 1С-Битрикс
Сеть магазинов — 50 точек по городам, разные форматы, разный режим работы. Пользователь хочет найти ближайший магазин с нужным ассортиментом, открытый прямо сейчас. Стандартный компонент bitrix:map.yandex.view просто показывает все метки без фильтрации. Фильтрация — кастомный функционал.
Хранение данных о магазинах
Точки магазинов удобнее хранить в инфоблоке, не в стандартном справочнике b_catalog_store. Инфоблок даёт гибкость в полях, мультиязычность, удобный редактор в административной части.
Инфоблок Магазины (например, STORES):
| Свойство | Код | Тип |
|---|---|---|
| Адрес | ADDRESS |
Строка |
| Город | CITY |
Список |
| Формат | FORMAT |
Список (Гипермаркет / Супермаркет / Магазин у дома) |
| Режим работы | SCHEDULE |
Строка |
| Широта | LAT |
Число |
| Долгота | LON |
Число |
| Телефон | PHONE |
Строка |
| Метро | METRO |
Строка |
API эндпоинт для карты
Фронтенд карты работает через AJAX. PHP-эндпоинт принимает параметры фильтра и возвращает точки:
// /local/ajax/stores.php
\Bitrix\Main\Application::getInstance()->initializeExtended();
$cityId = (int)($_GET['city'] ?? 0);
$format = trim($_GET['format'] ?? '');
$openNow = ($_GET['open_now'] ?? '') === '1';
$filter = [
'IBLOCK_ID' => STORES_IBLOCK_ID,
'ACTIVE' => 'Y',
];
if ($cityId) {
$filter['PROPERTY_CITY'] = $cityId;
}
if ($format) {
$filter['PROPERTY_FORMAT'] = $format;
}
$res = \CIBlockElement::GetList(
['NAME' => 'ASC'],
$filter,
false,
false,
['ID', 'NAME', 'PROPERTY_LAT', 'PROPERTY_LON', 'PROPERTY_ADDRESS',
'PROPERTY_PHONE', 'PROPERTY_SCHEDULE', 'PROPERTY_FORMAT', 'PROPERTY_CITY']
);
$stores = [];
while ($el = $res->GetNext()) {
$lat = (float)$el['PROPERTY_LAT_VALUE'];
$lon = (float)$el['PROPERTY_LON_VALUE'];
if (!$lat || !$lon) continue;
if ($openNow && !isOpenNow($el['PROPERTY_SCHEDULE_VALUE'])) {
continue;
}
$stores[] = [
'id' => $el['ID'],
'name' => $el['NAME'],
'address' => $el['PROPERTY_ADDRESS_VALUE'],
'phone' => $el['PROPERTY_PHONE_VALUE'],
'schedule' => $el['PROPERTY_SCHEDULE_VALUE'],
'format' => $el['PROPERTY_FORMAT_VALUE'],
'lat' => $lat,
'lon' => $lon,
];
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['stores' => $stores, 'count' => count($stores)]);
Определение «открыт сейчас»
Режим работы хранится в виде строки «Пн-Пт: 9:00-21:00, Сб-Вс: 10:00-20:00». Функция парсит строку и проверяет текущее время:
function isOpenNow(string $schedule): bool
{
$now = new DateTime('now', new DateTimeZone('Europe/Moscow'));
$dayNum = (int)$now->format('N'); // 1=Пн, 7=Вс
$timeStr = $now->format('H:i');
// Парсим шаблон "Пн-Пт: 9:00-21:00"
preg_match_all('/([А-Яа-я-]+):\s*(\d+:\d+)-(\d+:\d+)/u', $schedule, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
if (dayRangeCovers($m[1], $dayNum) && timeInRange($timeStr, $m[2], $m[3])) {
return true;
}
}
return false;
}
Фронтенд: Яндекс Карты + фильтр
// Инициализация карты и фильтрации
ymaps.ready(async function() {
const map = new ymaps.Map('store-map', { center: [55.76, 37.64], zoom: 10 });
const clusterer = new ymaps.Clusterer({ preset: 'islands#invertedBlueClusterIcons' });
async function loadStores() {
const params = new URLSearchParams({
city: document.getElementById('filter-city').value,
format: document.getElementById('filter-format').value,
open_now: document.getElementById('filter-open').checked ? '1' : '0',
});
const data = await fetch('/local/ajax/stores.php?' + params).then(r => r.json());
clusterer.removeAll();
map.geoObjects.remove(clusterer);
const placemarks = data.stores.map(store => {
const pm = new ymaps.Placemark(
[store.lat, store.lon],
{
balloonContentHeader: store.name,
balloonContentBody:
`<b>${store.address}</b><br>${store.phone}<br>${store.schedule}`,
hintContent: store.name,
},
{ preset: 'islands#blueDotIcon' }
);
return pm;
});
clusterer.add(placemarks);
map.geoObjects.add(clusterer);
document.getElementById('store-count').textContent = data.count;
}
// Загрузка при изменении фильтров
document.querySelectorAll('.store-filter').forEach(el => {
el.addEventListener('change', loadStores);
});
loadStores();
});
Геолокация «Рядом со мной»
При клике «Показать ближайшие» браузер запрашивает координаты пользователя и сортирует точки по расстоянию:
navigator.geolocation.getCurrentPosition(pos => {
const userLat = pos.coords.latitude;
const userLon = pos.coords.longitude;
// Сортировка по расстоянию (формула Haversine — достаточно для небольших расстояний)
stores.sort((a, b) => {
const da = Math.hypot(a.lat - userLat, a.lon - userLon);
const db = Math.hypot(b.lat - userLat, b.lon - userLon);
return da - db;
});
// Перемещаем карту к пользователю
map.panTo([userLat, userLon], { duration: 500 });
});
Кеширование
Список магазинов меняется редко. Данные кешируются в файловом кеше Битрикс на 3600 секунд и инвалидируются при редактировании элемента инфоблока через обработчик события OnAfterIBlockElementUpdate.
| Конфигурация | Срок |
|---|---|
| Базовая карта с фильтрами город/формат | 3–4 дня |
| + геолокация, «открыт сейчас», кластеризация | +2–3 дня |
| + интеграция с ассортиментом (фильтр по товарам в точке) | +1 неделя |







