Стилизация кластеров

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

В MapLibre GL JS кластеры представляют собой специальные объекты, создаваемые движком карты автоматически при включении кластеризации источника данных. Каждый кластер содержит набор служебных свойств:

  • cluster — признак того, что объект является кластером;
  • cluster_id — уникальный идентификатор кластера;
  • point_count — количество точек внутри кластера;
  • point_count_abbreviated — сокращённое представление количества точек.

Именно эти свойства используются для настройки внешнего вида кластеров.


Базовая настройка кластеров

Источник GeoJSON с включённой кластеризацией:

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

После создания источника обычно добавляются два слоя:

  1. Круг для отображения самого кластера.
  2. Подпись с количеством объектов.
map.addLayer({
    id: 'clusters',
    type: 'circle',
    source: 'earthquakes',
    filter: ['has', 'point_count'],
    paint: {
        'circle-color': '#4A90E2',
        'circle-radius': 20
    }
});

map.addLayer({
    id: 'cluster-count',
    type: 'symbol',
    source: 'earthquakes',
    filter: ['has', 'point_count'],
    layout: {
        'text-field': '{point_count_abbreviated}',
        'text-size': 12
    }
});

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


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

Одно из главных преимуществ MapLibre GL JS — поддержка выражений (Expressions), позволяющих динамически изменять внешний вид объектов на основе их свойств.

Количество объектов внутри кластера доступно через поле point_count.

Пример изменения цвета в зависимости от размера кластера:

paint: {
    'circle-color': [
        'step',
        ['get', 'point_count'],
        '#51bbd6',
        100,
        '#f1f075',
        750,
        '#f28cb1'
    ]
}

Логика работы:

Количество точек Цвет
до 100 голубой
от 100 до 749 жёлтый
от 750 розовый

Чем больше объектов внутри кластера, тем сильнее визуальное выделение.


Изменение размера кластеров

Размер также можно связывать с количеством объединённых объектов.

paint: {
    'circle-radius': [
        'step',
        ['get', 'point_count'],
        15,
        100,
        25,
        750,
        35
    ]
}

Теперь крупные кластеры будут не только менять цвет, но и становиться больше.

Подобный подход улучшает визуальное восприятие плотности данных.


Одновременное изменение размера и цвета

На практике цвет и размер обычно изменяются одновременно.

map.addLayer({
    id: 'clusters',
    type: 'circle',
    source: 'earthquakes',
    filter: ['has', 'point_count'],
    paint: {
        'circle-color': [
            'step',
            ['get', 'point_count'],
            '#4FC3F7',
            50,
            '#29B6F6',
            200,
            '#039BE5',
            1000,
            '#01579B'
        ],
        'circle-radius': [
            'step',
            ['get', 'point_count'],
            15,
            50,
            20,
            200,
            30,
            1000,
            45
        ]
    }
});

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

  • пользователей;
  • заказов;
  • транспортных средств;
  • событий;
  • датчиков IoT;
  • объектов недвижимости.

Интерполяция вместо ступенчатого изменения

Функция step создаёт резкие переходы между значениями. Для плавного изменения применяется interpolate.

paint: {
    'circle-radius': [
        'interpolate',
        ['linear'],
        ['get', 'point_count'],
        0, 10,
        100, 20,
        500, 35,
        1000, 50
    ]
}

Теперь радиус будет увеличиваться постепенно.

То же самое можно использовать для цвета.

paint: {
    'circle-color': [
        'interpolate',
        ['linear'],
        ['get', 'point_count'],
        0, '#81D4FA',
        500, '#29B6F6',
        1000, '#01579B'
    ]
}

Результат выглядит значительно современнее по сравнению со ступенчатой схемой.


Настройка прозрачности

Для уменьшения визуальной перегрузки можно использовать прозрачность.

paint: {
    'circle-opacity': 0.7
}

Или изменять прозрачность динамически:

paint: {
    'circle-opacity': [
        'interpolate',
        ['linear'],
        ['get', 'point_count'],
        0, 0.5,
        1000, 1
    ]
}

Крупные кластеры будут более заметными.


Создание эффекта выделения с обводкой

Контур помогает отделить кластер от фоновой карты.

paint: {
    'circle-color': '#2196F3',
    'circle-radius': 25,
    'circle-stroke-width': 3,
    'circle-stroke-color': '#FFFFFF'
}

Белая граница особенно полезна на спутниковых картах и насыщенных стилях.


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

Иногда требуется более выразительный дизайн.

Для этого создаются несколько слоёв кластеров.

Внешний слой:

map.addLayer({
    id: 'cluster-shadow',
    type: 'circle',
    source: 'earthquakes',
    filter: ['has', 'point_count'],
    paint: {
        'circle-color': '#90CAF9',
        'circle-radius': 30,
        'circle-opacity': 0.4
    }
});

