Контролы в OpenLayers представляют собой самостоятельные UI-элементы,
встроенные в контейнер карты и связанные с её жизненным циклом. Базовая
точка расширения — класс ol/control/Control, который
инкапсулирует DOM-узел, позиционирование и привязку к экземпляру
карты.
Контрол отличается от взаимодействий (Interactions) тем, что не влияет напрямую на геометрию и события карты, а лишь предоставляет интерфейс управления или отображения состояния.
Любой пользовательский контрол строится вокруг наследования или
композиции Control. Основные элементы:
element)setMapПростейшая реализация:
import Control from 'ol/control/Control.js';
class SimpleButtonControl extends Control {
constructor(options = {}) {
const button = document.createElement('button');
button.innerHTML = 'Click';
const element = document.createElement('div');
element.className = 'custom-control ol-unselectable ol-control';
element.appendChild(button);
super({
element: element,
target: options.target
});
button.addEventListener('click', () => {
console.log('Control activated');
});
}
}
Ключевой момент: Control ожидает готовый DOM-элемент,
который он помещает в контейнер карты.
Контрол проходит несколько стадий:
DOM формируется до подключения к карте.
setMap)Метод вызывается автоматически при добавлении в
map.addControl().
setMap(map) {
super.setMap(map);
}
В этот момент можно подписываться на события карты:
this.getMap().on('moveend', () => {
console.log('Карта перемещена');
});
Контрол регистрируется через коллекцию controls:
import Map from 'ol/Map.js';
const map = new Map({
target: 'map',
controls: []
});
map.addControl(new SimpleButtonControl());
Контрол автоматически вставляется в контейнер
ol-overlaycontainer-stopevent.
Контролы часто отражают состояние карты: масштаб, центр, слой.
Пример контрола отображения координат курсора:
import Control from 'ol/control/Control.js';
class MousePositionControl extends Control {
constructor() {
const element = document.createElement('div');
element.className = 'mouse-position ol-control';
super({ element });
this.element = element;
}
setMap(map) {
super.setMap(map);
if (!map) return;
map.on('pointermove', (event) => {
const coord = event.coordinate;
this.element.innerHTML = coord
.map(c => c.toFixed(2))
.join(', ');
});
}
}
Контролы полностью зависят от CSS. OpenLayers использует классы:
ol-controlol-unselectableol-zoomol-rotateКастомный стиль:
.custom-control {
background: white;
padding: 6px;
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.custom-control button {
border: none;
background: #2a7ae2;
color: white;
padding: 4px 8px;
cursor: pointer;
}
Важно учитывать, что все контролы находятся поверх карты и должны
поддерживать pointer-events корректно.
Контрол может хранить внутреннее состояние и синхронизироваться с картой.
Пример переключателя слоя:
class LayerToggleControl extends Control {
constructor(layer) {
const button = document.createElement('button');
button.textContent = 'Toggle';
const element = document.createElement('div');
element.className = 'custom-control';
element.appendChild(button);
super({ element });
this.layer = layer;
this.visible = true;
button.addEventListener('click', () => {
this.visible = !this.visible;
this.layer.setVisible(this.visible);
});
}
}
Контролы часто зависят от событий:
moveendpointermovechange:resolutionchange:visibleПример масштаб-зависимого отображения:
setMap(map) {
super.setMap(map);
const view = map.getView();
const update = () => {
const zoom = view.getZoom();
this.element.innerHTML = `Zoom: ${zoom.toFixed(2)}`;
};
view.on('change:resolution', update);
update();
}
OpenLayers поддерживает стандартные позиции:
top-lefttop-rightbottom-leftbottom-rightЗадание позиции:
import Control from 'ol/control/Control.js';
const control = new Control({
element: document.createElement('div'),
target: undefined
});
// CSS класс позиции задаётся контейнером
control.element.className += ' ol-control ol-top-right';
Чаще позиционирование управляется через встроенные контролы или CSS контейнеры.
Контрол может содержать несколько элементов:
class ToolbarControl extends Control {
constructor() {
const element = document.createElement('div');
element.className = 'toolbar';
const zoomIn = document.createElement('button');
zoomIn.textContent = '+';
const zoomOut = document.createElement('button');
zoomOut.textContent = '-';
element.appendChild(zoomIn);
element.appendChild(zoomOut);
super({ element });
zoomIn.addEventListener('click', () => {
const view = this.getMap().getView();
view.setZoom(view.getZoom() + 1);
});
zoomOut.addEventListener('click', () => {
const view = this.getMap().getView();
view.setZoom(view.getZoom() - 1);
});
}
}
При удалении карты или контрола важно отписываться от событий:
setMap(map) {
if (this._listener && this.getMap()) {
this.getMap().un('moveend', this._listener);
}
super.setMap(map);
if (map) {
this._listener = () => console.log('moveend');
map.on('moveend', this._listener);
}
}
Без корректной очистки возможно накопление обработчиков и утечки памяти.
Контрол может синхронизироваться с внешним состоянием приложения:
Пример подписки на внешний источник:
class ExternalStateControl extends Control {
constructor(store) {
const element = document.createElement('div');
element.className = 'state-control';
super({ element });
this.store = store;
this.store.subscribe((state) => {
this.element.innerHTML = state.label;
});
}
}
Контролы не перерисовываются автоматически при ресайзе контейнера карты, поэтому иногда требуется ручное обновление:
window.addEventListener('resize', () => {
const map = this.getMap();
if (map) {
map.updateSize();
}
});
getMap()ViewКонтролы часто применяются для:
Каждый из этих сценариев требует разделения логики: UI внутри контрола, бизнес-логика вне его, взаимодействие через события или состояние приложения.