Розробка карти вибору пункту видачі для інтернет-магазину
Покупець відкриває крок доставки, бачить список з 47 адрес пунктів видачі, намагається знайти потрібний район у випадаючому списку та закриває вкладку. Наш інженерний досвід показує: без інтерактивної карти до 40% користувачів кидають замовлення на цьому етапі. Ми – команда з 7-річним досвідом у розробці e-commerce рішень, реалізували понад 30 подібних проектів. З нашим рішенням людина одразу бачить точки на карті біля дому, роботи, по дорозі – і обирає за 5 секунд. Ми впровадили такі карти для 30+ інтернет-магазинів, середнє зростання конверсії на етапі вибору доставки – 18%.
Проблеми вибору ПВЗ та як карта їх вирішує
Google Analytics показує, що сторінка вибору доставки – одна з найбільш «просадних»: до 40% користувачів йдуть, не вибравши спосіб. Без карти покупець змушений прокручувати довгий список адрес, зіставляти з уявною картою міста і часто помиляється або кидає оформлення. Типові складності:
- Довгі списки без візуальної прив'язки – назви вулиць ні про що не говорять, якщо людина погано знає район.
- Відсутність інформації про завантаженість – ПВЗ може бути переповнений, але у списку цього не видно.
- Неможливість порівняти кілька точок одночасно – доводиться запам'ятовувати адреси та перемикатися між вкладками.
Карта повертає наочність: маркери показують розташування, спливаючі підказки – деталі (режим роботи, терміни, вартість), групування в кластери – масштабованість. В результаті менше покинутих кошиків та економія бюджету на ретаргетингу. Економія на логістиці досягає 20-30% за рахунок зниження повернень.
Джерела даних про ПВЗ
Пункти видачі надходять з API служб доставки. У кожної своя структура, але суть одна – список об'єктів з координатами, адресою, годинами роботи, обмеженнями за вагою та габаритами.
СДЕК:
GET https://api.cdek.ru/v2/deliverypoints?city_code=44&weight_max=30&type=PVZ
Authorization: Bearer {token}
Boxberry:
GET https://api.boxberry.ru/json.php?token={token}&method=ListPoints&CityCode=77&prepaid=1
Як ми кешуємо довідник ПВЗ?
Список ПВЗ змінюється рідко – раз на добу. Запитувати його при кожному відкритті сторінки – марнотратство. Ми синхронізуємо дані за розкладом: щоночі artisan-команда завантажує всі точки від кожного перевізника, очищає таблицю і вставляє актуальний набір. Користувач отримує дані з локальної БД за 10–20 мс замість 500–2000 мс від API.
Приклад artisan-команди для синхронізації
class SyncPickupPoints extends Command
{
public function handle(CdekService $cdek, BoxberryService $boxberry): void
{
$carriers = [
'cdek' => fn() => $cdek->getAllPickupPoints(),
'boxberry' => fn() => $boxberry->getAllPickupPoints(),
];
foreach ($carriers as $carrier => $fetcher) {
$points = $fetcher();
$this->info("$carrier: {$points->count()} points");
PickupPoint::where('carrier', $carrier)->delete();
PickupPoint::insert(
$points->map(fn($p) => [
'carrier' => $carrier,
'external_id' => $p['code'],
'name' => $p['name'],
'address' => $p['address'],
'city' => $p['city'],
'lat' => $p['lat'],
'lng' => $p['lng'],
'work_time' => $p['work_time'],
'max_weight' => $p['max_weight_kg'],
'cash_allowed' => $p['cash_allowed'],
'updated_at' => now(),
])->toArray()
);
}
$this->info('Done');
}
}
Як ми оптимізуємо геопошук?
Для пошуку найближчих ПВЗ використовуємо PostGIS. Порівняйте два підходи: PostGIS з просторовим індексом GIST проти формули гаверсинуса на стороні SQL. Виграш у швидкості – в 10-30 разів на наборах понад 10 000 точок.
| Критерій | PostGIS (GIST) | Формула гаверсинуса в SQL |
|---|---|---|
| Час виконання запиту (10 км, 20 точок) | 5–15 мс | 50–200 мс |
| Точність | Відстань по сфероїду | Приблизна |
| Підтримка радіуса | Вбудована (ST_DWithin) | Розрахунок в WHERE |
| Індексування | Просторовий індекс GIST | Немає, повне сканування |
Ми обираємо PostGIS: він дає виграш у швидкості в 10–30 разів на великих наборах даних. Документація PostGIS стверджує, що просторові індекси роблять запити до геоданих на порядок швидше.
CREATE EXTENSION IF NOT EXISTS postgis;
ALTER TABLE pickup_points ADD COLUMN location geography(POINT, 4326);
UPDATE pickup_points SET location = ST_SetSRID(ST_MakePoint(lng, lat), 4326);
CREATE INDEX idx_pickup_points_location ON pickup_points USING GIST(location);
SELECT
id, carrier, name, address, work_time, cash_allowed,
ST_Distance(location, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)) AS distance_m
FROM pickup_points
WHERE
max_weight >= :weight
AND ST_DWithin(location, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography, 10000)
ORDER BY distance_m
LIMIT 20;
Рендеринг карти з кластеризацією
Зазначимо: коли точок багато – кілька тисяч – рендерити кожну як окремий DOM-елемент не можна, браузер зависне. Використовуємо Leaflet + MarkerClusterGroup. Завантаження даних відбувається тільки для видимої області карти (подія moveend).
import L from 'leaflet';
import 'leaflet.markercluster';
const map = L.map('pickup-map').setView([55.7558, 37.6173], 11);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
const markers = L.markerClusterGroup({
maxClusterRadius: 50,
iconCreateFunction: (cluster) => {
const count = cluster.getChildCount();
return L.divIcon({
html: `<div class="cluster-icon">${count}</div>`,
className: '',
iconSize: [40, 40],
});
},
});
map.on('moveend', async () => {
const bounds = map.getBounds();
const response = await fetch('/api/pickup-points?' + new URLSearchParams({
north: bounds.getNorth(),
south: bounds.getSouth(),
east: bounds.getEast(),
west: bounds.getWest(),
weight: cartWeight,
}));
const points = await response.json();
markers.clearLayers();
points.forEach((point) => {
const marker = L.marker([point.lat, point.lng], {
icon: carrierIcon(point.carrier),
});
marker.bindPopup(buildPopup(point));
marker.on('click', () => selectPickupPoint(point));
markers.addLayer(marker);
});
});
map.addLayer(markers);
Що входить у роботу
При замовленні розробки ви отримуєте:
- Інтеграцію з 1–5 службами доставки (API, синхронізація, кешування)
- Геопошук через PostGIS або альтернативний алгоритм
- Карту з кластеризацією, фільтрами та попапами
- Адаптивну верстку під мобільні пристрої
- Визначення міста покупця (геолокація + IP)
- Документацію по API та інструкцію з оновлення довідника
- 2 тижні безкоштовної підтримки після запуску
- Гарантію на розроблене рішення – 6 місяців
Процес роботи
- Аналітика – вивчаємо вашу форму замовлення, список служб доставки, типові сценарії покупців.
- Проєктування – погоджуємо структуру даних, API-методи, логіку фільтрів.
- Реалізація – пишемо код, налаштовуємо cron-завдання, підключаємо карту.
- Тестування – перевіряємо на реальних даних, імітуємо 1000+ точок.
- Деплой та навчання – вивантажуємо на бойовий сервер, передаємо доступи та документацію.
Терміни розробки
| Етап | Термін |
|---|---|
| Одна служба доставки, карта з кластеризацією | 4–6 днів |
| Агрегатор 3–5 служб, фільтри, геолокація | 2 тижні |
| Додавання карти в існуючу форму замовлення | 3–5 днів |
Мінімальна вартість робіт – від 15 000 грн за інтеграцію однієї служби доставки. Остаточна вартість розраховується індивідуально – залежить від кількості служб, складності фільтрів та необхідності переробки форми замовлення. Грамотна карта ПВЗ окупається за рахунок зростання конверсії – вкладення повертаються протягом 3–6 місяців.
Зв'яжіться з нами, щоб обговорити ваш проєкт та отримати демо працюючої карти. Замовте карту ПВЗ під ключ – оцінимо ваш проект безкоштовно.
Детальніше про PostGIS та Leaflet.markercluster.