Внутренний слой:

map.addLayer({
    id: 'clusters',
    type: 'circle',
    source: 'earthquakes',
    filter: ['has', 'point_count'],
    paint: {
        'circle-color': '#1976D2',
        'circle-radius': 20
    }
});

В результате появляется эффект свечения или ореола.


Стилизация текста внутри кластеров

Количество объектов обычно отображается через слой типа symbol.

map.addLayer({
    id: 'cluster-count',
    type: 'symbol',
    source: 'earthquakes',
    filter: ['has', 'point_count'],
    layout: {
        'text-field': '{point_count_abbreviated}',
        'text-size': 14,
        'text-font': ['Open Sans Bold']
    },
    paint: {
        'text-color': '#FFFFFF'
    }
});

Настраиваемые параметры:

  • размер текста;
  • цвет;
  • шрифт;
  • прозрачность;
  • положение.

Динамический размер текста

Размер подписи также может зависеть от количества объектов.

layout: {
    'text-field': '{point_count_abbreviated}',
    'text-size': [
        'interpolate',
        ['linear'],
        ['get', 'point_count'],
        0, 10,
        100, 14,
        1000, 18
    ]
}

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


Отображение различных подписей

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

layout: {
    'text-field': [
        'case',
        ['<', ['get', 'point_count'], 100],
        'Мало',
        ['<', ['get', 'point_count'], 500],
        'Средне',
        'Много'
    ]
}

Такой подход применяется в аналитических системах и информационных панелях.


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

Важно визуально различать кластеры и одиночные объекты.

Для этого используется фильтр:

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

Пример слоя отдельных объектов:

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

Чёткое разделение помогает пользователю понимать структуру данных на карте.


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

MapLibre GL JS поддерживает накопление пользовательских значений внутри кластеров через параметр clusterProperties.

Например, можно суммировать количество продаж.

map.addSource('stores', {
    type: 'geojson',
    data: storesData,
    cluster: true,

    clusterProperties: {
        totalSales: [
            '+',
            ['get', 'sales']
        ]
    }
});

После этого свойство totalSales становится доступным в кластере.

Изменение цвета:

paint: {
    'circle-color': [
        'interpolate',
        ['linear'],
        ['get', 'totalSales'],
        0, '#C8E6C9',
        10000, '#4CAF50',
        50000, '#1B5E20'
    ]
}

Теперь оформление зависит не от числа точек, а от суммарного объёма продаж.


Визуализация плотности данных

Кластеры могут служить индикатором плотности.

Популярная схема:

paint: {
    'circle-color': [
        'step',
        ['get', 'point_count'],
        '#00E676',
        50,
        '#FFEB3B',
        200,
        '#FF9800',
        1000,
        '#F44336'
    ]
}

Интерпретация:

  • зелёный — низкая плотность;
  • жёлтый — средняя;
  • оранжевый — высокая;
  • красный — очень высокая.

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


Использование масштабирования карты

Стили могут зависеть не только от данных, но и от текущего масштаба.

paint: {
    'circle-radius': [
        'interpolate',
        ['linear'],
        ['zoom'],
        0, 10,
        10, 20,
        15, 35
    ]
}

При приближении карты кластеры становятся крупнее.

Комбинация масштаба и количества объектов:

paint: {
    'circle-radius': [
        'interpolate',
        ['linear'],
        ['zoom'],
        0, 10,
        10, [
            'interpolate',
            ['linear'],
            ['get', 'point_count'],
            0, 15,
            1000, 40
        ]
    ]
}

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


Создание анимированного эффекта через изменение состояния

MapLibre GL JS позволяет изменять свойства слоя динамически.

Например:

map.setPaintProperty(
    'clusters',
    'circle-color',
    '#FF5722'
);

Или:

map.setPaintProperty(
    'clusters',
    'circle-radius',
    40
);

Это может использоваться для:

  • выделения найденных объектов;
  • отображения активного фильтра;
  • визуального отклика на действия пользователя;
  • сигнализации о событиях в реальном времени.

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

Для небольших наборов данных:

  • минималистичные цвета;
  • радиус 10–20 пикселей;
  • тонкая обводка.

Для городских сервисов:

  • изменение цвета по плотности;
  • размер по количеству объектов;
  • контрастная подпись.

Для аналитических панелей:

  • использование clusterProperties;
  • отображение агрегированных метрик;
  • несколько уровней цветового кодирования.

Для карт с тёмной темой:

  • яркие насыщенные цвета;
  • светлая обводка;
  • белый текст внутри кластеров.

Грамотно настроенная стилизация превращает кластеры из простого механизма оптимизации в полноценный инструмент визуального анализа данных, позволяющий мгновенно оценивать плотность, распределение и значимость объектов на карте независимо от их количества.