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).
Для регистрации пользовательского элемента необходимо реализовать два метода:
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);
Внутри плагинов это позволяет реализовывать:
Custom Layer API позволяет подключать собственный рендеринг через WebGL.
Минимальный шаблон:
const customLayer = {
id: 'custom-layer',
type: 'custom',
renderingMode: '3d',
onAdd(map, gl) {
},
render(gl, matrix) {
}
};
Регистрация:
map.addLayer(customLayer);
Такой механизм применяется для:
Один из наиболее популярных видов плагинов — интеграция с 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);
}
Полноценный плагин обычно проходит несколько этапов:
addControl.onAdd.onRemove.Схематично:
const plugin =
new CustomPlugin(options);
map.addControl(plugin);
map.removeControl(plugin);
После удаления необходимо очищать:
Для распространения расширений обычно используется структура:
plugin/
│
├── src/
│ ├── index.js
│ ├── styles.css
│ └── plugin.js
│
├── dist/
│
├── package.json
│
└── README.md
Экспорт:
export default MyPlugin;
Подключение:
import MyPlugin
from 'maplibre-plugin';
Подобная организация обеспечивает совместимость с современными сборщиками:
Качественный плагин для MapLibre GL JS обычно обладает следующими характеристиками:
Грамотное использование Control API, Custom Layer API, системы событий и механизмов работы с источниками данных позволяет создавать расширения практически любого уровня сложности: от небольших кнопок интерфейса до полноценных геоинформационных модулей с собственной визуализацией, аналитикой и интеграцией внешних сервисов.