Разработка системы выбора пункта выдачи на карте для интернет-магазина
Покупатель открывает шаг доставки, видит список из 47 адресов пунктов выдачи, пытается найти нужный район в выпадающем списке и закрывает вкладку. Наш инженерный опыт показывает: без интерактивной карты до 40% пользователей бросают заказ на этом этапе. Мы — команда с 7-летним опытом в разработке e-commerce решений. С нашим решением человек сразу видит точки на карте рядом с домом, работой, по дороге — и выбирает за 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 недели бесплатной поддержки после запуска
Процесс работы
- Аналитика — изучаем вашу форму заказа, список служб доставки, типичные сценарии покупателей.
- Проектирование — согласовываем структуру данных, API-методы, логику фильтров.
- Реализация — пишем код, настраиваем cron-задачи, подключаем карту.
- Тестирование — проверяем на реальных данных, имитируем 1000+ точек.
- Деплой и обучение — выгружаем на боевой сервер, передаём доступы и документацию.
Сроки разработки
| Этап | Срок |
|---|---|
| Одна служба доставки, карта с кластеризацией | 4–6 дней |
| Агрегатор 3–5 служб, фильтры, геолокация | 2 недели |
| Добавление карты в существующую форму заказа | 3–5 дней |
Окончательная стоимость рассчитывается индивидуально — зависит от числа служб, сложности фильтров и необходимости переработки формы заказа. Грамотная карта ПВЗ окупается за счёт роста конверсии — вложения возвращаются в течение 3–6 месяцев. Средний срок окупаемости карты ПВЗ — 4 месяца за счёт роста конверсии.
Свяжитесь с нами, чтобы обсудить ваш проект и получить демо работающей карты. Закажите карту ПВЗ для вашего интернет-магазина — мы улучшим конверсию доставки.
Подробнее о PostGIS и Leaflet.markercluster.







