Circle layer для кластеров

В Mapbox GL JS кластеризация данных реализуется на уровне источника GeoJSONSource через параметр cluster: true. При этом визуализация кластеров и отдельных точек строится с использованием нескольких слоёв, ключевым из которых является circle layer.

Mapbox GL JS позволяет отображать большие наборы точечных данных за счёт агрегации близких объектов в кластеры. Эти кластеры становятся виртуальными объектами с полем point_count, которое используется для стилизации через circle layer.

Базовая структура источника данных:

map.addSource('earthquakes', {
  type: 'geojson',
  data: '/data/earthquakes.geojson',
  cluster: true,
  clusterRadius: 50,
  clusterMaxZoom: 14
});

Параметры кластеризации:

  • cluster: true — включает группировку точек
  • clusterRadius — радиус в пикселях, определяющий плотность кластеризации
  • clusterMaxZoom — максимальный zoom, на котором выполняется кластеризация

Circle layer как визуальный слой кластеров

Circle layer применяется для отображения кластерных объектов как кругов с изменяемыми параметрами радиуса, цвета и прозрачности.

Ключевой момент: кластерные объекты отличаются от обычных точек наличием свойства:

  • point_count — количество точек внутри кластера

Это позволяет разделять визуализацию на три логических слоя:

  1. Кластеры (circle layer)
  2. Подписи кластеров (symbol layer)
  3. Некластеризованные точки (circle layer)

Базовый circle layer для кластеров

map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'earthquakes',
  filter: ['has', 'point_count'],
  paint: {
    'circle-color': '#51bbd6',
    'circle-radius': 18,
    'circle-stroke-width': 2,
    'circle-stroke-color': '#fff'
  }
});

Фильтрация кластеров

filter: ['has', 'point_count']

Означает, что слой применим только к объектам, содержащим point_count, то есть кластерам.

Для некластеризованных точек используется обратный фильтр:

filter: ['!', ['has', 'point_count']]

Динамическое изменение размера circle layer

Размер круга часто зависит от количества точек внутри кластера. Для этого используется выражение step:

'circle-radius': [
  'step',
  ['get', 'point_count'],
  15,
  10, 20,
  50, 30,
  100, 40
]

Логика:

  • до 10 точек — радиус 15
  • от 10 до 49 — 20
  • от 50 до 99 — 30
  • 100+ — 40

Такой подход позволяет визуально выделять плотные области данных.

Цветовая сегментация кластеров

Circle layer часто дополняется цветовой шкалой:

'circle-color': [
  'step',
  ['get', 'point_count'],
  '#a6cee3',
  20,
  '#1f78b4',
  50,
  '#b2df8a',
  100,
  '#33a02c'
]

Цвет становится индикатором плотности данных, что уменьшает необходимость чтения подписей.

Добавление слоя подписей кластеров

Circle layer сам по себе не отображает числовое значение, поэтому поверх него используется symbol layer:

map.addLayer({
  id: 'cluster-count',
  type: 'symbol',
  source: 'earthquakes',
  filter: ['has', 'point_count'],
  layout: {
    'text-field': '{point_count_abbreviated}',
    'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
    'text-size': 12
  }
});

Ключевые элементы:

  • {point_count_abbreviated} — сокращённое отображение числа (например, 1.2k)
  • text-size — масштаб текста независимо от circle radius

Отображение некластеризованных точек

Отдельный circle layer используется для одиночных объектов:

map.addLayer({
  id: 'unclustered-point',
  type: 'circle',
  source: 'earthquakes',
  filter: ['!', ['has', 'point_count']],
  paint: {
    'circle-color': '#ff5722',
    'circle-radius': 6,
    'circle-stroke-width': 1,
    'circle-stroke-color': '#fff'
  }
});

Такая схема обеспечивает визуальное разделение между агрегированными и исходными данными.

Слои и порядок рендеринга

В Mapbox GL JS порядок добавления слоёв критичен:

  1. circle layer кластеров
  2. symbol layer с числами
  3. circle layer одиночных точек

Это гарантирует корректное перекрытие элементов.

Взаимодействие с кластерами

Circle layer часто используется как интерактивная зона для раскрытия кластеров.

Определение клика по кластеру

map.on('click', 'clusters', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['clusters']
  });

  const clusterId = features[0].properties.cluster_id;
  const source = map.getSource('earthquakes');

  source.getClusterExpansionZoom(clusterId, (err, zoom) => {
    if (err) return;

    map.easeTo({
      center: features[0].geometry.coordinates,
      zoom: zoom
    });
  });
});

Логика раскрытия

  • cluster_id идентифицирует конкретный кластер
  • getClusterExpansionZoom вычисляет zoom, на котором кластер распадается
  • easeTo выполняет плавное приближение

Hover-эффекты для circle layer

Состояния hover реализуются через изменение paint свойств:

map.on('mouseenter', 'clusters', () => {
  map.setPaintProperty('clusters', 'circle-stroke-width', 3);
  map.getCanvas().style.cursor = 'pointer';
});

map.on('mouseleave', 'clusters', () => {
  map.setPaintProperty('clusters', 'circle-stroke-width', 2);
  map.getCanvas().style.cursor = '';
});

Использование выражений для сложной стилизации

Circle layer поддерживает выражения Mapbox Style Specification.

Пример комбинированного управления прозрачностью:

'circle-opacity': [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 0.6,
  10, 0.9
]

Или зависимость от количества точек:

'circle-opacity': [
  'interpolate',
  ['linear'],
  ['get', 'point_count'],
  0, 0.4,
  100, 1
]

Производительность circle layer при кластеризации

Circle layer сам по себе лёгкий, но основная нагрузка возникает на этапе генерации кластеров в источнике.

Ключевые оптимизации:

  • ограничение clusterRadius для уменьшения пересчёта кластеров
  • использование clusterMaxZoom для отключения кластеризации на высоких zoom
  • минимизация числа слоёв (не дублировать circle layer без необходимости)

Комбинированная архитектура отображения

Типичная архитектура слоёв:

  • clusters — circle layer для кластеров
  • cluster-count — symbol layer с числом
  • unclustered-point — circle layer для одиночных точек

Такая структура позволяет отделить:

  • геометрию (circle layer)
  • текстовую информацию (symbol layer)
  • логику фильтрации (cluster/unclustered)

Стилизация через data-driven properties

Circle layer поддерживает полную data-driven стилизацию:

'circle-color': [
  'case',
  ['>', ['get', 'point_count'], 50],
  '#e31a1c',
  '#1f78b4'
]

Такой подход позволяет формировать сложные визуальные кодировки без дополнительных слоёв.

Поведение на разных zoom уровнях

Circle layer изменяет визуальное восприятие в зависимости от масштаба:

  • низкий zoom — крупные кластеры, высокая агрегация
  • средний zoom — постепенное расщепление кластеров
  • высокий zoom — отображение отдельных точек

Эта динамика напрямую связана с параметром clusterMaxZoom.

Взаимодействие с фильтрами и источниками

Circle layer может комбинироваться с фильтрами по атрибутам GeoJSON:

filter: [
  'all',
  ['has', 'point_count'],
  ['>', ['get', 'point_count'], 20]
]

Это позволяет отображать только “значимые” кластеры, игнорируя мелкие группы.

Расширенные сценарии применения

Circle layer для кластеров используется в задачах:

  • визуализация событий (землетрясения, инциденты)
  • отображение пользовательских точек (геосервисы)
  • аналитика плотности данных
  • тепловые прокси-отображения через кластеры

В каждом случае circle layer остаётся базовым примитивом визуализации кластеров благодаря простоте и высокой производительности.