Обработка событий загрузки карты

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

События загрузки позволяют определить момент, когда определённый этап инициализации завершён и можно безопасно выполнять дальнейшие действия:

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

Базовое создание карты выглядит следующим образом:

const map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [37.6176, 55.7558],
    zoom: 10
});

После выполнения конструктора карта ещё не готова к работе. Большинство действий необходимо выполнять внутри обработчиков соответствующих событий.


Подписка на события

Для регистрации обработчика используется метод on():

map.on('load', () => {
    console.log('Карта загружена');
});

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

map.on('имя_события', (event) => {
    // обработка события
});

Удаление обработчика выполняется через метод off():

function onLoad() {
    console.log('Карта готова');
}

map.on('load', onLoad);

map.off('load', onLoad);

Для однократного выполнения применяется метод once():

map.once('load', () => {
    console.log('Событие выполнится только один раз');
});

Событие load

Назначение

Событие load считается одним из наиболее важных в MapLibre GL JS. Оно возникает после полной загрузки начального стиля карты и готовности карты к дальнейшей настройке.

map.on('load', () => {
    console.log('Карта полностью инициализирована');
});

После получения этого события безопасно:

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

Пример добавления слоя после загрузки:

map.on('load', () => {
    map.addSource('cities', {
        type: 'geojson',
        data: './cities.geojson'
    });

    map.addLayer({
        id: 'cities-layer',
        type: 'circle',
        source: 'cities',
        paint: {
            'circle-radius': 6,
            'circle-color': '#ff0000'
        }
    });
});

Почему нельзя добавлять слои до события load

Нередко возникает ошибка:

map.addLayer({
    id: 'test',
    type: 'circle'
});

Если код выполняется сразу после создания карты, может появиться исключение:

Style is not done loading

Причина заключается в том, что стиль ещё не загружен.

Неправильно:

const map = new maplibregl.Map({...});

map.addLayer({...});

Правильно:

const map = new maplibregl.Map({...});

map.on('load', () => {
    map.addLayer({...});
});

Событие style.load

Назначение

Событие style.load вызывается после загрузки стиля карты.

map.on('style.load', () => {
    console.log('Стиль загружен');
});

Особенно полезно при динамической смене стилей.

Например:

map.setStyle('style-dark.json');

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

Для повторного добавления данных используется:

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

    map.addSource('roads', {
        type: 'geojson',
        data: './roads.geojson'
    });

    map.addLayer({
        id: 'roads-layer',
        type: 'line',
        source: 'roads'
    });

});

Разница между load и style.load

load

Срабатывает при первой полной инициализации карты.

map.on('load', () => {
    console.log('Первый запуск');
});

style.load

Срабатывает каждый раз после загрузки стиля.

map.on('style.load', () => {
    console.log('Стиль готов');
});

Если стиль меняется несколько раз:

map.setStyle('style1.json');
map.setStyle('style2.json');
map.setStyle('style3.json');

Событие load выполнится один раз, а style.load — после каждой загрузки нового стиля.


Событие sourcedata

Назначение

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

map.on('sourcedata', (e) => {
    console.log(e);
});

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

  • загрузке GeoJSON;
  • загрузке векторных тайлов;
  • обновлении источников;
  • изменении состояния данных.

Пример:

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

    if (e.sourceId === 'cities') {
        console.log('Источник cities обновлён');
    }

});

Информация объекта события sourcedata

Объект события содержит большое количество служебной информации.

Пример:

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

    console.log(e.sourceId);
    console.log(e.sourceDataType);
    console.log(e.isSourceLoaded);

});

Часто используемые свойства:

Свойство Описание
sourceId Идентификатор источника
source Объект источника
isSourceLoaded Полностью ли загружен источник
sourceDataType Тип изменения данных

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

Иногда требуется дождаться завершения загрузки конкретного источника.

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

    if (
        e.sourceId === 'cities' &&
        e.isSourceLoaded
    ) {
        console.log('GeoJSON полностью загружен');
    }

});

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


