Setting up geolocation for the nearest 1C-Bitrix store

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    565
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

Setting up geolocation-based nearest store detection in 1C-Bitrix

A user opens the "Our Stores" page and sees a list of 30 addresses with no order. They live in Minsk, but the first five stores are in Moscow. Seemingly trivial task — show the nearest ones — in practice comes down to geolocation accuracy, distance calculation, and storing store coordinates in Bitrix.

Storing store data

In Bitrix stores are kept in two places depending on used functionality:

Module sale, pickup points: table b_sale_store with fields ID, TITLE, ADDRESS, GPS_N (latitude), GPS_S (longitude). This is the standard structure for pickup points.

Info block of stores: if stores are formatted as infoblock elements, coordinates usually stored in custom properties type String or in specialized fields like Map (with third-party modules).

For distance calculation you need numeric coordinates. If they're stored in string property in format "53.9045, 27.5615" — you'll need to parse them during retrieval, which is inconvenient. Right solution: store latitude and longitude in two separate numeric properties or use b_sale_store.GPS_N / b_sale_store.GPS_S.

Determining user location

Two approaches:

Browser geolocation (Geolocation API): accurate to 50 meters but requires user permission. Asynchronous — can't use with server-side rendering.

IP geolocation: works without permission request, accuracy — down to city/district. Bitrix has built-in location module with GeoIP database (b_geocode_city, b_geocode_country). Method \Bitrix\Location\Service\FormatService::getInstance() works with addresses; for IP geolocation use \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;

Distance calculation: Haversine formula in SQL

Most efficient approach — calculate distances directly in SQL query. Haversine formula for 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;

For MySQL syntax is similar. On PostgreSQL you can additionally use earthdistance extension with cube, which is faster for large point sets.

Via Bitrix ORM direct SQL is called through \Bitrix\Main\Application::getConnection()->query(). D7 ORM has no built-in Haversine expression — you'll need to use ExpressionField with raw SQL or native query.

Frontend: two steps

  1. On page load — show stores sorted by IP geolocation (server-side sort, instant).
  2. After getting exact coordinates via navigator.geolocation.getCurrentPosition() — re-sort via AJAX request to component with lat and lon parameters.
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 handler component reads lat/lon from GET, runs SQL with Haversine, returns JSON. In Bitrix this is implemented via component with ajax_mode = Y parameter or via custom endpoint in /local/ajax/.

What we configure

  • Checking coordinate presence in b_sale_store or store infoblock, filling them in
  • SQL query with Haversine formula via Application::getConnection()
  • IP geolocation via GeoIp\Manager for initial sort without user permission
  • AJAX endpoint for re-sorting after getting exact browser coordinates
  • Caching results for each coordinate pair (rounded to 0.01 degree)