Настройка геолокационного определения ближайшего магазина 1С-Битрикс
Пользователь открывает страницу «Наши магазины» и видит список из 30 адресов без какого-либо порядка. Он живёт в Минске, а первые пять магазинов в списке — Москва. Казалось бы, банальная задача — показать ближайшие — на практике упирается в точность геолокации, расчёт расстояний и хранение координат магазинов в Битрикс.
Хранение данных магазинов
В Битрикс магазины хранятся в двух местах в зависимости от используемого функционала:
Модуль sale, торговые точки: таблица b_sale_store с полями ID, TITLE, ADDRESS, GPS_N (широта), GPS_S (долгота). Это стандартная структура для точек самовывоза.
Инфоблок магазинов: если магазины оформлены как элементы инфоблока, координаты обычно хранятся в пользовательских свойствах типа String или в специализированных полях типа Map (при наличии сторонних модулей).
Для расчёта расстояний нужны числовые координаты. Если они хранятся в строковом свойстве инфоблока в формате «53.9045, 27.5615» — при выборке их придётся парсить, что неудобно. Правильное решение: хранить широту и долготу в двух отдельных числовых свойствах или использовать b_sale_store.GPS_N / b_sale_store.GPS_S.
Определение координат пользователя
Два подхода:
Браузерная геолокация (Geolocation API): точная, до 50 метров, но требует разрешения пользователя. Асинхронная — нельзя использовать при серверном рендеринге.
IP-геолокация: работает без запроса разрешения, точность — до города/района. В Битрикс встроен модуль location с базой GeoIP (b_geocode_city, b_geocode_country). Метод \Bitrix\Location\Service\FormatService::getInstance() работает с адресами, для IP-геолокации используется \Bitrix\Main\Service\GeoIp\Manager::getLocationByIp().
$location = \Bitrix\Main\Service\GeoIp\Manager::getLocationByIp(
\Bitrix\Main\Context::getCurrent()->getRequest()->getRemoteAddress()
);
$userLat = $location['LATITUDE'] ?? null;
$userLon = $location['LONGITUDE'] ?? null;
Расчёт расстояний: формула Хаверсина в SQL
Самый эффективный подход — считать расстояния прямо в SQL-запросе. Формула Хаверсина для PostgreSQL:
SELECT
id,
title,
gps_n AS lat,
gps_s AS lon,
(
6371 * acos(
cos(radians(:user_lat)) * cos(radians(gps_n)) *
cos(radians(gps_s) - radians(:user_lon)) +
sin(radians(:user_lat)) * sin(radians(gps_n))
)
) AS distance_km
FROM b_sale_store
WHERE active = 'Y'
AND gps_n IS NOT NULL
AND gps_s IS NOT NULL
ORDER BY distance_km
LIMIT 5;
Для MySQL синтаксис аналогичен. На PostgreSQL дополнительно можно использовать расширение earthdistance с cube, что быстрее для больших наборов точек.
Через ORM Битрикс прямой SQL вызывается через \Bitrix\Main\Application::getConnection()->query(). Готового выражения Хаверсина в D7 ORM нет — придётся использовать ExpressionField с raw SQL или нативный запрос.
Фронтенд: два шага
- При загрузке страницы — показываем магазины, отсортированные по IP-геолокации (серверная сортировка, мгновенно).
- После получения точных координат через
navigator.geolocation.getCurrentPosition()— пересортировываем через AJAX-запрос к компоненту с параметрамиlatиlon.
navigator.geolocation.getCurrentPosition(function(pos) {
fetch('/ajax/nearest-stores/?lat=' + pos.coords.latitude + '&lon=' + pos.coords.longitude)
.then(r => r.json())
.then(stores => renderStoreList(stores));
});
Компонент-обработчик AJAX читает lat/lon из GET, выполняет SQL с Хаверсином, возвращает JSON. В Битрикс это реализуется через компонент с параметром ajax_mode = Y или через собственный endpoint в /local/ajax/.
Что настраиваем
- Проверку наличия координат в
b_sale_storeили инфоблоке магазинов, их заполнение - SQL-запрос с формулой Хаверсина через
Application::getConnection() - IP-геолокацию через
GeoIp\Managerдля первичной сортировки без разрешения пользователя - AJAX-endpoint для пересортировки после получения точных координат браузера
- Кеширование результата для каждой пары координат (округлённой до 0.01 градуса)







