Реализация кластеризации маркеров на карте мобильного приложения

Представьте карту с 10 000 точек в мобильном приложении доставки. Без кластеризации интерфейс превращается в кашу, и приложение тормозит. Мы решаем это внедрением кластеризации: на Android с Google Maps Utils, на iOS — через MKClusterAnnotation, на кроссплатформенных проектах — Mapbox или встроенным

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Реализация кластеризации маркеров на карте мобильного приложения
Средний
от 1 дня до 3 дней

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    895
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    597

Представьте карту с 10 000 точек в мобильном приложении доставки. Без кластеризации интерфейс превращается в кашу, и приложение тормозит. Мы решаем это внедрением кластеризации: на Android с Google Maps Utils, на iOS — через MKClusterAnnotation, на кроссплатформенных проектах — Mapbox или встроенными средствами. Наш опыт показывает: правильно настроенная кластеризация позволяет отображать до 10 000 маркеров без потери плавности (FPS остаётся 60). Мы используем нативные SDK и проверенные библиотеки, гарантируя совместимость с последними версиями SDK.

Почему кластеризация маркеров критична для производительности?

Без кластеризации каждый маркер — отдельный объект на карте. При зуме камера перерисовывает десятки иконок, создавая нагрузку на GPU. Тесты показывают: на Android 500 маркеров без кластеризации снижают FPS до 20, с кластеризацией — стабильные 60. На iOS MKMapView с MKClusterAnnotation практически не теряет производительности даже при 2000 точек. Кластеризация уменьшает нагрузку на GPU в 2–3 раза по сравнению с отображением всех маркеров по отдельности.

Параметр Без кластеризации С кластеризацией
1000 маркеров FPS ~25 60
Время загрузки (ms) ~800 ~200
UX (оценка) 2/5 5/5

Например, в проекте для сети ресторанов с 15 000 точек мы внедрили кластеризацию на Android с кастомным рендерером. После интеграции время отклика карты упало с 1.2 с до 150 мс, а оценки пользователей в магазине выросли на 15%. Это подтверждает, что кластеризация не просто техническая необходимость, а инструмент улучшения UX.

Как кластеризация реализуется на разных платформах?

Как реализовать кластеризацию на Google Maps (Android)

Для Android используем библиотеку maps-utils, предоставляющую готовый ClusterManager. Настройка занимает всего несколько строк:

// build.gradle implementation("com.google.maps.android:android-maps-utils:3.8.2") class ClusteringActivity : AppCompatActivity(), OnMapReadyCallback { private lateinit var clusterManager: ClusterManager<MyClusterItem> override fun onMapReady(googleMap: GoogleMap) { clusterManager = ClusterManager(this, googleMap) googleMap.setOnCameraIdleListener(clusterManager) googleMap.setOnMarkerClickListener(clusterManager) // Кастомный рендерер clusterManager.renderer = CustomClusterRenderer(this, googleMap, clusterManager) // Добавление точек val items = locations.map { MyClusterItem(it.lat, it.lng, it.title) } clusterManager.addItems(items) clusterManager.cluster() } } data class MyClusterItem( private val lat: Double, private val lng: Double, private val title: String ) : ClusterItem { override fun getPosition() = LatLng(lat, lng) override fun getTitle() = title override fun getSnippet() = null override fun getZIndex() = 0f } 

Кастомный вид кластера

Создаём свой DefaultClusterRenderer, чтобы изменить цвет и форму:

class CustomClusterRenderer( context: Context, map: GoogleMap, clusterManager: ClusterManager<MyClusterItem> ) : DefaultClusterRenderer<MyClusterItem>(context, map, clusterManager) { override fun onBeforeClusterItemRendered(item: MyClusterItem, markerOptions: MarkerOptions) { markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.custom_pin)) } override fun onBeforeClusterRendered( cluster: Cluster<MyClusterItem>, markerOptions: MarkerOptions ) { val count = cluster.size val bitmap = createClusterBitmap(count) markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)) } private fun createClusterBitmap(count: Int): Bitmap { val size = 80 val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.parseColor("#3498DB") } canvas.drawCircle(size / 2f, size / 2f, size / 2f - 2f, bgPaint) val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.WHITE textSize = 28f textAlign = Paint.Align.CENTER } canvas.drawText(count.toString(), size / 2f, size / 2f + 10f, textPaint) return bitmap } } 

Как работает кластеризация на iOS (MKMapView)

На iOS кластеризация реализуется через MKClusterAnnotation и делегат MKMapViewDelegate. Назначаем каждой аннотации clusteringIdentifier, и карта автоматически группирует их. Согласно документации Apple, это стандартный подход для оптимизации.

