Steps и banners

В экосистеме Mapbox GL JS выражения (Expressions) позволяют динамически изменять внешний вид карты на основе данных, масштаба, состояния объектов и множества других факторов. Среди наиболее важных инструментов стилизации особое место занимают выражения step и различные визуальные элементы, реализуемые в виде баннеров (banners), информационных панелей и подписей, отображаемых поверх карты.

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

Баннеры представляют собой интерфейсный слой приложения и обычно реализуются средствами HTML, CSS и JavaScript поверх контейнера карты. Они отображают дополнительную информацию, легенды, уведомления, подсказки или результаты взаимодействия с объектами карты.


Выражение Step

Назначение

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

Общий синтаксис:

[
    "step",
    input,
    defaultValue,
    stop1, value1,
    stop2, value2,
    stop3, value3
]

Где:

  • input — анализируемое значение;
  • defaultValue — результат до достижения первого порога;
  • stop — контрольная точка;
  • value — значение после достижения соответствующего порога.

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

input < stop1        → defaultValue
input ≥ stop1        → value1
input ≥ stop2        → value2
input ≥ stop3        → value3

Step на основе масштаба карты

Часто требуется менять стиль в зависимости от уровня приближения.

Пример изменения размера текста:

map.addLayer({
    id: 'cities',
    type: 'symbol',
    source: 'cities',
    layout: {
        'text-field': ['get', 'name'],
        'text-size': [
            'step',
            ['zoom'],
            10,
            6, 12,
            10, 16,
            14, 24
        ]
    }
});

Результат:

Zoom Размер текста
< 6 10
6–9 12
10–13 16
≥ 14 24

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


Step на основе свойств объектов

Наиболее распространённый сценарий — тематическое отображение данных.

Предположим, имеется набор городов с населением:

{
    "name": "Karaganda",
    "population": 497000
}

Изменение радиуса кругов:

'circle-radius': [
    'step',
    ['get', 'population'],
    5,
    100000, 8,
    500000, 12,
    1000000, 18
]

Результат:

Население Радиус
< 100000 5
100000–499999 8
500000–999999 12
≥ 1000000 18

Изменение цвета через Step

Цветовое кодирование является одним из основных методов тематической визуализации.

Пример окрашивания регионов по плотности населения:

'fill-color': [
    'step',
    ['get', 'density'],
    '#edf8fb',
    50, '#b2e2e2',
    100, '#66c2a4',
    500, '#2ca25f',
    1000, '#006d2c'
]

Получается пять классов данных.


Step для прозрачности

Иногда необходимо скрывать малозначимые объекты.

'circle-opacity': [
    'step',
    ['get', 'score'],
    0.2,
    50, 0.4,
    75, 0.7,
    90, 1
]

Объекты с высоким рейтингом становятся визуально более заметными.


Step для толщины линий

Пример стилизации автомобильных дорог:

'line-width': [
    'step',
    ['get', 'traffic'],
    1,
    1000, 2,
    5000, 4,
    10000, 8
]

Интенсивность движения напрямую влияет на толщину отображаемой линии.


Step и уровни масштабирования

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

Пример изменения прозрачности:

'fill-opacity': [
    'step',
    ['zoom'],
    0.2,
    8, 0.5,
    12, 0.8
]

На удалённом масштабе слой выглядит менее навязчиво.


Сравнение Step и Interpolate

Mapbox GL JS предоставляет два похожих механизма:

Step

[
    'step',
    ['get', 'value'],
    5,
    100, 10,
    500, 20
]

Дискретный переход:

5 → 10 → 20

Interpolate

[
    'interpolate',
    ['linear'],
    ['get', 'value'],
    0, 5,
    100, 10,
    500, 20
]

Плавный переход:

5 → 6 → 7 → 8 → ...

Использование:

Задача Метод
Классы населения Step
Плавное изменение размеров Interpolate
Категории данных Step
Анимации Interpolate

Создание баннеров поверх карты

Mapbox отвечает за отображение карты, а баннеры обычно создаются через DOM-элементы.

Простейший пример:

