Drag and drop на карте

Перетаскивание объектов на карте в JavaScript API платформы HERE Technologies строится вокруг двух уровней взаимодействия: встроенных drag-событий карты и пользовательской реализации drag & drop через DOM и события браузера. В результате можно реализовать перемещение маркеров, интерактивное размещение объектов, перенос элементов интерфейса на карту и обратное преобразование экранных координат в географические.


В HERE Maps API маркеры поддерживают режим перетаскивания через параметр volatility. Он включает механизм изменения координат объекта при перемещении мыши или касании.

const platform = new H.service.Platform({
  apikey: 'YOUR_API_KEY'
});

const defaultLayers = platform.createDefaultLayers();

const map = new H.Map(
  document.getElementById('map'),
  defaultLayers.vector.normal.map,
  {
    center: { lat: 52.5200, lng: 13.4050 },
    zoom: 10,
    pixelRatio: window.devicePixelRatio || 1
  }
);

const beh * avior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

Создание перетаскиваемого маркера:

const marker = new H.map.Marker(
  { lat: 52.5200, lng: 13.4050 },
  {
    volatility: true
  }
);

map.addObject(marker);

Особенности volatility

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

События drag у объектов карты

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

marker.addEventListener('dragstart', function (ev) {
  console.log('Начало перетаскивания');
});

marker.addEventListener('drag', function (ev) {
  const pointer = ev.currentPointer;
  console.log(pointer.viewportX, pointer.viewportY);
});

marker.addEventListener('dragend', function (ev) {
  const geo = map.screenToGeo(ev.currentPointer.viewportX, ev.currentPointer.viewportY);
  console.log('Новая позиция:', geo);
});

Основные события

  • dragstart — фиксирует начальную точку перемещения
  • drag — вызывается при каждом движении курсора
  • dragend — завершение перемещения
  • dragenter / dragleave — для сложных сценариев взаимодействия

Преобразование экранных координат в географические

Ключевая операция при drag & drop — перевод пикселей в координаты карты.

const geoPoint = map.screenToGeo(
  pointer.viewportX,
  pointer.viewportY
);

И обратное преобразование:

const screenPoint = map.geoToScreen(
  { lat: 52.52, lng: 13.405 }
);

Это позволяет:

  • фиксировать позицию при drop
  • размещать объекты по клику
  • синхронизировать UI и карту

Реализация drag & drop HTML элементов на карту

Помимо встроенного drag маркеров, часто требуется перенос DOM-элементов (например, карточек объектов) на карту.

Подготовка draggable элемента

<div id="draggable" draggable="true">
  Объект для переноса
</div>
const draggable = document.getElementById('draggable');

draggable.addEventListener('dragstart', function (ev) {
  ev.dataTransfer.setData('text/plain', 'marker');
});

Обработка drop на карте

const mapContainer = map.getElement();

mapContainer.addEventListener('dragover', function (ev) {
  ev.preventDefault();
});

mapContainer.addEventListener('drop', function (ev) {
  ev.preventDefault();

  const rect = mapContainer.getBoundingClientRect();

  const viewportX = ev.clientX - rect.left;
  const viewportY = ev.clientY - rect.top;

  const geo = map.screenToGeo(viewportX, viewportY);

  const newMarker = new H.map.Marker(geo);
  map.addObject(newMarker);
});

Синхронизация drag с поведением карты

При активном перетаскивании объектов необходимо учитывать конфликт с перемещением самой карты. Для этого используется Behavior.

const beh * avior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

Можно временно блокировать перемещение карты:

behavior.disable(H.mapevents.Behavior.DRAGGING);

И включать обратно:

behavior.enable(H.mapevents.Behavior.DRAGGING);

Работа с кастомными объектами

Перетаскивание не ограничивается маркерами. Любой объект H.map.Object может быть расширен логикой drag.

const circle = new H.map.Circle(
  { lat: 52.52, lng: 13.405 },
  500
);

circle.draggable = true;
map.addObject(circle);

Далее требуется обработка событий на уровне карты:

map.addEventListener('dragstart', function (ev) {
  const target = ev.target;
  if (target instanceof H.map.Circle) {
    map.getViewModel().setLookAtData({
      position: target.getCenter()
    });
  }
});

Определение объекта под курсором

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

map.addEventListener('pointerdown', function (ev) {
  const target = ev.target;

  if (target && target.draggable) {
    console.log('Начато перемещение объекта');
  }
});

Drag с использованием пиксельного смещения

Иногда требуется сохранять смещение курсора относительно объекта:

let offset = { x: 0, y: 0 };

map.addEventListener('dragstart', function (ev) {
  const pointer = ev.currentPointer;
  const target = ev.target;

  const screen = map.geoToScreen(target.getGeometry());

  offset.x = pointer.viewportX - screen.x;
  offset.y = pointer.viewportY - screen.y;
});

При перемещении:

map.addEventListener('drag', function (ev) {
  const pointer = ev.currentPointer;

  const x = pointer.viewportX - offset.x;
  const y = pointer.viewportY - offset.y;

  const geo = map.screenToGeo(x, y);

  ev.target.setGeometry(geo);
});

Drag нескольких объектов

При массовом перемещении используется коллекция H.map.Group.

const group = new H.map.Group();

group.addObjects([
  new H.map.Marker({ lat: 52.52, lng: 13.4 }),
  new H.map.Marker({ lat: 52.53, lng: 13.41 })
]);

map.addObject(group);

Обработка перемещения:

group.addEventListener('drag', function (ev) {
  const target = ev.target;

  if (target instanceof H.map.Marker) {
    const pointer = ev.currentPointer;
    const geo = map.screenToGeo(pointer.viewportX, pointer.viewportY);
    target.setGeometry(geo);
  }
});

Ограничение области перетаскивания

Для контроля перемещения можно ограничивать координаты:

function clampGeo(geo) {
  return {
    lat: Math.max(-85, Math.min(85, geo.lat)),
    lng: Math.max(-180, Math.min(180, geo.lng))
  };
}

Применение:

map.addEventListener('drag', function (ev) {
  const pointer = ev.currentPointer;
  const geo = map.screenToGeo(pointer.viewportX, pointer.viewportY);

  ev.target.setGeometry(clampGeo(geo));
});

Практические сценарии использования drag & drop

Редактор точек интереса

Перетаскивание маркеров для точной установки POI с сохранением координат в базе данных.

Логистика и маршруты

Перемещение точек доставки с автоматическим пересчетом маршрута.

Геофенсинг

Перетаскивание границ зон на карте с обновлением геометрии полигона.

Пользовательские слои

Добавление объектов из интерфейса на карту через drag DOM-элементов с последующей геокодировкой.


Координация drag с производительностью

При интенсивном перемещении объектов важно снижать нагрузку:

  • ограничение частоты обновления drag через throttle
  • минимизация перерасчёта маршрутов
  • отключение лишних слоёв во время перетаскивания
  • использование requestAnimationFrame для синхронизации обновлений
let lastUpdate = 0;

map.addEventListener('drag', function (ev) {
  const now = performance.now();

  if (now - lastUpdate < 16) return;

  lastUpdate = now;

  const geo = map.screenToGeo(
    ev.currentPointer.viewportX,
    ev.currentPointer.viewportY
  );

  ev.target.setGeometry(geo);
});