Редактирование геометрии

Редактирование геометрии в MapLibre GL JS опирается на работу с источниками данных типа GeoJSON и динамическое обновление отображаемых объектов через механизм перерисовки слоёв. Вся интерактивная логика строится вокруг преобразования пользовательских действий (клик, перемещение курсора, перетаскивание) в модификации геометрии и последующую передачу обновлённого состояния в источник данных карты.

В MapLibre GL JS геометрические объекты представлены в формате GeoJSON, где каждая сущность описывается как Feature с координатами и свойствами. Источник данных типа geojson используется как центральный контейнер редактируемых объектов.

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

map.getSource('draw-layer').setData(geojson);

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

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

Каждое обновление GeoJSON вызывает пересчёт всех зависимых слоёв. Внутренний конвейер MapLibre выполняет:

  • пересборку буферов вершин;
  • повторную триангуляцию полигонов;
  • обновление линий и заливок;
  • перерасчёт кластеризации (если включена).

Частые вызовы setData становятся критическим фактором производительности, поэтому при редактировании геометрии применяется пакетирование изменений или локальное накопление состояния перед обновлением источника.

Базовые события взаимодействия

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

  • mousedown — начало захвата вершины или объекта;
  • mousemove — изменение координат;
  • mouseup — фиксация результата;
  • click — выбор объекта или вершины.

Перемещение координат осуществляется через преобразование экранных координат в географические:

const lngLat = map.unproject([e.point.x, e.point.y]);

Этот механизм является основой любого редактора геометрии поверх MapLibre GL JS.

Редактирование точечных объектов

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

Алгоритм включает:

  • определение активной точки;
  • отслеживание перемещения курсора;
  • обновление координаты в GeoJSON;
  • вызов обновления источника данных.
feature.geometry.coordinates = [lng, lat];
source.setData(data);

Для повышения отзывчивости часто используется промежуточное состояние без немедленного обновления источника на каждом событии mousemove, с применением throttling.

Редактирование линий

Линии (LineString) требуют управления массивом координат. Основные операции:

  • перемещение существующих вершин;
  • добавление новых узлов;
  • удаление сегментов.

При захвате вершины фиксируется её индекс в массиве координат. В процессе перетаскивания изменяется только соответствующий элемент:

coordinates[index] = [lng, lat];

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

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

Редактирование полигонов

Полигональные геометрии обладают дополнительной сложностью из-за наличия замкнутого контура и возможных внутренних колец (holes).

Структура координат:

  • внешний контур: coordinates[0];
  • внутренние отверстия: coordinates[1..n].

Редактирование включает:

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

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

ring[ring.length - 1] = ring[0];

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

Работа с состоянием через FeatureState

MapLibre GL JS поддерживает механизм FeatureState, позволяющий хранить временное состояние объектов без изменения GeoJSON:

  • выделение активной вершины;
  • подсветка редактируемого объекта;
  • состояние hover при наведении.
map.setFeatureState(
  { source: 'draw', id: featureId },
  { active: true }
);

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

Реализация перетаскивания вершин

Редактирование вершин основано на комбинации hit-testing и событий мыши. Определение активной вершины выполняется через анализ близости курсора к экранным координатам вершин.

Общий алгоритм:

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

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

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

В процессе редактирования часто применяется привязка координат:

  • к географической сетке;
  • к существующим вершинам;
  • к линиям или границам.

Механизм снаппинга реализуется через округление координат или поиск ближайших объектов в заданном радиусе:

lng = Math.round(lng * precision) / precision;
lat = Math.round(lat * precision) / precision;

Дополнительно могут применяться геометрические ограничения, например запрет выхода за заданный bounding box или сохранение топологической корректности.

Библиотека MapLibre GL Draw

Поверх базового API часто используется расширение MapLibre GL Draw, реализующее полноценный редактор геометрии. Оно предоставляет:

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

Внутренне библиотека использует отдельные interaction modes, каждый из которых управляет состоянием редактора и интерпретацией событий карты.

Архитектура строится вокруг состояния:

  • selected feature;
  • active vertex;
  • mode (draw, direct_select, simple_select).

Производительность при интерактивном редактировании

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

Основные оптимизации:

  • ограничение частоты обновления через requestAnimationFrame;
  • батчинг изменений GeoJSON;
  • минимизация вызовов setData;
  • использование простых слоёв во время перетаскивания;
  • временное отключение сложных эффектов (blur, shadow).

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

Архитектура пользовательского редактора

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

  • слой визуализации (fill, line, circle);
  • слой вершин (handles);
  • менеджер состояния геометрии;
  • обработчик событий карты;
  • модуль пространственного поиска;
  • механизм синхронизации GeoJSON.

Состояние геометрии часто хранится отдельно от источника карты и синхронизируется через явные коммиты изменений, что позволяет реализовать undo/redo и временные транзакции редактирования.

История изменений и откат состояния

Для поддержки отмены действий применяется хранение снимков состояния GeoJSON или журнал операций:

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

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

Типовые сценарии редактирования

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

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

Все сценарии объединяются общей моделью: изменение координат → пересборка GeoJSON → обновление источника → перерисовка слоёв.