<div id="banner">
    Система мониторинга транспорта
</div>

<div id="map"></div>

CSS:

#banner {
    position: absolute;
    top: 20px;
    left: 20px;

    padding: 12px 20px;

    background: white;
    border-radius: 6px;

    z-index: 1000;

    font-family: Arial;
}

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


Информационный баннер

Часто требуется отображать статистику.

HTML:

<div id="info-banner">
    Загрузка данных...
</div>

Jav * aScript:

map.on('load', () => {

    const count =
        map.querySourceFeatures('cities').length;

    document.getElementById('info-banner')
        .innerText =
        `Количество городов: ${count}`;

});

После загрузки карты баннер автоматически обновляется.


Динамическое изменение содержимого баннера

При выборе объекта можно показывать сведения о нём.

map.on('click', 'cities-layer', (e) => {

    const feature = e.features[0];

    document.getElementById('banner')
        .innerHTML = `
            <h3>${feature.properties.name}</h3>
            <p>
                Население:
                ${feature.properties.population}
            </p>
        `;
});

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


Баннер состояния карты

Отображение текущего масштаба:

const banner =
    document.getElementById('zoom-banner');

map.on('zoom', () => {

    banner.textContent =
        `Zoom: ${map.getZoom().toFixed(2)}`;

});

Информация обновляется в режиме реального времени.


Баннер загрузки данных

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

HTML:

<div id="loading-banner">
    Загрузка...
</div>

CSS:

#loading-banner {
    display: none;
}

Jav * aScript:

map.on('dataloading', () => {

    document
        .getElementById('loading-banner')
        .style.display = 'block';

});

map.on('idle', () => {

    document
        .getElementById('loading-banner')
        .style.display = 'none';

});

Баннер предупреждений

Для отображения ошибок:

map.on('error', (e) => {

    const banner =
        document.getElementById('error-banner');

    banner.style.display = 'block';

    banner.textContent =
        e.error.message;

});

Подобный механизм значительно упрощает диагностику проблем.


Легенда как разновидность баннера

При использовании step легенда должна соответствовать диапазонам данных.

Пример:

<div id="legend">

    <div>
        <span class="color low"></span>
        Менее 50
    </div>

    <div>
        <span class="color medium"></span>
        50–100
    </div>

    <div>
        <span class="color high"></span>
        Более 100
    </div>

</div>

Если стили слоя основаны на выражении step, легенда должна использовать те же пороговые значения.


Генерация легенды автоматически

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

const ranges = [
    {
        label: '< 50',
        color: '#edf8fb'
    },
    {
        label: '50 - 100',
        color: '#b2e2e2'
    },
    {
        label: '100 - 500',
        color: '#66c2a4'
    }
];

Создание элементов:

const legend =
    document.getElementById('legend');

ranges.forEach(item => {

    const div =
        document.createElement('div');

    div.innerHTML = `
        <span
            style="
                background:${item.color};
                width:15px;
                height:15px;
                display:inline-block;
            ">
        </span>
        ${item.label}
    `;

    legend.appendChild(div);

});

Баннеры и адаптивный интерфейс

На мобильных устройствах баннеры должны занимать минимум пространства.

Пример:

@media (max-width: 768px) {

    #banner {

        top: auto;
        bottom: 10px;

        left: 10px;
        right: 10px;

        width: auto;
    }

}

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


Совместное использование Step и баннеров

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

  1. Источник данных GeoJSON.
  2. Стилизацию через step.
  3. Автоматически генерируемую легенду.
  4. Информационный баннер.
  5. Панель статистики.
  6. Подсказки при взаимодействии с объектами.

Пример слоя:

map.addLayer({
    id: 'population-layer',
    type: 'fill',
    source: 'regions',
    paint: {
        'fill-color': [
            'step',
            ['get', 'population'],
            '#fee5d9',
            100000,
            '#fcae91',
            500000,
            '#fb6a4a',
            1000000,
            '#cb181d'
        ]
    }
});

Соответствующая легенда:

[
    '< 100000',
    '100000 - 500000',
    '500000 - 1000000',
    '> 1000000'
]

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