Реалізація визначення ближайших об'єктів (POI) у мобільному додатку
Знайти найближчий банкомат, аптеку або точку самовивозу — базовий сценарій для сотень додатків. Різниця між «працює добре» і «гальмує і показує застарілі» — в архітектурі запитів і правильному використанні API.
Два підходи: клієнтський і серверний пошук
Клієнтський пошук — завантажуємо всі точки (або їхнє підмножество) в додаток, шукаємо найближчі на пристрої. Працює для невеликих наборів даних — до кількох тисяч точок. Фільтрація за відстанню через формулу Хаверсина або через CLLocation.distance(from:) / Location.distanceTo(). Плюс: працює офлайн. Мінус: неможливо зберігати мільйон точок в пам'яті.
Серверний пошук — PostGIS ST_DWithin, MongoDB $near, Elasticsearch geo_distance query. Для великих датасетів тільки цей варіант. Додаток відправляє координати і радіус, сервер повертає відсортований список.
Google Places Nearby Search
Для POI з відкритих даних (кафе, банки, аптеки) — Google Places API:
GET https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=55.75,37.62&radius=1000&type=pharmacy&key=...
На iOS через GMSPlacesClient.findPlaceLikelihoodList або прямий HTTP. На Android через Retrofit. Повертає до 20 результатів за запит, наступна сторінка — через pagetoken. Важливо: pagetoken активується не миттєво, потрібна затримка 2 секунди перед запитом наступної сторінки.
Для кастомних точок (власні магазини, пункти видачі) — власний бекенд. PostGIS запит:
SELECT id, name, lat, lon,
ST_Distance(geom, ST_MakePoint(:lon, :lat)::geography) AS distance_m
FROM locations
WHERE ST_DWithin(geom, ST_MakePoint(:lon, :lat)::geography, :radius)
ORDER BY distance_m
LIMIT 50;
Відображення на карті і кластеризація
Якщо точок більше 50 на екрані — потрібна кластеризація. На iOS: GMSMarkerClusterer з google-maps-ios-utils. На Android: ClusterManager з android-maps-utils. У Flutter: flutter_map + flutter_map_marker_cluster.
Кластери пересчитуються при кожній зміні зуму. Без debounce на подію onCameraMove це вызивает лаг — розрахунок кластерів повинен відбуватися асинхронно, не на main thread.
При тапі на кластер — плавне масштабування через CameraUpdate.newLatLngBounds() до меж кластера, не просто зум до центру.
Обновлення при перемішенні
Не перезапитувати POI на кожне обновлення геолокації. Логіка: запитуємо при першій загрузці і при перемішенні користувача більше ніж на N метрів від центру останнього запиту (для більшості випадків — 300-500 м). CLLocation.distance(from: lastQueryCenter) > threshold.
Строк: два-чотири дні — вибір провайдера, API-інтеграція, відображення з кластеризацією, логіка обновлення.







