MapLibre GL Draw

Библиотека MapLibre GL JS предоставляет низкоуровневый рендеринг векторных карт на WebGL, однако для работы с интерактивным рисованием геометрий используется дополнительный инструмент — MapLibre GL Draw. Это расширение позволяет создавать, редактировать и удалять географические объекты непосредственно на карте, оперируя стандартом GeoJSON и интегрируясь с системой слоёв MapLibre.


Архитектура и принцип работы MapLibre GL Draw

MapLibre GL Draw представляет собой контрол, который подключается к экземпляру карты MapLibre GL JS и управляет отдельным слоем данных. В основе лежит модель:

  • GeoJSON FeatureCollection как единый источник истины
  • внутренний state менеджмент для редактируемых объектов
  • синхронизация между визуальным слоем и данными
  • событийная модель для отслеживания изменений

Каждый объект на карте хранится как GeoJSON Feature:

  • Point — маркеры
  • LineString — линии и маршруты
  • Polygon — полигоны и области

Редактирование не изменяет напрямую слой MapLibre, а обновляет внутреннее состояние, которое затем пересобирает источник данных.


Установка и подключение

MapLibre GL Draw обычно используется как отдельный пакет:

npm install @maplibre/maplibre-gl-draw

Подключение в проекте:

import maplibregl from "maplibre-gl";
import MapboxDraw from "@maplibre/maplibre-gl-draw";
import "@maplibre/maplibre-gl-draw/dist/mapbox-gl-draw.css";

Несмотря на историческое имя MapboxDraw, пакет совместим с MapLibre GL JS через адаптированный API.


Инициализация и добавление на карту

Контрол добавляется через стандартный механизм MapLibre:

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

const draw = new MapboxDraw({
  displayControlsDefault: false,
  controls: {
    point: true,
    line_string: true,
    polygon: true,
    trash: true
  }
});

map.addControl(draw);

После добавления контрол становится частью UI карты и начинает управлять собственным слоем данных.


Режимы работы (modes)

Ключевая концепция MapLibre GL Draw — режимы редактирования:

  • simple_select — выбор объектов
  • direct_select — точечное редактирование вершин
  • draw_point — создание точек
  • draw_line_string — создание линий
  • draw_polygon — создание полигонов
  • static — режим без редактирования

Смена режима выполняется программно:

draw.changeMode("draw_polygon");

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


Работа с GeoJSON данными

Добавление объектов

draw.add({
  type: "Feature",
  geometry: {
    type: "Point",
    coordinates: [30.3, 59.9]
  },
  properties: {}
});

Получение всех объектов

const data = draw.getAll();

Результат всегда возвращается в формате FeatureCollection.

Удаление объектов

draw.delete(featureId);

или массово:

draw.deleteAll();

Выбор и редактирование объектов

MapLibre GL Draw поддерживает два уровня взаимодействия:

Простое выделение

draw.changeMode("simple_select", {
  featureIds: ["id1", "id2"]
});

Точечное редактирование вершин

draw.changeMode("direct_select", {
  featureId: "id1"
});

В этом режиме доступны:

  • перемещение вершин
  • удаление точек геометрии
  • добавление новых узлов

События изменения состояния

Контрол генерирует набор событий, позволяющих синхронизировать данные:

создание объекта

map.on("draw.create", (e) => {
  console.log(e.features);
});

обновление объекта

map.on("draw.update", (e) => {
  console.log(e.features);
});

удаление объекта

map.on("draw.delete", (e) => {
  console.log(e.features);
});

смена режима

map.on("draw.modechange", (e) => {
  console.log(e.mode);
});

Эта событийная модель позволяет интегрировать Draw в любые внешние состояния приложения.


Настройка контролов интерфейса

Конфигурация UI элементов управляется через controls:

const draw = new MapboxDraw({
  controls: {
    point: true,
    line_string: true,
    polygon: true,
    trash: true,
    combine_features: true,
    uncombine_features: true
  }
});

Дополнительно можно скрыть стандартные кнопки:

displayControlsDefault: false

Работа с объединением геометрий

MapLibre GL Draw поддерживает операции объединения объектов:

  • combine_features — объединение нескольких объектов в MultiFeature
  • uncombine_features — разбиение MultiFeature

Пример сценария:

draw.changeMode("simple_select", {
  featureIds: ["a", "b"]
});

draw.combineFeatures();

Ограничения и валидация геометрий

Валидация выполняется на уровне пользовательской логики:

  • запрет самопересечений полигонов
  • контроль минимального количества вершин
  • ограничение площади
  • фильтрация координат

Пример проверки:

