Setting Up Nearest Store Suggestions by Geolocation in 1C-Bitrix
The "find the nearest store" feature is needed by retailers with a network of offline locations. The user grants location access — the site displays the nearest store, its opening hours, and product stock. This article covers an implementation based on the Bitrix warehouse module.
Storing Store Addresses
In Bitrix, stores (warehouses/retail points) are stored in the b_catalog_store table (the catalog module). Each warehouse has fields: TITLE, ADDRESS, PHONE, SCHEDULE, GPS_N (latitude), GPS_S (longitude), ACTIVE.
If the GPS_N / GPS_S fields are not filled in — the addresses must be geocoded (via Yandex or Google APIs) and coordinates saved. This is done once for all stores.
Adding coordinates to a store programmatically:
\Bitrix\Catalog\StoreTable::update($storeId, [
'GPS_N' => 53.9045, // latitude
'GPS_S' => 27.5615, // longitude
]);
Calculating Distance and Selecting the Nearest Store
The user's coordinates are obtained via JavaScript navigator.geolocation.getCurrentPosition() and sent to the server. The server calculates the distance to each warehouse using the Haversine formula.
Query with distance-sorted results via SQL (PostgreSQL/MySQL with the Haversine formula):
SELECT id, title, address, gps_n, gps_s,
(6371 * acos(
cos(radians(:lat)) * cos(radians(gps_n)) *
cos(radians(gps_s) - radians(:lng)) +
sin(radians(:lat)) * sin(radians(gps_n))
)) AS distance
FROM b_catalog_store
WHERE active = 'Y' AND gps_n IS NOT NULL
ORDER BY distance ASC
LIMIT 5;
Or a PHP implementation without direct SQL, if you prefer to use the Bitrix ORM:
function haversineDistance(
float $lat1, float $lon1,
float $lat2, float $lon2
): float {
$R = 6371; // km
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat/2)**2
+ cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2)**2;
return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
}
$stores = \Bitrix\Catalog\StoreTable::getList([
'filter' => ['=ACTIVE' => 'Y', '!=GPS_N' => false],
'select' => ['ID', 'TITLE', 'ADDRESS', 'GPS_N', 'GPS_S', 'SCHEDULE', 'PHONE'],
])->fetchAll();
usort($stores, function($a, $b) use ($userLat, $userLng) {
$dA = haversineDistance($userLat, $userLng, $a['GPS_N'], $a['GPS_S']);
$dB = haversineDistance($userLat, $userLng, $b['GPS_N'], $b['GPS_S']);
return $dA <=> $dB;
});
$nearest = array_slice($stores, 0, 3);
AJAX Endpoint for the Frontend
// /local/ajax/nearest-store.php
$lat = (float)$_POST['lat'];
$lng = (float)$_POST['lng'];
// ... selection and sorting ...
header('Content-Type: application/json');
echo json_encode([
'nearest' => [
'id' => $nearest[0]['ID'],
'title' => $nearest[0]['TITLE'],
'address' => $nearest[0]['ADDRESS'],
'distance' => round($dist, 1),
'schedule' => $nearest[0]['SCHEDULE'],
'phone' => $nearest[0]['PHONE'],
],
]);
Showing Stock at the Nearest Store
After determining the store, product stock at that location can be displayed directly on the product card. Data from b_catalog_store_product:
$stock = \Bitrix\Catalog\StoreProductTable::getList([
'filter' => [
'=PRODUCT_ID' => $productId,
'=STORE_ID' => $nearestStoreId,
],
'select' => ['AMOUNT'],
])->fetch();
$inStock = $stock && $stock['AMOUNT'] > 0;
UI and UX
Typical interface: a popup block in the header "Nearest store: [Name], [distance] km" or a widget on the product page "In-store availability". A "Detect" button triggers the geolocation request. If the user denies access — display a list of stores with address search.
For a full map, connect the Yandex Maps JS API or Google Maps API and place markers for all locations.
| Stage | Time |
|---|---|
| Geocoding store addresses | 2–3 h |
| AJAX endpoint with distance calculation | 3–4 h |
| Widget on the page / in the header | 3–5 h |
| Stock display for the nearest warehouse | 2–3 h |
| Map integration (optional) | 4–6 h |







