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

Отображение количества точек в Mapbox GL JS опирается на механизм кластеризации источников данных, при котором множество близко расположенных объектов объединяется в один кластерный объект с агрегированными свойствами. Основной целью такого подхода является визуальное упрощение карты при больших объёмах данных и предоставление числовой информации о плотности распределения объектов.

Кластеризация включается на уровне источника данных GeoJSON. При этом библиотека автоматически группирует точки в кластеры в зависимости от масштаба карты и заданных параметров.

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

Ключевые параметры:

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

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

  • point_count — количество точек внутри кластера
  • point_count_abbreviated — сокращённое отображение количества (например, 1.2k)

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

Для визуализации числа точек внутри кластера используется слой символов (symbol layer) с текстовым полем, привязанным к свойствам кластера.

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

Здесь используется выражение ['get', 'point_count_abbreviated'], которое извлекает агрегированное значение из кластера. Это позволяет автоматически отображать сокращённые значения без дополнительной логики на стороне приложения.

Использование полного количества точек

В случаях, когда требуется точное число объектов без сокращений, применяется свойство point_count:

'text-field': ['get', 'point_count']

Разница между форматами заключается исключительно в визуальном представлении:

  • point_count — полное число
  • point_count_abbreviated — формат с суффиксами (k, m)

Визуализация кластеров через окружности

Количество точек часто дополняется визуальной дифференциацией кластеров по размеру или цвету. Это реализуется через circle layer, где стили зависят от значения point_count.

map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'points',
  filter: ['has', 'point_count'],
  paint: {
    'circle-color': [
      'step',
      ['get', 'point_count'],
      '#51bbd6',
      100,
      '#f1f075',
      750,
      '#f28cb1'
    ],
    'circle-radius': [
      'step',
      ['get', 'point_count'],
      20,
      100,
      30,
      750,
      40
    ]
  }
});

Такая конструкция позволяет формировать многоуровневую визуализацию плотности данных:

  • малые кластеры выделяются одним цветом и размером
  • средние — другим
  • крупные — третьим

Настройка кастомной агрегации кластеров

Mapbox GL JS поддерживает создание пользовательских агрегированных свойств через clusterProperties. Это позволяет вычислять дополнительные метрики внутри кластера, например количество объектов по категориям.

map.addSource('points', {
  type: 'geojson',
  data: 'points.geojson',
  cluster: true,
  clusterProperties: {
    redCount: ['+', ['case', ['==', ['get', 'color'], 'red'], 1, 0]],
    blueCount: ['+', ['case', ['==', ['get', 'color'], 'blue'], 1, 0]]
  }
});

После этого новые свойства доступны аналогично point_count:

'text-field': [
  'concat',
  'R: ', ['get', 'redCount'],
  ' B: ', ['get', 'blueCount']
]

Такой подход позволяет отображать не только общее количество точек, но и их внутреннюю структуру.

Формирование адаптивного отображения количества

При работе с различными уровнями масштаба важно учитывать изменение плотности данных. Для этого используются выражения интерполяции и ступенчатые функции.

'text-size': [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 10,
  10, 14,
  15, 18
]

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

Разделение кластеров и отдельных точек

Для корректного отображения количества точек необходимо разделять слои кластеров и одиночных объектов с помощью фильтров:

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

Одиночные точки могут отображаться отдельно:

map.addLayer({
  id: 'unclustered-point',
  type: 'circle',
  source: 'points',
  filter: ['!', ['has', 'point_count']],
  paint: {
    'circle-color': '#11b4da',
    'circle-radius': 6
  }
});

Это разделение обеспечивает чистую визуальную структуру: кластеры показывают количество, а отдельные точки — конкретные объекты.

Поведение при изменении масштаба

Кластеры динамически пересчитываются при изменении масштаба карты. Это означает, что значение point_count не является статичным и зависит от текущего уровня детализации.

При увеличении масштаба:

  • крупные кластеры распадаются на более мелкие
  • количество точек в кластере уменьшается
  • одиночные точки становятся видимыми

При уменьшении масштаба:

  • количество кластеров уменьшается
  • их размер и значение point_count увеличиваются

Оптимизация отображения больших наборов данных

При работе с тысячами или миллионами точек кластеризация становится ключевым механизмом оптимизации. Основные аспекты:

  • уменьшение количества DOM-элементов рендера
  • снижение нагрузки на WebGL
  • ускорение отрисовки при панорамировании и зуме

Дополнительно используется:

map.addSource('points', {
  type: 'geojson',
  data: 'points.geojson',
  cluster: true,
  clusterRadius: 40,
  clusterMaxZoom: 12
});

Меньший clusterRadius повышает детализацию, но увеличивает количество кластеров, тогда как больший — наоборот.

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

Для улучшения читаемости чисел часто применяются форматированные выражения:

'text-field': [
  'format',
  ['get', 'point_count'],
  { 'font-scale': 1.2 }
]

Это позволяет комбинировать стили текста и улучшать визуальную иерархию числовых значений внутри кластеров.

Интерактивное поведение кластеров

Кластеры могут быть использованы как интерактивные элементы. При клике возможно получение информации о количестве объектов:

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

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

  source.getClusterExpansionZoom(clusterId, (err, zoom) => {
    map.easeTo({
      center: features[0].geometry.coordinates,
      zoom: zoom
    });
  });
});

Хотя этот механизм не изменяет отображение количества точек напрямую, он усиливает его функциональную роль в навигации по данным.