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

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

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

Плагины в этом контексте не являются формально стандартизированными модулями — это соглашение об архитектуре, опирающееся на интерфейсы:

  • управление картой через экземпляр Map
  • расширение UI через IControl
  • добавление кастомной графики через CustomLayerInterface
  • подключение данных через Source

Базовые точки расширения

Контролы интерфейса карты

Контролы представляют собой DOM-элементы, интегрируемые в карту через стандартный контракт.

Основной интерфейс:

  • onAdd(map) — инициализация
  • onRemove() — очистка ресурсов
  • getDefaultPosition() — позиционирование в интерфейсе карты

Контролы используются для реализации UI-плагинов: панели инструментов, переключатели слоёв, фильтры данных.

class ZoomLoggerControl {
  onAdd(map) {
    this._map = map;
    this._container = document.createElement('div');
    this._container.className = 'maplibre-ctrl';

    this._container.innerHTML = 'Zoom: ' + map.getZoom();

    this._map.on('zoom', () => {
      this._container.innerHTML = 'Zoom: ' + map.getZoom().toFixed(2);
    });

    return this._container;
  }

  onRemove() {
    this._map = undefined;
    this._container.remove();
  }

  getDefaultPosition() {
    return 'top-right';
  }
}

Контролы формируют основу UI-ориентированных плагинов, особенно в случае аналитических панелей и инструментов редактирования.


Плагины на основе событийной модели

MapLibre GL JS предоставляет богатую систему событий:

  • load
  • move
  • zoom
  • click
  • render
  • data

Плагины часто строятся как подписчики на эти события, расширяющие поведение карты без вмешательства в рендер-цикл.

Пример структуры плагина-логгера:

class MapEventLogger {
  constructor(map) {
    this.map = map;
    this.handlers = {
      move: this.onMove.bind(this),
      zoom: this.onZoom.bind(this)
    };

    this.init();
  }

  init() {
    this.map.on('move', this.handlers.move);
    this.map.on('zoom', this.handlers.zoom);
  }

  onMove() {
    const center = this.map.getCenter();
    console.log('Center updated:', center);
  }

  onZoom() {
    console.log('Zoom level:', this.map.getZoom());
  }

  destroy() {
    this.map.off('move', this.handlers.move);
    this.map.off('zoom', this.handlers.zoom);
  }
}

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


Кастомные слои как основа графических плагинов

Одним из наиболее мощных механизмов расширения является интерфейс CustomLayerInterface, позволяющий подключать произвольную WebGL-рендеризацию.

Этот механизм используется для:

  • 3D-визуализации
  • потоковой отрисовки данных
  • интеграции с WebGL-движками
  • визуализации сложных геометрий

Структура кастомного слоя:

  • onAdd(map, gl) — инициализация ресурсов WebGL
  • render(gl, matrix) — отрисовка кадра
  • onRemove() — очистка
class PulsingDotLayer {
  onAdd(map, gl) {
    this.map = map;
    this.gl = gl;

    this.program = this.createProgram(gl);
    this.buffer = gl.createBuffer();

    const vertices = new Float32Array([
      0.0, 0.0
    ]);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  }

  render(gl, matrix) {
    gl.useProgram(this.program);

    gl.uniformMatrix4fv(
      gl.getUniformLocation(this.program, 'u_matrix'),
      false,
      matrix
    );

    gl.drawArrays(gl.POINTS, 0, 1);
  }

  onRemove() {
    this.gl.deleteBuffer(this.buffer);
  }
}

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


Плагины источников данных (Sources)

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

Основные типы:

  • geojson
  • vector
  • raster
  • image
  • video

Плагин может реализовать собственный источник через map.addSource, включая динамическое обновление данных.

map.addSource('dynamic-points', {
  type: 'geojson',
  data: {
    type: 'FeatureCollection',
    features: []
  }
});

Обновление данных становится частью логики плагина:

function updateData(sourceId, features) {
  map.getSource(sourceId).setData({
    type: 'FeatureCollection',
    features
  });
}

Такая модель позволяет реализовывать:

  • realtime трекинг
  • потоковые геоданные
  • интеграцию с WebSocket-сервисами

Структура масштабируемого плагина

Полноценный плагин обычно включает несколько слоёв ответственности:

1. Ядро плагина

Отвечает за связь с MapLibre и управление жизненным циклом.

2. Рендер-слой

Реализует визуализацию через слои или WebGL.

3. Data-слой

Инкапсулирует работу с источниками данных.

4. UI-слой

Контролы и взаимодействие с пользователем через DOM.


Пример комплексной структуры:

class HeatmapPlugin {
  constructor(map, options) {
    this.map = map;
    this.options = options;

    this.sourceId = 'heatmap-source';
    this.layerId = 'heatmap-layer';

    this.init();
  }

  init() {
    this.map.addSource(this.sourceId, {
      type: 'geojson',
      data: { type: 'FeatureCollection', features: [] }
    });

    this.map.addLayer({
      id: this.layerId,
      type: 'heatmap',
      source: this.sourceId,
      paint: {
        'heatmap-intensity': 1
      }
    });
  }

  setData(features) {
    this.map.getSource(this.sourceId).setData({
      type: 'FeatureCollection',
      features
    });
  }

  destroy() {
    this.map.removeLayer(this.layerId);
    this.map.removeSource(this.sourceId);
  }
}

Управление жизненным циклом плагинов

Корректная работа расширений требует строгого контроля ресурсов:

  • регистрация обработчиков событий
  • очистка WebGL буферов
  • удаление слоёв и источников
  • освобождение DOM-элементов

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


Интеграция с внешними системами

Плагины часто выступают мостом между картой и внешними сервисами:

  • WebSocket потоки
  • REST API
  • GraphQL подписки
  • MQTT брокеры

Типичный паттерн — реактивное обновление данных:

socket.onmess age = (event) => {
  const data = JSON.parse(event.data);
  map.getSource('realtime').setData(data);
};

Композиция плагинов

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

Основные подходы:

  • разделение по слоям (heatmap + markers + clusters)
  • разделение по доменам (транспорт, аналитика, мониторинг)
  • событийная координация через глобальный event bus

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


Производительность в плагинах

Ключевые ограничения возникают в трёх областях:

  • частота перерисовки (render loop)
  • количество WebGL вызовов
  • частота обновления источников данных

Оптимизация включает:

  • батчинг данных
  • минимизацию setData
  • использование requestAnimationFrame
  • кэширование вычислений матриц трансформации

Особое внимание требуется кастомным слоям, так как они напрямую влияют на WebGL pipeline карты.


Безопасность и изоляция логики

Плагины работают в том же контексте, что и карта, поэтому:

  • отсутствует sandbox изоляция
  • возможны конфликты namespace
  • требуется аккуратное управление глобальными объектами

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


Расширение возможностей через комбинирование API

Максимальная гибкость достигается при сочетании:

  • UI-контролов
  • кастомных слоёв
  • источников данных
  • событийной модели

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