Development of Store Locator (Store/Office Map) on 1C-Bitrix
A user enters their city or address and wants to see the nearest store with a route. Standard Bitrix tools — neither bitrix:catalog.element nor trading offers — solve this task. You need a map, geolocation, radius search, and filtering by location type. All this is custom development based on an infoblock and mapping API.
Storing Sales Point Data
Each sales point is an infoblock element. Minimum property set:
-
ADDRESS— full address (string) -
LAT/LNG— coordinates (numeric properties with 6-digit precision) -
PHONE— phone (string or multiple property) -
WORKING_HOURS— working hours (string or JSON:{"mon-fri": "9:00-20:00", "sat": "10:00-18:00"}) -
POINT_TYPE— location type (list: "Store", "Pickup Point", "Service Center") -
CITY_ID— binding to city reference (city infoblock element) -
METRO— nearest metro station -
PHOTO— location photo
Coordinates (LAT/LNG) are key. They're obtained when creating a point: either manually via Yandex.Maps, or via geocoding from address through Yandex Geocoder API when saving the element:
AddEventHandler('iblock', 'OnBeforeIBlockElementAdd', 'geocodeStoreAddress');
function geocodeStoreAddress(&$arFields) {
if ($arFields['IBLOCK_ID'] !== IBLOCK_STORES_ID) return;
$address = urlencode($arFields['PROPERTIES']['ADDRESS']['VALUE']);
$http = new \Bitrix\Main\Web\HttpClient();
$response = $http->get("https://geocode-maps.yandex.ru/1.x/?apikey=KEY&format=json&geocode=$address");
$data = json_decode($response, true);
$coords = $data['response']['GeoObjectCollection']['featureMember'][0]['GeoObject']['Point']['pos'];
[$lng, $lat] = explode(' ', $coords);
$arFields['PROPERTIES']['LAT']['VALUE'] = $lat;
$arFields['PROPERTIES']['LNG']['VALUE'] = $lng;
}
Choosing Mapping API
Yandex.Maps API — priority for Russian audience. Detailed RF coverage, traffic, user geolocation, route building. Pricing: free up to 1,000 requests/day, then commercial plan.
2GIS API — good alternative for Russian and CIS cities, detailed office building maps (floor plans).
OpenStreetMap + Leaflet.js — free option without request limits. Geocoding via Nominatim (free but rate-limited) or paid Geocoding API.
Component Architecture
Store locator consists of two parts:
PHP backend (component.php) — gets list of points from infoblock, optionally filters by city or radius:
// Radius search via haversine formula (approximation)
// For catalogs up to 1000 points — client-side JS filtering is sufficient
$res = CIBlockElement::GetList(
[],
['IBLOCK_ID' => IBLOCK_STORES_ID, 'ACTIVE' => 'Y'],
false, false,
['ID', 'NAME', 'PROPERTY_LAT', 'PROPERTY_LNG', 'PROPERTY_ADDRESS',
'PROPERTY_PHONE', 'PROPERTY_WORKING_HOURS', 'PROPERTY_POINT_TYPE']
);
$points = [];
while ($el = $res->GetNext()) {
$points[] = [
'id' => $el['ID'],
'name' => $el['NAME'],
'lat' => (float)$el['PROPERTY_LAT_VALUE'],
'lng' => (float)$el['PROPERTY_LNG_VALUE'],
'address' => $el['PROPERTY_ADDRESS_VALUE'],
'phone' => $el['PROPERTY_PHONE_VALUE'],
'hours' => $el['PROPERTY_WORKING_HOURS_VALUE'],
'type' => $el['PROPERTY_POINT_TYPE_VALUE'],
];
}
Data is passed to JS via <script> with JSON or via AJAX endpoint.
JS frontend — initializes map, adds markers, implements search and filtering:
// Yandex.Maps API 3.0
import { load } from '@2gis/mapgl';
// or ymaps3.ready.then(() => { ... })
const map = new ymaps3.YMap(document.getElementById('map'), {
location: { center: [37.617, 55.755], zoom: 10 }
});
// Clustering for large number of points
const clusterer = new ymaps3.YMapClusterer({ ... });
Marker clustering — mandatory for 50+ points. Without it, the map is overloaded and unusable. Yandex.Maps 3.0 and Leaflet.js (via Leaflet.markercluster plugin) support this out of the box.
Geolocation Search
// Getting browser geolocation
navigator.geolocation.getCurrentPosition((pos) => {
const userLat = pos.coords.latitude;
const userLng = pos.coords.longitude;
// sort points by distance (haversine formula in JS)
stores.sort((a, b) => distance(userLat, userLng, a.lat, a.lng) - distance(userLat, userLng, b.lat, b.lng));
renderStoreList(stores.slice(0, 5)); // show 5 nearest
});
If user denies geolocation — fallback: city detection via IP using Sypex Geo (PHP library, databases stored locally) or ip-api.com API.
Sidebar with Store List
Map without sidebar — poor UX. Typical layout: left side store list with distance from user, right side map. Click on store in list centers map and opens balloon. Click on marker highlights card in list.
Synchronization via shared state (simple JS object or small Vue/Alpine.js store):
let selectedStore = null;
function selectStore(id) {
selectedStore = id;
// update highlighting in list
document.querySelectorAll('.store-card').forEach(el => el.classList.remove('active'));
document.querySelector(`[data-store-id="${id}"]`).classList.add('active');
// move map
map.setCenter([stores[id].lng, stores[id].lat], { duration: 300 });
}
Filtering by Location Type and City
Dropdown with cities — SELECT with onChange filtering the stores array client-side and redrawing markers. For 100+ cities, list is unwieldy; better — autocomplete field with search.
Filter by location type (store / pickup / service) — checkboxes hiding/showing corresponding markers via marker.setVisible(false).
Performance with Large Number of Points
With 500+ points, passing the entire array in JS on page load is undesirable — large JSON slows parsing. Solution:
- Paginated AJAX request: on start, load points within visible map area, on map movement — load new ones
-
Server-side radius search: request PHP with
lat,lng,radiusparams, filter in SQL via haversine formula or PostGIS (if PostgreSQL) -
Caching: store list rarely changes, aggressive cache for 1 hour in
b_cachetable
Timeframes
| Scale | What's Included | Timeframe |
|---|---|---|
| Up to 50 points | Map + list + city filter | 1–2 weeks |
| 50–300 points | + clustering, geolocation, nearby search | 2–4 weeks |
| 300+ points | + server-side radius search, PostGIS | 4–6 weeks |
Store locator — a touchpoint between online and offline. Properly implemented, it reduces call center calls with "where are you located" and boosts footfall conversion.







