Реалізація пошуку найближчих об'єктів (POI) у мобільному додатку
Уявіть: додаток відображає сотню маркерів на карті, а прокрутка перетворюється на слайд-шоу. Або користувач натискає «Знайти аптеку», а список завантажується півхвилини. Саме це відбувається, коли пошук найближчих об'єктів (POI) реалізований без урахування масштабу даних та без кластеризації. Ми, команда мобільних розробників з п’ятирічним досвідом, вирішили цю задачу для більш ніж 50 проєктів (iOS, Android, Flutter). Гарантуємо: POI завантажуватимуться за частки секунди навіть при датасеті у 100 000 точок.
Два підходи: клієнтський та серверний пошук
Клієнтський пошук — завантажуємо всі точки (або їх підмножину) у додаток, шукаємо найближчі на пристрої. Працює для невеликих наборів даних — до 5000 точок. Фільтрація за відстанню через формулу Гаверсина або через CLLocation.distance(from:) / Location.distanceTo(). Плюс: працює офлайн. Мінус: не можна зберігати мільйон точок у пам'яті.
Серверний пошук — PostGIS ST_DWithin, MongoDB $near, Elasticsearch geo_distance query. Для великих датасетів (від 10 000 точок) тільки цей варіант. Додаток надсилає координати та радіус, сервер повертає відсортований список. Використовуємо індексацію на стороні БД — це в десять разів швидше, ніж повний перебір.
Порівняння підходів
| Параметр | Клієнтський | Серверний |
|---|---|---|
| Макс. точок | ~5 000 | не обмежено |
| Офлайн-режим | Так | Ні (тільки кеш) |
| Швидкість | Миттєво | 50-200 мс |
| Складність | Низька | Середня |
| Трафік | Високий (всі дані) | Низький (тільки результат) |
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. Додатково — кешуємо результати на годину, щоб знизити навантаження.
Чому варто обрати наш підхід?
Досвідчені розробники (iOS, Android, Flutter) використовують лише перевірені стеки: Swift 5.9, Kotlin, Flutter 3.x. Ми інтегрували Google Places API у 30+ проєктах і знаємо всі тонкощі pagetoken. Гарантуємо продуктивність: так, кластеризація рахується асинхронно, а запити ходять з debounce. Результат — карта не гальмує навіть на старих пристроях.
Що входить у роботу
- Дослідження датасету та вибір стратегії (клієнт/сервер/гібрид)
- Проєктування архітектури модуля POI
- Інтеграція Google Places API або налаштування власного бекенду з PostGIS
- Реалізація кластеризації з асинхронним перерахунком
- Логіка оновлення при переміщенні з debounce та кешуванням
- Unit-тести та UI-тести
- Документація для розробників та навчання вашої команди
- Підтримка після деплою: 1 місяць безкоштовно
Процес роботи
| Етап | Тривалість | Результат |
|---|---|---|
| Аналітика | 1 день | Вивчення даних, навантаження, сценаріїв використання |
| Проєктування | 1 день | Вибір підходу, схема запитів |
| Реалізація | 2-4 дні | Код, налаштування індексів |
| Тестування | 1 день | Перевірка на реальних пристроях з різними радіусами |
| Деплой | 1 день | Публікація в магазини або розгортання сервера |
| Підтримка | — | Виправлення багів по гарячих лініях |
Орієнтовні строки
Базова інтеграція (Google Places + карта + кластеризація) — від 3 до 5 днів. Повне рішення з серверним пошуком та кастомними точками — до 7 днів. Оцінимо ваш проєкт за один день — просто напишіть нам.
Як оптимізувати запити до сервера?
- Використовуйте просторові індекси (GiST у PostGIS, 2dsphere у MongoDB)
- Обмежте радіус: не більше 5 км для пішохідного пошуку
- Додайте ліміт на кількість результатів (50-100)
- Кешуйте відповідь на 5-10 хвилин для статичних POI
- При частих запитах від одного користувача — агрегуйте їх у batch
Типові помилки
- Відсутність debounce при зміні зуму — інтерфейс гальмує
- Запит нової сторінки Places API без затримки — отримуємо порожню відповідь
- Зберігання всіх координат у пам'яті — перевитрата пам'яті
- Ігнорування pagetoken — бачимо лише 20 результатів
- Кластеризація на main thread — лаги при скролі
Приклад конфігурації кластеризації на Swift
let clusterManager = GMUClusterManager(mapView: mapView, algorithm: GMUNonHierarchicalDistanceBasedAlgorithm(), renderer: renderer) clusterManager.setDelegate(self, mapDelegate: self) Зв'яжіться з нами — отримайте консультацію щодо архітектури POI для вашого додатка. Оцінимо проєкт за один день, запропонуємо найкраще рішення під ключ.