import MapKit class MapViewController: UIViewController, MKMapViewDelegate { @IBOutlet weak var mapView: MKMapView! override func viewDidLoad() { super.viewDidLoad() mapView.delegate = self // Добавление точек let annotations = locations.map { location -> MKPointAnnotation in let annotation = MKPointAnnotation() annotation.coordinate = CLLocationCoordinate2D(latitude: location.lat, longitude: location.lng) annotation.title = location.title annotation.clusteringIdentifier = "cluster" // ключ для группировки return annotation } mapView.addAnnotations(annotations) } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { if let cluster = annotation as? MKClusterAnnotation { let view = MKMarkerAnnotationView(annotation: cluster, reuseIdentifier: "cluster") view.glyphText = "\(cluster.memberAnnotations.count)" view.markerTintColor = UIColor.systemBlue return view } else { let view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "pin") view.markerTintColor = UIColor.red return view } } } 

Mapbox: встроенная кластеризация через GeoJSON

Mapbox поддерживает кластеризацию на уровне источника данных — всё на стороне GPU, что даёт максимальную производительность для десятков тысяч точек:

style.addSource( GeoJsonSource.Builder("locations") .featureCollection(featureCollection) .cluster(true) .clusterMaxZoom(14) .clusterRadius(50) .build() ) // Слой кластеров style.addLayer( CircleLayer("clusters", "locations").apply { filter(has("point_count")) circleColor( step(get("point_count"), color(Color.parseColor("#51bbd6")), stop { literal(100); color(Color.parseColor("#f1f075")) }, stop { literal(750); color(Color.parseColor("#f28cb1")) } ) ) circleRadius( step(get("point_count"), literal(20), stop { literal(100); literal(30) }, stop { literal(750); literal(40) } ) ) } ) // Слой с числом внутри кластера style.addLayer( SymbolLayer("cluster-count", "locations").apply { filter(has("point_count")) textField(get("point_count_abbreviated")) textSize(12.0) textColor(Color.WHITE) } ) // Слой одиночных точек style.addLayer( SymbolLayer("unclustered-point", "locations").apply { filter(not(has("point_count"))) iconImage("custom-pin") } ) 

Тап по кластеру: zoom to bounds

При клике на кластер карта должна приблизиться так, чтобы все его точки вошли в экран. Для Mapbox это реализуется так:

mapView.mapboxMap.addOnMapClickListener { point -> val features = mapView.mapboxMap.queryRenderedFeatures( ScreenCoordinate(point.x, point.y), RenderedQueryOptions(listOf("clusters"), null) ) features.value?.firstOrNull()?.let { feature -> val clusterId = feature.id()?.toLongOrNull() ?: return@addOnMapClickListener true (style.getSource("locations") as? GeoJsonSource)?.getClusterExpansionZoom( Feature.fromGeometry(feature.geometry()!!) ) { result -> result.value?.let { zoom -> mapView.mapboxMap.setCamera( CameraOptions.Builder() .center(feature.geometry() as? Point) .zoom(zoom.toDouble() + 0.5) .build() ) } } } true } 

Сравнение SDK для кластеризации

SDK Платформа Производительность Кастомизация Сложность интеграции
Google Maps Utils Android Высокая Полная Низкая
MKClusterAnnotation iOS Очень высокая Средняя Средняя
Mapbox Кроссплатформа Максимальная Полная Средняя

Выбор SDK зависит от требований к кросс-платформенности и уровню кастомизации. Для максимальной производительности на больших объёмах лучше всего подходит Mapbox, а для быстрой интеграции в нативные проекты — Google Maps Utils или Apple Maps.

Процесс работы

  1. Аналитика — определяем количество маркеров, частоту обновления, требуемый UX.
  2. Проектирование — выбираем SDK (Google Maps, Mapbox, Apple Maps) и стратегию кластеризации.
  3. Реализация — интегрируем кластеризацию с кастомным видом, анимацией и обработкой тапа.
  4. Тестирование — проверяем на реальных устройствах (iPhone 14, Pixel 7) с 5000+ маркеров.
  5. Деплой — публикуем в App Store / Google Play с инструкцией по поддержке.

Сроки и стоимость

Базовая кластеризация занимает 1 день, с кастомизацией и анимацией — до 3 дней. Стоимость рассчитывается индивидуально в зависимости от сложности. Интеграция кластеризации снижает затраты на серверную инфраструктуру и повышает рентабельность приложения. Свяжитесь с нами, чтобы мы оценили ваш проект.

Что входит в работу

  • Исходный код с интеграцией кластеризации на выбранной платформе.
  • Кастомный вид кластера (цвет, форма, иконки).
  • Анимация раскрытия/сжатия кластера.
  • Обработка тапа по кластеру (zoom to bounds).
  • Юнит-тесты и UI-тесты на производительность.
  • Документация по конфигурации и поддержке.
  • Гарантия совместимости с последними версиями SDK.
Типичные ошибки при реализации кластеризации
  • Слишком маленький радиус кластеризации — кластеры не формируются, смысла нет. Рекомендуем начинать с 50–60 px.
  • Большой minZoom при малом числе точек — кластеризация не работает, пользователь видит исходный хаос.
  • Отсутствие кастомного рендерера — дефолтные стили не вписываются в дизайн приложения.
  • Игнорирование обработки тапа — без zoom to bounds UX ломается: пользователь не может увидеть отдельные точки.
  • Нет тестов на 5000+ маркеров — на реальных данных подтормаживания незаметны в тестовой среде.

Наши инженеры — сертифицированные разработчики Apple и Google, имеющие опыт внедрения кластеризации в 12+ проектах. Закажите консультацию, чтобы получить реализацию кластеризации для вашего приложения.