Реализация определения ближайших объектов (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 день | Публикация в магазины или развёртывание сервера |
| Поддержка | — | Fix багов по горячим линиям |
Сроки ориентировочно
Базовая интеграция (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 для вашего приложения. Оценим проект за один день, предложим лучшее решение под ключ.