map.on("draw.create", (e) => {
  const feature = e.features[0];

  if (feature.geometry.type === "Polygon") {
    if (feature.geometry.coordinates[0].length < 4) {
      draw.delete(feature.id);
    }
  }
});

Кастомизация стилей

MapLibre GL Draw использует внутренние слои MapLibre, которые можно переопределять:

const draw = new MapboxDraw({
  styles: [
    {
      id: "gl-draw-polygon-fill",
      type: "fill",
      paint: {
        "fill-color": "#00ff00",
        "fill-opacity": 0.3
      }
    }
  ]
});

Кастомизация затрагивает:

  • цвет вершин
  • линии обводки
  • активные состояния
  • hover-эффекты

Работа с источниками данных

Внутри MapLibre GL Draw создаётся отдельный source:

  • тип: GeoJSON source
  • id: draw features
  • динамическое обновление через setData

Это означает, что любые внешние изменения должны учитывать актуальное состояние:

const all = draw.getAll();

map.getSource("mapbox-gl-draw-cold").setData(all);

Импорт и экспорт данных

GeoJSON можно сохранять и восстанавливать:

Экспорт

const geojson = draw.getAll();
localStorage.setItem("draw-data", JSON.stringify(geojson));

Импорт

const saved = JSON.parse(localStorage.getItem("draw-data"));

draw.set(saved);

Это позволяет реализовать:

  • сохранение чертежей
  • обмен данными
  • серверную синхронизацию

Интеграция с внешними слоями MapLibre GL JS

Draw не изолирован от основной карты. Он может взаимодействовать с пользовательскими слоями:

  • проверка пересечений с тайлами
  • snap-to-road логика через внешние источники
  • подсветка объектов через queryRenderedFeatures

Пример:

map.on("click", (e) => {
  const features = map.queryRenderedFeatures(e.point);

  console.log(features);
});

Работа на мобильных устройствах

MapLibre GL Draw поддерживает touch-интеракции:

  • drag для перемещения вершин
  • long press для активации режимов
  • pinch zoom не конфликтует с редактированием

Особое внимание требуется к:

  • точности попадания в вершины
  • увеличению hit-area для мобильных экранов
  • предотвращению ложных событий tap

Производительность при большом количестве объектов

При работе с тысячами объектов критично:

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

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

  • хранение данных вне Draw state
  • синхронизацию только при завершении действия
  • дебаунс событий draw.update

Расширение поведения через кастомные modes

MapLibre GL Draw позволяет создавать собственные режимы:

const customMode = {
  onSetup() {
    return {};
  },
  onClick(state, e) {
    console.log(e.lngLat);
  }
};

draw.addMode("custom_mode", customMode);

Это используется для:

  • построения маршрутов
  • рисования по сетке
  • магнитного прилипания к объектам
  • специализированных GIS-инструментов

Типизация и использование в TypeScript

Библиотека поддерживает базовые типы:

import { MapboxDraw } from "@maplibre/maplibre-gl-draw";

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

  • Feature
  • Geometry
  • FeatureCollection
  • DrawMode

Типизация важна при работе с серверной валидацией GeoJSON.


Связь с GeoJSON стандартом

MapLibre GL Draw строго следует спецификации RFC 7946:

  • координаты в формате [longitude, latitude]
  • использование WGS84
  • FeatureCollection как контейнер

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


Синхронизация состояния между клиентом и сервером

В архитектуре SPA часто используется схема:

  • Draw как локальный редактор
  • сервер как источник истины
  • diff между состояниями

Пример подхода:

map.on("draw.update", () => {
  const data = draw.getAll();
  fetch("/api/save", {
    method: "POST",
    body: JSON.stringify(data)
  });
});

Обработка сложных сценариев редактирования

В продвинутых сценариях требуется учитывать:

  • вложенные MultiPolygon структуры
  • самопересечения
  • топологическую корректность
  • snapping к сетке

Реализация часто требует внешних библиотек геообработки, таких как turf.js, которые работают совместно с Draw.

import * as turf from "@turf/turf";

const cleaned = turf.cleanCoords(feature);

Взаимодействие с пользовательскими слоями MapLibre GL JS

Draw может сосуществовать с:

  • heatmap слоями
  • raster tiles
  • vector tiles
  • 3D extrusions

Важно учитывать порядок слоёв:

  • draw layers обычно поверх базовых данных
  • кастомные слои могут перекрывать интерактивные вершины

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

Контрол можно динамически добавлять и удалять:

map.addControl(draw);
map.removeControl(draw);

Это полезно при переключении режимов приложения:

  • режим просмотра
  • режим редактирования
  • режим анализа данных