API для плагинов

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

Под плагином обычно понимается отдельный компонент, который:

  • добавляет новый интерфейс;
  • расширяет работу карты;
  • подключает внешние сервисы;
  • добавляет новые инструменты визуализации;
  • автоматизирует типовые операции.

Типичная структура плагина включает:

class CustomPlugin {
    constructor(options = {}) {
        this.options = options;
    }

    onAdd(map) {
        this.map = map;

        this.container = document.createElement('div');
        this.container.className = 'custom-plugin';

        return this.container;
    }

    onRemove() {
        this.container.remove();
        this.map = undefined;
    }
}

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


Интерфейс Control API

Основным механизмом создания расширений является интерфейс управления (Control API).

Для регистрации пользовательского элемента необходимо реализовать два метода:

  • onAdd(map)
  • onRemove()

Пример минимального элемента управления:

class HelloControl {
    onAdd(map) {
        this.map = map;

        this.container = document.createElement('div');
        this.container.className = 'maplibregl-ctrl';

        this.container.textContent = 'Hello MapLibre';

        return this.container;
    }

    onRemove() {
        this.container.parentNode.removeChild(this.container);
        this.map = undefined;
    }
}

Подключение:

const control = new HelloControl();

map.addControl(control, 'top-right');

Позиции размещения:

'top-left'
'top-right'
'bottom-left'
'bottom-right'

MapLibre автоматически добавляет контейнер в соответствующую область интерфейса карты.


Создание пользовательских кнопок

Наиболее распространённый тип плагинов — дополнительные панели инструментов.

Пример кнопки центрирования карты:

class CenterControl {
    onAdd(map) {
        this.map = map;

        const container = document.createElement('div');
        container.className = 'maplibregl-ctrl maplibregl-ctrl-group';

        const button = document.createElement('button');
        button.textContent = '⌂';

        button.addEventListener('click', () => {
            map.flyTo({
                center: [37.6176, 55.7558],
                zoom: 10
            });
        });

        container.appendChild(button);

        this.container = container;

        return container;
    }

    onRemove() {
        this.container.remove();
    }
}

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

map.addControl(new CenterControl());

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


Передача параметров в плагин

Плагин обычно должен быть настраиваемым.

Пример:

class HomeControl {
    constructor(options) {
        this.options = options;
    }

    onAdd(map) {
        this.map = map;

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

        button.textContent = 'Home';

        button.oncl ick = () => {
            map.flyTo({
                center: this.options.center,
                zoom: this.options.zoom
            });
        };

        const container = document.createElement('div');
        container.className =
            'maplibregl-ctrl maplibregl-ctrl-group';

        container.appendChild(button);

        this.container = container;

        return container;
    }

    onRemove() {
        this.container.remove();
    }
}

Подключение:

map.addControl(
    new HomeControl({
        center: [30.3, 59.9],
        zoom: 12
    })
);

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


Работа с событиями карты внутри плагинов

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

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

class CoordinatesControl {
    onAdd(map) {
        this.map = map;

        this.container = document.createElement('div');
        this.container.className = 'maplibregl-ctrl';

        this.listener = (event) => {
            const { lng, lat } = event.lngLat;

            this.container.textContent =
                `${lng.toFixed(5)}, ${lat.toFixed(5)}`;
        };

        map.on('mousemove', this.listener);

        return this.container;
    }

    onRemove() {
        this.map.off('mousemove', this.listener);

        this.container.remove();
    }
}

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


Доступ к состоянию карты

После получения экземпляра карты через onAdd становятся доступны все методы MapLibre.

Получение текущего масштаба:

const zoom = map.getZoom();

Получение центра:

const center = map.getCenter();

Получение текущего наклона:

const pitch = map.getPitch();

Получение направления:

const bearing = map.getBearing();

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

class StatusControl {
    onAdd(map) {
        this.map = map;

        this.container = document.createElement('div');
        this.container.className = 'maplibregl-ctrl';

        const update = () => {
            this.container.innerHTML =
                `Zoom: ${map.getZoom().toFixed(2)}`;
        };

        map.on('zoom', update);

        update();

        this.update = update;

        return this.container;
    }

    onRemove() {
        this.map.off('zoom', this.update);
        this.container.remove();
    }
}

Создание собственных панелей

Плагин может содержать сложный интерфейс.

Пример боковой панели:

class InfoPanel {
    onAdd(map) {
        this.map = map;

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

        panel.className = 'info-panel';

        panel.innerHTML = `
            <h3>Информация</h3>
            <div class="content"></div>
        `;

        this.container = panel;

        return panel;
    }

    onRemove() {
        this.container.remove();
    }
}

Дополнительные стили:

.info-panel {
    width: 250px;
    background: white;
    padding: 15px;
    font-family: sans-serif;
}

Подобные панели могут содержать таблицы, формы, фильтры и аналитические данные.


Плагины для работы со слоями

Плагин может полностью управлять отображением слоёв карты.

Пример переключателя видимости:

class LayerToggleControl {
    constructor(layerId) {
        this.layerId = layerId;
    }