Событие data

Событие более общего уровня.

map.on('data', (e) => {
    console.log(e);
});

Оно возникает при любых изменениях данных карты:

  • загрузке стиля;
  • загрузке источников;
  • обновлении тайлов;
  • изменении внутренних структур.

Пример:

map.on('data', (e) => {
    console.log('Тип данных:', e.dataType);
});

Свойство dataType

Позволяет определить источник события.

map.on('data', (e) => {
    console.log(e.dataType);
});

Возможные значения:

style
source

Проверка:

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

    if (e.dataType === 'style') {
        console.log('Изменился стиль');
    }

    if (e.dataType === 'source') {
        console.log('Изменились данные источника');
    }

});

Событие styledata

Назначение

Возникает при обновлении данных стиля.

map.on('styledata', () => {
    console.log('Стиль обновлён');
});

Событие может вызываться несколько раз в процессе работы карты.

Например:

map.setPaintProperty(
    'roads',
    'line-color',
    '#00ff00'
);

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


Событие sourcedataloading

Назначение

Сообщает о начале загрузки данных источника.

map.on('sourcedataloading', (e) => {
    console.log('Начало загрузки');
});

Практический пример:

map.on('sourcedataloading', () => {
    showLoader();
});

Событие styledataloading

Назначение

Возникает при начале загрузки данных стиля.

map.on('styledataloading', () => {
    console.log('Стиль начал загружаться');
});

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


Событие dataloading

Это общий аналог событий загрузки данных.

map.on('dataloading', () => {
    console.log('Идёт загрузка данных');
});

Срабатывает раньше события data.

Типичная последовательность:

dataloading
data

Событие idle

Назначение

Одно из наиболее полезных событий для контроля полной готовности карты.

map.on('idle', () => {
    console.log('Карта находится в состоянии покоя');
});

Событие возникает тогда, когда:

  • завершены запросы данных;
  • завершена отрисовка;
  • отсутствуют анимации;
  • отсутствуют незавершённые операции.

Фактически это состояние полной готовности интерфейса.


Использование idle после добавления данных

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

    map.addSource('buildings', {
        type: 'geojson',
        data: './buildings.geojson'
    });

    map.addLayer({
        id: 'buildings-layer',
        type: 'fill',
        source: 'buildings'
    });

    map.once('idle', () => {
        console.log('Все данные отображены');
    });

});

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


Проверка состояния через isStyleLoaded

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

if (map.isStyleLoaded()) {
    console.log('Стиль уже готов');
}

Метод возвращает логическое значение:

true
false

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

Для конкретного источника:

const source = map.getSource('cities');

Однако обычно контроль осуществляется через события:

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

    if (
        e.sourceId === 'cities' &&
        e.isSourceLoaded
    ) {
        console.log('Источник готов');
    }

});

Создание индикатора загрузки карты

Распространённый сценарий — отображение спиннера.

HTML:

<div id="loader">
    Загрузка...
</div>

Jav * aScript:

map.on('dataloading', () => {
    loader.style.display = 'block';
});

map.on('idle', () => {
    loader.style.display = 'none';
});

Логика выглядит следующим образом:

  1. Начинается загрузка.
  2. Отображается индикатор.
  3. Карта загружает тайлы и данные.
  4. Наступает событие idle.
  5. Индикатор скрывается.

Комбинирование нескольких событий

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

map.on('load', initMap);

map.on('style.load', restoreLayers);

map.on('sourcedata', monitorSources);

map.on('idle', updateUI);

Где:

function initMap() {
    console.log('Инициализация');
}

function restoreLayers() {
    console.log('Восстановление слоёв');
}

function monitorSources(event) {
    console.log(event.sourceId);
}

function updateUI() {
    console.log('Обновление интерфейса');
}

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


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

При первом запуске карты часто наблюдается следующая цепочка:

styledataloading
styledata
load
sourcedataloading
sourcedata
data
idle

При смене стиля:

styledataloading
style.load
styledata
sourcedataloading
sourcedata
idle

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