Система взаимодействий (Interactions) в OpenLayers построена как расширяемый слой обработки пользовательского ввода, отделённый от логики отображения слоёв и источников данных. Каждый interaction представляет собой модуль, который подключается к карте и перехватывает события мыши и клавиатуры, преобразуя их в действия над картой или геометрией.
Ключевая идея архитектуры заключается в том, что карта не содержит «жёстко встроенного поведения» — масштабирование, перемещение, выделение объектов реализованы через набор независимых взаимодействий, которые можно комбинировать, отключать или заменять.
Основные особенности:
ol.interactionpointer events)Любое взаимодействие в OpenLayers наследуется от базового класса
ol/interaction/Interaction.
Типовая структура кастомного взаимодействия:
import Interaction from 'ol/interaction/Interaction';
class CustomInteraction extends Interaction {
constructor(options = {}) {
super({
handleDownEvent: options.handleDownEvent,
handleDragEvent: options.handleDragEvent,
handleMoveEvent: options.handleMoveEvent,
handleUpEvent: options.handleUpEvent,
});
}
}
Внутренне Interaction работает через набор обработчиков событий:
handleDownEvent — нажатие кнопки мышиhandleDragEvent — перемещение при зажатой кнопкеhandleMoveEvent — движение курсораhandleUpEvent — отпускание кнопкиhandleClick — одиночный клик (опционально)Каждый из этих методов может возвращать true или
false, управляя тем, будет ли событие передано следующим
взаимодействиям.
Добавление кастомного поведения выполняется через коллекцию interactions карты:
import Map from 'ol/Map';
import View from 'ol/View';
const map = new Map({
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
}),
});
map.addInteraction(new CustomInteraction());
Порядок добавления имеет значение: взаимодействия обрабатываются последовательно, и некоторые могут блокировать события.
В основе кастомных взаимодействий лежит работа с
MapBrowserEvent. Этот объект содержит:
Пример использования:
handleMoveEvent(evt) {
const coordinate = evt.coordinate;
const pixel = evt.pixel;
console.log('Координаты:', coordinate);
console.log('Пиксель:', pixel);
}
Ключевой момент — разделение экранных координат и географических координат. Это позволяет строить взаимодействия, не зависящие от текущего масштаба карты.
Interaction часто реализует внутреннюю конечную машину состояний:
Пример:
class DragBoxInteraction extends Interaction {
constructor() {
super();
this.dragging = false;
this.startPixel = null;
}
handleDownEvent(evt) {
this.dragging = true;
this.startPixel = evt.pixel;
return true;
}
handleDragEvent(evt) {
if (!this.dragging) return;
const current = evt.pixel;
console.log('drag from', this.startPixel, 'to', current);
}
handleUpEvent() {
this.dragging = false;
this.startPixel = null;
return false;
}
}
Такой подход позволяет реализовывать сложные инструменты: выделение областей, измерения, рисование.
OpenLayers широко использует функции условий
(condition functions) для управления тем, когда interaction
активен.
Примеры стандартных условий:
singleClickdoubleClickshiftKeyOnlyaltKeyOnlyПример кастомной логики:
import {shiftKeyOnly} from 'ol/events/condition';
class ShiftDragInteraction extends Interaction {
handleDownEvent(evt) {
if (!shiftKeyOnly(evt)) {
return false;
}
this.active = true;
this.start = evt.coordinate;
return true;
}
}
Использование условий позволяет разделять поведение без усложнения логики самого interaction.
Одним из типичных сценариев является выделение feature под курсором.
import Sel ect from 'ol/interaction/Select';
const select = new Select({
condition: function (evt) {
return evt.type === 'pointermove';
},
});
Однако кастомная реализация даёт больше контроля:
import {getFeaturesAtPixel} fr om 'ol/render/canvas';
class HoverSelectInteraction extends Interaction {
handleMoveEvent(evt) {
const map = evt.map;
map.forEachFeatureAtPixel(evt.pixel, (feature, layer) => {
feature.set('hover', true);
});
}
}
Важный аспект — производительность. Постоянные запросы к
forEachFeatureAtPixel требуют оптимизации на больших
слоях.
Перетаскивание объектов — один из самых сложных типов взаимодействий.
Типовой алгоритм:
class MoveFeatureInteraction extends Interaction {
constructor() {
super();
this.feature = null;
this.startCoord = null;
}
handleDownEvent(evt) {
this.feature = evt.map.forEachFeatureAtPixel(
evt.pixel,
f => f
);
if (this.feature) {
this.startCoord = evt.coordinate;
return true;
}
return false;
}
handleDragEvent(evt) {
if (!this.feature) return;
const dx = evt.coordinate[0] - this.startCoord[0];
const dy = evt.coordinate[1] - this.startCoord[1];
const geometry = this.feature.getGeometry();
geometry.translate(dx, dy);
this.startCoord = evt.coordinate;
}
handleUpEvent() {
this.feature = null;
return false;
}
}
Interactions часто изменяют стиль курсора через DOM-элемент map viewport:
handleMoveEvent(evt) {
const element = evt.map.getTargetElement();
const hit = evt.map.hasFeatureAtPixel(evt.pixel);
element.style.cursor = hit ? 'pointer' : 'default';
}
Дополнительные состояния могут отображаться через overlay или изменение стилей feature.
Interaction может остановить дальнейшую обработку события:
handleDownEvent(evt) {
evt.originalEvent.preventDefault();
return true;
}
Возврат true означает, что событие «поглощено» текущим
interaction и не передаётся следующим обработчикам.
Это важно при построении:
Кастомные interactions часто взаимодействуют с
ol/geom:
PointLineStringPolygonПример модификации линии:
handleDragEvent(evt) {
const geometry = this.feature.getGeometry();
const coordinates = geometry.getCoordinates();
coordinates.push(evt.coordinate);
geometry.setCoordinates(coordinates);
}
При этом важно учитывать immutability подходы: в некоторых случаях лучше создавать новый массив координат вместо модификации существующего.
Критические аспекты:
handleMoveEventforEachFeatureAtPixelПример оптимизации:
handleMoveEvent(evt) {
if (!this._lastTime || Date.now() - this._lastTime > 16) {
this._lastTime = Date.now();
this.process(evt);
}
}
OpenLayers позволяет одновременно использовать несколько interactions:
DragPanMouseWheelZoomПорядок имеет значение:
map.addInteraction(new CustomInteraction());
map.addInteraction(new Select());
map.addInteraction(new DragPan());
В этом случае кастомный interaction может перехватывать события раньше стандартных.
Подходы к созданию сложных инструментов:
Наследование
Композиция
Пример композиции:
class DrawingTool {
constructor(map) {
this.map = map;
this.draw = new DrawInteraction();
this.modify = new ModifyInteraction();
map.addInteraction(this.draw);
map.addInteraction(this.modify);
}
}
OpenLayers абстрагирует различия между:
Все они приводятся к единому MapBrowserEventPointer.
Это позволяет кастомным interactions работать одинаково на:
Особое внимание уделяется:
Interaction может быть включён или выключен динамически:
interaction.setActive(false);
interaction.setActive(true);
Это используется для:
Interactions тесно связаны с состоянием Map:
Пример использования view в interaction:
handleDragEvent(evt) {
const view = evt.map.getView();
const resolution = view.getResolution();
console.log('resolution:', resolution);
}
Это позволяет адаптировать поведение к масштабу карты, например изменять чувствительность перетаскивания.