    onAdd(map) {
        this.map = map;

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

        button.textContent = 'Toggle Layer';

        button.oncl ick = () => {
            const visible =
                map.getLayoutProperty(
                    this.layerId,
                    'visibility'
                );

            map.setLayoutProperty(
                this.layerId,
                'visibility',
                visible === 'visible'
                    ? 'none'
                    : 'visible'
            );
        };

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

        container.className =
            'maplibregl-ctrl maplibregl-ctrl-group';

        container.appendChild(button);

        this.container = container;

        return container;
    }

    onRemove() {
        this.container.remove();
    }
}

Управление источниками данных

Плагин может динамически добавлять и удалять источники.

Создание источника:

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

Удаление:

map.removeSource('cities');

Обновление данных:

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

source.setData(newData);

Внутри плагинов это позволяет реализовывать:

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

Плагины на основе пользовательских слоёв

Custom Layer API позволяет подключать собственный рендеринг через WebGL.

Минимальный шаблон:

const customLayer = {
    id: 'custom-layer',
    type: 'custom',

    renderingMode: '3d',

    onAdd(map, gl) {

    },

    render(gl, matrix) {

    }
};

Регистрация:

map.addLayer(customLayer);

Такой механизм применяется для:

  • трёхмерной графики;
  • визуализации датчиков;
  • интеграции Three.js;
  • отрисовки частиц;
  • научных визуализаций.

Интеграция с Three.js

Один из наиболее популярных видов плагинов — интеграция с Three.js.

В методе onAdd обычно создаются:

this.scene = new THREE.Scene();

this.camera =
    new THREE.Camera();

this.renderer =
    new THREE.WebGLRenderer({
        canvas: map.getCanvas(),
        context: gl
    });

В методе render выполняется:

this.renderer.resetState();

this.renderer.render(
    this.scene,
    this.camera
);

Подобные плагины позволяют размещать на карте:

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

Создание системы событий внутри плагина

Крупные плагины часто реализуют собственную шину событий.

Пример:

class PluginEvents {
    constructor() {
        this.listeners = {};
    }

    on(name, callback) {
        if (!this.listeners[name]) {
            this.listeners[name] = [];
        }

        this.listeners[name].push(callback);
    }

    emit(name, payload) {
        const handlers =
            this.listeners[name] || [];

        handlers.forEach(handler =>
            handler(payload)
        );
    }
}

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

events.on('layerLoaded', data => {
    console.log(data);
});

events.emit('layerLoaded', {
    count: 100
});

Такой подход облегчает масштабирование расширений.


Плагины для работы с геоданными

Распространённый сценарий — создание инструментов анализа GeoJSON.

Пример подсчёта объектов:

class FeatureCounter {
    constructor(sourceId) {
        this.sourceId = sourceId;
    }

    count() {
        const source =
            this.map.getSource(this.sourceId);

        return source._data.features.length;
    }
}

На практике подобные плагины могут выполнять:

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

Асинхронные плагины

Многие расширения работают с удалёнными сервисами.

Пример загрузки данных:

class RemoteDataPlugin {

    async load(url) {
        const response =
            await fetch(url);

        return await response.json();
    }

}

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

const data =
    await plugin.load('/api/geojson');

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

try {
    const data =
        await plugin.load(url);

} catch (error) {
    console.error(error);
}

Жизненный цикл плагина

Полноценный плагин обычно проходит несколько этапов:

  1. Создание экземпляра.
  2. Передача параметров.
  3. Подключение через addControl.
  4. Инициализация в onAdd.
  5. Подписка на события.
  6. Работа с картой.
  7. Освобождение ресурсов.
  8. Вызов onRemove.

Схематично:

const plugin =
    new CustomPlugin(options);

map.addControl(plugin);

map.removeControl(plugin);

После удаления необходимо очищать:

  • DOM-элементы;
  • обработчики событий;
  • таймеры;
  • WebGL-ресурсы;
  • внешние подключения.

Организация плагина как отдельного пакета

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

plugin/
│
├── src/
│   ├── index.js
│   ├── styles.css
│   └── plugin.js
│
├── dist/
│
├── package.json
│
└── README.md

Экспорт:

export default MyPlugin;

Подключение:

import MyPlugin
    from 'maplibre-plugin';

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

  • Vite;
  • Webpack;
  • Rollup;
  • Parcel.

Рекомендации по разработке расширений

Качественный плагин для MapLibre GL JS обычно обладает следующими характеристиками:

  • не изменяет глобальное состояние приложения;
  • использует собственное пространство имён;
  • корректно очищает ресурсы;
  • поддерживает повторное подключение;
  • документирует API;
  • допускает настройку через параметры;
  • не зависит от внутренней реализации MapLibre;
  • минимизирует количество обработчиков событий;
  • не блокирует главный поток длительными вычислениями;
  • обеспечивает совместимость между версиями библиотеки.

Грамотное использование Control API, Custom Layer API, системы событий и механизмов работы с источниками данных позволяет создавать расширения практически любого уровня сложности: от небольших кнопок интерфейса до полноценных геоинформационных модулей с собственной визуализацией, аналитикой и интеграцией внешних сервисов.