Архитектура визуализации в OpenLayers построена на разделении уровня данных и уровня отрисовки. Геометрии, источники и слои не привязаны к конкретному способу рендеринга, что позволяет переключать стратегии отображения без изменения бизнес-логики.
Ключевые рендереры:
<canvas>На уровне слоя выбор стратегии определяется параметрами
renderMode, типом слоя и внутренними реализациями
LayerRenderer.
Наиболее гибкий способ кастомизации без переопределения внутренних классов — использование события postrender. Оно позволяет выполнять произвольную отрисовку поверх уже готового слоя.
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import {fromLonLat} from 'ol/proj.js';
const layer = new VectorLayer({
source: new VectorSource()
});
layer.on('postrender', (event) => {
const context = event.context;
const size = event.frameState.size;
context.save();
context.fillStyle = 'rgba(255, 0, 0, 0.2)';
context.fillRect(0, 0, size[0], size[1]);
context.restore();
});
Ключевой момент — преобразование координат карты в пиксели:
import {toLonLat} from 'ol/proj.js';
layer.on('postrender', (event) => {
const ctx = event.context;
const frameState = event.frameState;
const pixelRatio = frameState.pixelRatio;
const coordinate = fromLonLat([37.6173, 55.7558]);
const pixel = event.map.getPixelFromCoordinate(coordinate);
ctx.beginPath();
ctx.arc(pixel[0] * pixelRatio, pixel[1] * pixelRatio, 8, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();
});
OpenLayers применяет матрицы трансформации для синхронизации мира и экрана:
layer.on('postrender', (event) => {
const ctx = event.context;
const transform = event.inversePixelTransform;
ctx.save();
ctx.setTransform(...transform);
ctx.fillStyle = 'rgba(0, 128, 255, 0.5)';
ctx.fillRect(0, 0, 1000000, 1000000);
ctx.restore();
});
VectorLayer поддерживает три режима:
vector — полная отрисовка векторных объектовimage — предварительный рендеринг в изображениеhybrid — смешанный режимconst layer = new VectorLayer({
source: new VectorSource(),
renderMode: 'image'
});
В режиме image происходит:
Это снижает нагрузку при больших наборах данных, но усложняет кастомный рендеринг, так как доступ к отдельным feature ограничивается итоговым изображением.
Более низкоуровневый подход — создание собственного рендерера слоя.
OpenLayers использует внутреннюю систему классов
LayerRenderer, которую можно расширять.
Рендерер отвечает за:
import BaseVectorLayer from 'ol/layer/BaseVector.js';
class CustomLayer extends BaseVectorLayer {
createRenderer() {
return new CustomRenderer(this);
}
}
class CustomRenderer {
constructor(layer) {
this.layer = layer;
}
prepareFrame(frameState) {
this.frameState = frameState;
return true;
}
renderFrame(frameState, target) {
const ctx = target.getContext('2d');
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, frameState.size[0], frameState.size[1]);
return target;
}
}
Рендерер участвует в следующих этапах:
prepareFrame — подготовка данныхrenderFrame — отрисовка кадраdispose — очистка ресурсовТакой подход используется для сложных визуализаций, включая тепловые карты и кастомные символические слои.
OpenLayers предоставляет WebGL-слои для высокопроизводительной визуализации больших массивов точек и линий.
Основной класс:
WebGLPointsLayerWebGLVectorLayer (в некоторых версиях)import WebGLPointsLayer from 'ol/layer/WebGLPoints.js';
const layer = new WebGLPointsLayer({
source: vectorSource,
style: {
'circle-radius': 6,
'circle-fill-color': 'rgba(255,0,0,0.6)'
}
});
WebGL-слои используют декларативную модель:
style: {
'circle-radius': ['interpolate', ['linear'], ['get', 'value'], 0, 3, 100, 12],
'circle-fill-color': [
'case',
['>', ['get', 'value'], 50],
'red',
'green'
]
}
При необходимости полного контроля подключаются GLSL-шейдеры:
style: {
symbol: {
vertexShader: `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}
`,
fragmentShader: `
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 0.7);
}
`
}
}
Такой подход используется для:
При кастомной отрисовке стандартная система выбора объектов может не работать автоматически.
map.on('click', (event) => {
const pixel = event.pixel;
layer.getFeaturesAtPixel(pixel, (feature) => {
console.log(feature);
});
});
При кастомном canvas-рендеринге требуется самостоятельная проверка:
layer.on('postrender', (event) => {
const ctx = event.context;
// отрисовка
});
И отдельная логика проверки попадания:
function isInsideCircle(pixel, center, radius) {
const dx = pixel[0] - center[0];
const dy = pixel[1] - center[1];
return dx * dx + dy * dy <= radius * radius;
}
При работе с большими сценами критичны следующие механизмы:
const pr = event.frameState.pixelRatio;
ctx.scale(pr, pr);
Игнорирование этого параметра приводит к размытию на Retina-дисплеях.
Используется для гибридной визуализации:
Подготовка изображения вне основного цикла:
const offscreen = document.createElement('canvas');
const ctx = offscreen.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);
OpenLayers использует систему frameState, которая
контролирует:
Принудительная перерисовка:
layer.changed();
map.render();
Кастомные рендереры должны синхронизироваться с:
map.on('moveend', () => {
layer.changed();
});
Слои рендерятся по z-index:
layer.setZIndex(10);
При кастомных рендерах порядок может быть критичен, особенно при
использовании postrender, где отрисовка происходит поверх
всех стандартных слоёв.