OpenLayers 10.9.0 渲染架构分析
使用AI对 OpenLayers 10.9.0渲染架构进行的分析,可以用来参考,便于理解(纯文字,按需食用)
目录
- 整体架构概览
- [渲染调度系统(Map 级)](#渲染调度系统(Map 级) "#2-%E6%B8%B2%E6%9F%93%E8%B0%83%E5%BA%A6%E7%B3%BB%E7%BB%9Fmap-%E7%BA%A7")
- 渲染器层次结构(renderer/)
- [Canvas 2D 渲染管线(render/canvas/)](#Canvas 2D 渲染管线(render/canvas/) "#4-canvas-2d-%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BFrendercanvas")
- [WebGL 渲染管线(render/webgl/)](#WebGL 渲染管线(render/webgl/) "#5-webgl-%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BFrenderwebgl")
- 矢量特征渲染流程(renderer/vector.js)
- [Canvas 共享与隔离机制](#Canvas 共享与隔离机制 "#7-canvas-%E5%85%B1%E4%BA%AB%E4%B8%8E%E9%9A%94%E7%A6%BB%E6%9C%BA%E5%88%B6")
- Declutter(避让)策略
- [Hit Detection(命中检测)](#Hit Detection(命中检测) "#9-hit-detection%E5%91%BD%E4%B8%AD%E6%A3%80%E6%B5%8B")
- 渲染事件系统
- 特殊渲染策略
1. 整体架构概览
OpenLayers 的渲染系统分为三个层次:
scss
┌─────────────────────────────────────────────────────────────┐
│ Map (调度层) │
│ render() → requestAnimationFrame → renderFrame_() │
│ 构建 FrameState,调度 CompositeMapRenderer │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ CompositeMapRenderer (合成层) │
│ 遍历图层 → 逐层调用 layer.render(frameState, target) │
│ 管理 DOM 元素(children_)、declutter 合成 │
└──────────────────────┬──────────────────────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌──────────────────────┐ ┌────────────────────────┐
│ Canvas 2D 渲染管线 │ │ WebGL 渲染管线 │
│ renderer/canvas/* │ │ renderer/webgl/* │
│ │ │ │
│ VectorLayer │ │ WebGLVectorLayer │
│ VectorImageLayer │ │ WebGLPointsLayer │
│ TileLayer │ │ Heatmap │
│ VectorTileLayer │ │ WebGLTileLayer │
│ ImageLayer │ │ FlowLayer │
└──────────────────────┘ └────────────────────────┘
核心目录结构
| 目录/文件 | 职责 |
|---|---|
Map.js |
渲染调度、FrameState 构建、动画循环 |
renderer/Map.js |
地图渲染器基类(抽象) |
renderer/Composite.js |
Canvas 地图渲染器,合成所有图层 |
renderer/Layer.js |
图层渲染器基类(抽象) |
renderer/canvas/ |
Canvas 2D 图层渲染器族 |
renderer/webgl/ |
WebGL 图层渲染器族 |
renderer/vector.js |
矢量特征渲染辅助(风格分派) |
render/ |
渲染基础设施(Builder/Executor/WebGL) |
2. 渲染调度系统(Map 级)
2.1 渲染调度流程
scss
用户操作 / 属性变化 / 瓦片加载完成
│
▼
Map.render() ← 调度入口
│ (guard: animationDelayKey_ === undefined)
▼
requestAnimationFrame(animationDelay_)
│ (下一个 async)
▼
animationDelay_()
│ 清除 animationDelayKey_
▼
renderFrame_(Date.now())
│
├── 1. 构建 FrameState
├── 2. renderer_.renderFrame(frameState)
├── 3. if frameState.animate → this.render() [循环]
├── 4. 累积 postRenderFunctions
├── 5. 派发 movestart/moveend 事件
├── 6. 派发 POSTRENDER 事件
├── 7. 计算 renderComplete_
└── 8. setTimeout(0) → handlePostRender()
│
├── 瓦片队列管理
├── 派发 RENDERCOMPLETE / LOADSTART / LOADEND
└── 执行 postRenderFunctions
源码位置 : Map.js
2.2 关键方法
| 方法 | 位置 | 职责 |
|---|---|---|
render() |
Map.js:1537 |
通过 requestAnimationFrame 调度下一帧 |
renderSync() |
Map.js:1510 |
取消 RAF,立即同步渲染 |
animationDelay_() |
Map.js:1501 |
RAF 回调,调用 renderFrame_ |
renderFrame_(time) |
Map.js:1600 |
核心渲染方法,构建 FrameState 并委托渲染器 |
handlePostRender() |
Map.js:1225 |
后处理:瓦片加载、事件派发 |
redrawText() |
Map.js:1520 |
字体加载后重绘所有图层文字 |
2.3 FrameState 结构
FrameState 是每帧渲染的完整状态快照,是一个纯数据对象:
javascript
frameState = {
animate: false, // 是否继续动画
coordinateToPixelTransform, // 坐标→像素变换矩阵
declutter: null, // 避让数据 { groupName: RBush }
extent: [...], // 当前视图范围
nextExtent: [...], // 动画插值的下一帧范围
index: frameIndex++, // 单调递增帧序号
layerIndex: 0, // 当前图层索引
layerStatesArray: [...], // 所有图层状态
pixelRatio: 1, // 设备像素比
pixelToCoordinateTransform, // 像素→坐标变换矩阵
postRenderFunctions: [], // 帧后回调队列
size: [width, height], // 地图视口尺寸
tileQueue: TileQueue, // 瓦片优先级队列
time: timestamp, // 帧时间戳
usedTiles: {}, // 本帧已使用瓦片
viewState: { center, resolution, rotation, projection, zoom, ... },
viewHints: { animating, interacting }, // 视图状态提示
wantedTiles: {}, // 本帧期望瓦片
mapId: uid, // 地图唯一标识
renderTargets: {}, // 已渲染元素标识
};
2.4 瓦片队列策略
handlePostRender() 中的瓦片加载策略 (Map.js:1237-1255):
| 状态 | maxTotalLoading | maxNewLoads |
|---|---|---|
| 静止 | maxTilesLoading_(默认较大) |
maxTilesLoading_ |
| 动画/交互中 | 8 | 2 |
| 动画/交互 + 帧超时(>8ms) | 0 | 0 |
这意味着在快速平移/缩放时,瓦片加载被大幅限制以保持帧率流畅。
3. 渲染器层次结构(renderer/)
3.1 类继承体系
scss
MapRenderer (renderer/Map.js) ← 抽象基类
└── CompositeMapRenderer (renderer/Composite.js) ← 唯一实现(Canvas 合成)
LayerRenderer (renderer/Layer.js) ← 抽象基类
├── CanvasLayerRenderer (renderer/canvas/Layer.js) ← Canvas 2D 基类
│ ├── CanvasVectorLayerRenderer ← 矢量图层
│ ├── CanvasVectorImageLayerRenderer ← 矢量图像图层
│ ├── CanvasTileLayerRenderer ← 瓦片图层
│ ├── CanvasVectorTileLayerRenderer ← 矢量瓦片图层
│ └── CanvasImageLayerRenderer ← 图片图层
│
└── WebGLLayerRenderer (renderer/webgl/Layer.js) ← WebGL 基类
├── WebGLVectorLayerRenderer ← WebGL 矢量
├── WebGLPointsLayerRenderer ← WebGL 点(已弃用)
├── WebGLTileLayerRenderer ← WebGL 瓦片
├── WebGLVectorTileLayerRenderer ← WebGL 矢量瓦片
└── FlowLayerRenderer ← 粒子流场
3.2 LayerRenderer 核心接口
每个图层渲染器必须实现 (renderer/Layer.js):
| 方法 | 职责 |
|---|---|
prepareFrame(frameState) |
判断本帧是否需要渲染 |
renderFrame(frameState, target) |
执行渲染,返回 DOM 元素 |
renderDeferred(frameState) |
延迟渲染(用于 declutter) |
forEachFeatureAtCoordinate(...) |
命中检测 |
getFeatures(pixel) |
异步命中检测 |
3.3 CompositeMapRenderer --- 帧合成流程
renderer/Composite.js:101-204:
javascript
renderFrame(frameState) {
this.calculateMatrices2D(frameState);
this.dispatchRenderEvent(PRECOMPOSE, frameState);
// 按 zIndex 排序图层
const layerStatesArray = frameState.layerStatesArray.sort(
(a, b) => a.zIndex - b.zIndex,
);
// 检测是否需要 declutter
const declutter = layerStatesArray.some(
(layerState) => layerState.layer instanceof BaseVectorLayer
&& layerState.layer.getDeclutter(),
);
if (declutter) frameState.declutter = {};
// 逐层渲染
this.children_ = [];
let previousElement = null;
for (let i = 0; i < layerStatesArray.length; i++) {
const element = layer.render(frameState, previousElement);
if (element !== previousElement) {
this.children_.push(element); // 不同的 canvas 才推入
previousElement = element;
}
}
// Declutter 合成
this.declutter(frameState, renderedLayerStates);
// 更新 DOM
replaceChildren(this.element_, this.children_);
}
核心设计 : 将前一个图层的渲染结果 (previousElement) 传给下一个图层,使其有机会复用 canvas 容器。
4. Canvas 2D 渲染管线(render/canvas/)
这是 OpenLayers 最复杂、最成熟的渲染管线,采用 记录-回放(Record-Replay) 模式。
4.1 架构图
scss
┌──────────────┐
│ Feature │
│ + Style │
└──────┬───────┘
│
renderer/vector.js
(按几何类型分派)
│
┌──────────────▼──────────────┐
│ BuilderGroup │
│ (按 zIndex × builderType │
│ 管理 Builder 实例) │
│ │
│ getBuilder(zIndex, type) │
└──────────────┬───────────────┘
│
┌────────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌────────────────┐ ┌──────────────┐
│ PolygonBuilder │ │LineStringBuilder│ │ ImageBuilder │ ...
│ (drawPolygon) │ │(drawLineString) │ │(drawPoint) │
│ │ │ │ │ │
│ 产出: │ │ 产出: │ │ 产出: │
│ instructions[]│ │ instructions[] │ │ instructions[]│
│ coordinates[] │ │ coordinates[] │ │ coordinates[] │
└───────┬───────┘ └───────┬────────┘ └──────┬───────┘
│ │ │
└──────────────────┼───────────────────┘
│
BuilderGroup.finish()
返回 allInstructions:
{ zIndex → { type → SerializableInstructions } }
│
┌────────────▼────────────┐
│ ExecutorGroup │
│ 为每个 (zIndex, type) │
│ 创建 Executor 实例 │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Executor │
│ 指令解释器 (VM) │
│ │
│ execute(context, ...) │
│ ↓ switch 指令码: │
│ BEGIN_GEOMETRY │
│ BEGIN_PATH → ctx.beginPath() │
│ MOVE_TO_LINE_TO → ctx.moveTo() │
│ CIRCLE → ctx.arc() │
│ SET_FILL_STYLE → ctx.fillStyle= │
│ FILL → ctx.fill() │
│ SET_STROKE_STYLE → ... │
│ STROKE → ctx.stroke() │
│ DRAW_IMAGE → ctx.drawImage() │
│ DRAW_CHARS → 文字渲染 │
│ END_GEOMETRY │
└─────────────────────────┘
4.2 Instruction --- 指令集
render/canvas/Instruction.js 定义了 13 个指令码:
| 操作码 | 名称 | Canvas 2D 调用 |
|---|---|---|
| 0 | BEGIN_GEOMETRY |
标记几何体开始(携带 feature、geometry) |
| 1 | BEGIN_PATH |
context.beginPath() |
| 2 | CIRCLE |
context.arc(...) |
| 3 | CLOSE_PATH |
context.closePath() |
| 4 | CUSTOM |
调用用户自定义渲染函数 |
| 5 | DRAW_CHARS |
沿线绘制文字 |
| 6 | DRAW_IMAGE |
绘制图标/文字标签 |
| 7 | END_GEOMETRY |
标记几何体结束(触发命中回调) |
| 8 | FILL |
context.fill() |
| 9 | MOVE_TO_LINE_TO |
context.moveTo() + context.lineTo() |
| 10 | SET_FILL_STYLE |
设置 context.fillStyle |
| 11 | SET_STROKE_STYLE |
设置描边属性 |
| 12 | STROKE |
context.stroke() |
4.3 Builder --- 指令生产者
类层次:
scss
VectorContext (抽象基类)
└── CanvasBuilder (render/canvas/Builder.js)
├── PolygonBuilder → 处理 Circle + Polygon
├── LineStringBuilder → 处理 LineString
├── ImageBuilder → 处理图标/点符号
└── TextBuilder → 处理文字标签
Builder 不直接绘制,而是将几何数据和样式转化为指令数组:
javascript
// PolygonBuilder.drawPolygon 的核心逻辑(简化)
drawPolygon(polygonGeometry, feature, index) {
// 1. 记录 BEGIN_GEOMETRY 指令
this.beginGeometry(geometry, feature, index);
// 2. 设置填充/描边样式(仅在样式变化时记录)
this.updateFillStyle(state, this.createFill);
this.updateStrokeStyle(state, this.applyStroke);
// 3. 记录路径指令
this.instructions.push(BEGIN_PATH);
// ... 坐标追加到 this.coordinates[],记录 MOVE_TO_LINE_TO
this.instructions.push(CLOSE_PATH);
// 4. 记录绘制指令
this.instructions.push(...fillInstruction); // 或 strokeInstruction
// 5. 记录 END_GEOMETRY
this.endGeometry(feature);
}
关键设计 : 坐标存储在扁平数组 this.coordinates[] 中,指令通过索引引用坐标段,而非内嵌坐标。这避免了数据重复。
Builder.finish() 返回:
javascript
{
instructions: number[], // 主渲染指令
hitDetectionInstructions: number[], // 命中检测指令(独立的)
coordinates: number[], // 扁平坐标数组
textStates: {}, // 文字状态缓存
fillStates: {}, // 填充状态缓存
strokeStates: {}, // 描边状态缓存
}
4.4 BuilderGroup --- 按 zIndex × 类型组织 Builder
javascript
// render/canvas/BuilderGroup.js
this.buildersByZIndex_ = {
// zIndex → { builderType → Builder }
0: {
'Polygon': PolygonBuilder,
'LineString': LineStringBuilder,
'Image': ImageBuilder,
'Text': TextBuilder,
'Circle': PolygonBuilder,
},
1: { ... },
};
4.5 Executor --- 指令解释器(虚拟机)
render/canvas/Executor.js 的 execute_() 方法是整个 Canvas 2D 渲染管线的核心------一个巨大的 switch 语句解释指令数组:
性能优化:
| 优化策略 | 说明 |
|---|---|
| 坐标缓存 | pixelCoordinates_ 缓存变换后的像素坐标,仅在 transform 变化时重新计算 |
| 批量 fill/stroke | 当 overlaps=false 时,连续的 FILL/STROKE 指令批量执行(最多 200 次),减少上下文切换 |
| 文字标签缓存 | labels_ 缓存预渲染到 offscreen canvas 的文字标签 |
| 样式去重 | 仅在样式确实变化时才调用 SET_FILL_STYLE / SET_STROKE_STYLE |
4.6 ExecutorGroup --- 执行协调器
javascript
// render/canvas/ExecutorGroup.js
execute(targetContext, scaledCanvasSize, transform, viewRotation, snapToPixel, builderTypes, declutterTree) {
// 1. 按 zIndex 排序
// 2. 遍历每个 zIndex 下的每个 builderType
// 3. 如果启用 deferred:
// - Polygon/LineString/Circle/Default → 推入 ZIndexContext(延迟执行)
// - Image/Text → 立即执行(需要 declutter 信息)
// 如果不启用 deferred:
// - 所有类型直接执行
}
4.7 ZIndexContext --- 延迟渲染代理
render/canvas/ZIndexContext.js 使用 JavaScript Proxy 拦截所有 Canvas 2D API 调用:
javascript
// 核心机制
const proxy = new Proxy(this, {
get: (target, property) => {
return function(...args) {
target.instructions_.push({ property, args });
};
},
set: (target, property, value) => {
target.instructions_.push({ property, value });
return true;
}
});
后续调用 zIndexContext.draw(realContext) 时按序回放到真实 canvas 上。
4.8 CanvasImmediateRenderer --- 直绘模式
render/canvas/Immediate.js 继承 VectorContext,但完全绕过 Builder/Executor 管线。每个 drawXxx 调用直接发出 Canvas 2D API 命令。
用于:
- render 事件回调中用户自定义绘制
- hit detection 中的即时渲染
5. WebGL 渲染管线(render/webgl/)
5.1 架构对比
| 方面 | Canvas 2D | WebGL |
|---|---|---|
| 渲染模式 | 指令记录-回放 | GPU 批处理 |
| 坐标处理 | CPU 变换 | GPU 顶点着色器 |
| 批处理单位 | zIndex × builderType | 几何类型(point/line/polygon) |
| 缓冲区 | JS 数组 | WebGL Buffer (GPU) |
| 着色 | Canvas 2D API | GLSL 着色器 |
| 命中检测 | 小 canvas + 像素回读 | WebGL RenderTarget + 像素回读 |
| Worker | 无 | Web Worker 异步生成缓冲区 |
5.2 核心类
scss
WebGLLayerRenderer (renderer/webgl/Layer.js)
├── 管理 WebGLHelper
├── canvas 分组(按 className)
└── prepareFrame / renderFrame 生命周期
│
├── WebGLVectorLayerRenderer
│ ├── MixedGeometryBatch (feature → 扁平坐标)
│ └── VectorStyleRenderer (style → shader → buffer → draw)
│
├── WebGLPointsLayerRenderer (已弃用)
│ └── Web Worker 生成点缓冲区
│
└── WebGLTileLayerRenderer
└── 纹理贴图 + 着色器
5.3 VectorStyleRenderer --- WebGL 矢量渲染核心
scss
Feature + FlatStyle
│
▼
MixedGeometryBatch.addFeature()
→ 按几何类型分到 pointBatch / lineStringBatch / polygonBatch
→ 存储扁平坐标
│
▼
VectorStyleRenderer.generateBuffers(batch, transform)
→ generateRenderInstructions_() → Float32Array
→ postMessage 到 Web Worker
→ Worker 生成 WebGL buffers
→ 返回 WebGLBuffers { pointBuffers, lineStringBuffers, polygonBuffers }
│
▼
VectorStyleRenderer.render(buffers, frameState, callback)
→ 遍历 renderPasses
→ 绑定 buffer → enable attributes → drawElements
5.4 WebGL Helper --- 上下文管理
webgl/Helper.js 负责:
- WebGL 上下文创建与缓存(通过
canvasCacheKey共享) - Shader 编译与 program 管理
- Uniform 设置
- 帧缓冲操作
WebGL canvas 的分组逻辑在 renderer/webgl/Layer.js:144-191:相邻的同 className WebGL 图层共享 canvas。
6. 矢量特征渲染流程(renderer/vector.js)
renderer/vector.js 是 Canvas 2D 管线中 Feature → Builder 的桥梁:
scss
renderFeature(builderGroup, feature, style, ...)
│
├── 获取 geometry(通过 style.getGeometryFunction())
├── 简化 geometry(simplifyTransformed)
│
├── 如果有自定义 renderer:
│ GEOMETRY_RENDERERS[type] → 按类型分派
│
└── GEOMETRY_RENDERERS 映射:
Point → renderPointGeometry()
LineString → renderLineStringGeometry()
Polygon → renderPolygonGeometry()
MultiPoint → renderMultiPointGeometry()
MultiLineString → renderMultiLineStringGeometry()
MultiPolygon → renderMultiPolygonGeometry()
Circle → renderCircleGeometry()
GeometryCollection → renderGeometryCollectionGeometry()
每种几何类型渲染函数的核心模式:
javascript
function renderPolygonGeometry(builderGroup, geometry, style, feature, index) {
// 1. 获取填充/描边样式
const fillStyle = style.getFill();
const strokeStyle = style.getStroke();
// 2. 获取对应 zIndex 的 Builder
const polygonReplay = builderGroup.getBuilder(style.getZIndex(), 'Polygon');
// 3. 设置样式(产生 SET_FILL_STYLE / SET_STROKE_STYLE 指令)
polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
// 4. 绘制几何(产生 BEGIN_PATH / MOVE_TO_LINE_TO / CLOSE_PATH / FILL 等指令)
polygonReplay.drawPolygon(geometry, feature, index);
// 5. 如果有文字标签
if (textStyle && textStyle.getText()) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), 'Text');
textReplay.setTextStyle(textStyle);
textReplay.drawText(geometry, feature, index);
}
}
7. Canvas 共享与隔离机制
7.1 决策点: useContainer()
renderer/canvas/Layer.js:160-219:
javascript
useContainer(target, transform, backgroundColor) {
const layerClassName = this.getLayer().getClassName();
// 复用条件 1: className 相同
// 复用条件 2: backgroundColor 匹配
// 复用条件 3: canvas transform 等价
if (条件全部满足) {
this.container = target;
this.context = context;
this.containerReused = true; // 复用!
}
if (!this.container) {
// 创建全新的 <div> + <canvas>
container = document.createElement('div');
context = createCanvasContext2D();
container.appendChild(context.canvas);
}
}
7.2 复用链
scss
Composite.renderFrame() 中:
previousElement = null
Layer 0: render(frameState, null)
→ useContainer(null, ...) → 创建新 canvas
→ 返回 element_0
→ previousElement = element_0
Layer 1: render(frameState, element_0)
→ useContainer(element_0, ...)
→ 如果 className/transform/background 一致 → 复用 canvas
→ 否则创建新 canvas
→ 返回 element_1
→ if (element_1 !== previousElement) children_.push(element_1)
7.3 强制分离方法
| 方法 | 原理 | 副作用 |
|---|---|---|
不同 className |
target.className !== layerClassName |
仅 CSS class 不同 |
不同 background |
backgroundColor 不匹配 | 渲染背景色层 |
| WebGL 渲染器 | 完全不同的渲染管线 | 需使用 flat style |
opacity < 1 |
创建临时 canvas 再合成 | 性能开销 |
8. Declutter(避让)策略
8.1 触发条件
renderer/Composite.js:116-124:
javascript
const declutter = layerStatesArray.some(
(layerState) => layerState.layer instanceof BaseVectorLayer
&& layerState.layer.getDeclutter(),
);
if (declutter) {
frameState.declutter = {};
}
8.2 分组机制
layer/BaseVector.js:111:
javascript
this.declutter_ = options.declutter ? String(options.declutter) : undefined;
- 相同
declutter值的图层共享同一个 RBush 树,进行跨图层避让 - 不同
declutter值的图层各自独立避让 declutter值可以是true、字符串或数字(内部统一转为字符串)
8.3 执行流程
scss
CompositeMapRenderer.declutter(frameState, layerStates)
│
│ // 第一遍:渲染 declutter 项(从顶层到底层)
├── for i = layerStates.length-1 → 0:
│ if layer.getDeclutter():
│ frameState.declutter[group] = new RBush(9) // 如果不存在
│ layer.renderDeclutter(frameState, layerState)
│ → ExecutorGroup.execute(..., DECLUTTER, declutterTree)
│ → Executor 只执行 Image/Text 指令
│ → 每个 image/text 检查 RBush 碰撞
│ → 不碰撞则加入 RBush 并渲染,碰撞则跳过
│
│ // 第二遍:渲染非 declutter 项 + 延迟渲染
└── layerStates.forEach(layerState =>
layerState.layer.renderDeferred(frameState)
)
→ ExecutorGroup.renderDeferred()
→ ZIndexContext.draw(realContext) // 回放延迟的 Canvas 调用
8.4 延迟渲染的作用
当 declutter 启用时,渲染管线变为两阶段:
- 记录阶段 :Polygon/LineString 的渲染不直接调用 Canvas API,而是通过
ZIndexContext记录 - 回放阶段 :
renderDeferred()时按 zIndex 排序后统一回放到真实 canvas
这确保了即使 polygon/line 在底层渲染,它们的 z-index 也能正确覆盖。
9. Hit Detection(命中检测)
OpenLayers 有三种命中检测机制:
9.1 Canvas 2D --- 小 canvas 回读法
render/canvas/ExecutorGroup.js:forEachFeatureAtCoordinate():
markdown
1. 创建小 canvas (size = hitTolerance × 2 + 1)
2. 遍历 zIndex(从高到低)和 builderType(从高到低)
3. 调用 executor.executeHitDetection() 渲染到小 canvas
4. 读取 ImageData,检查 alpha 通道
5. 第一个 alpha > 0 的像素对应的 feature 即命中
9.2 Canvas 2D --- ImageData 颜色编码法
render/canvas/hitdetect.js:createHitDetectionImageData():
ini
1. 为每个 feature 分配唯一颜色索引:
indexFactor = (256³ - 1) / featureCount
color = (i * indexFactor).toString(16)
2. 用 CanvasImmediateRenderer 以纯色渲染所有 feature
3. 点击时读取像素颜色,反算 feature 索引
9.3 WebGL --- RenderTarget 像素回读
renderer/webgl/VectorLayer.js:468-496:
csharp
1. 每帧额外渲染一次到 hitRenderTarget_(半分辨率)
2. 渲染时每个 feature 用唯一颜色标识
3. 点击时读取 RenderTarget 像素
4. 颜色 → ref → MixedGeometryBatch.getFeatureFromRef(ref)
10. 渲染事件系统
10.1 事件类型
render/EventType.js:
| 事件 | 触发者 | context | 用途 |
|---|---|---|---|
PRECOMPOSE |
Map | 无 | 所有图层合成前 |
POSTCOMPOSE |
Map | 无 | 所有图层合成后 |
PRERENDER |
Layer | Canvas2D/WebGL | 单个图层渲染前 |
POSTRENDER |
Layer | Canvas2D/WebGL | 单个图层渲染后 |
RENDERCOMPLETE |
Map | 无 | 所有瓦片加载完成,渲染完毕 |
10.2 RenderEvent 结构
javascript
{
type: 'prerender' | 'postrender' | 'precompose' | 'postcompose' | 'rendercomplete',
inversePixelTransform: Transform, // CSS 像素 → 渲染像素
frameState: FrameState, // 当前帧状态
context: CanvasRenderingContext2D // 或 WebGLRenderingContext
| undefined, // Map 级事件无 context
}
10.3 事件时机(有 declutter 时)
scss
renderFrame_() 开始
│
├── PRECOMPOSE (Map 级)
│
├── Layer PRERENDER(延迟,在 renderDeferred 中触发)
├── Layer 渲染(ZIndexContext 记录)
├── Layer POSTRENDER(延迟)
│
├── Declutter 第一遍:Image/Text
├── Declutter 第二遍:
│ ├── PRERENDER(真正触发)
│ ├── ZIndexContext.draw() → Polygon/LineString 回放
│ ├── POSTRENDER(真正触发)
│
├── POSTCOMPOSE (Map 级)
└── POSTRENDER (Map 级)
10.4 RENDERCOMPLETE 触发条件
javascript
// Map.js:1692-1698
this.renderComplete_ =
(this.hasListener(LOADSTART) || this.hasListener(LOADEND) || this.hasListener(RENDERCOMPLETE))
&& !this.tileQueue_.getTilesLoading() // 无瓦片在加载
&& !this.tileQueue_.getCount() // 无瓦片在队列
&& !this.getLoadingOrNotReady(); // 所有渲染器和数据源就绪
11. 特殊渲染策略
11.1 VectorImageLayer --- 矢量栅格化策略
renderer/canvas/VectorImageLayer.js:
markdown
原理:将矢量数据先渲染到 offscreen canvas,再作为图像图层显示
1. prepareFrame() 中:
- 创建放大的 frameState(extent × imageRatio)
- 委托 CanvasVectorLayerRenderer 渲染到独立 canvas
- 包装为 ImageCanvas 对象
2. renderFrame() 中:
- 作为普通 ImageLayer 渲染(drawImage 到主 canvas)
优点:动画和交互时性能好(复用缓存的栅格图像)
缺点:静止后才更新,视觉有延迟
11.2 几何简化策略
renderer/vector.js:64-66:
javascript
const SIMPLIFY_TOLERANCE = 0.5; // 设备像素容差
function getTolerance(resolution, pixelRatio) {
return (SIMPLIFY_TOLERANCE * resolution) / pixelRatio;
}
在 renderFeatureInternal() 中:
javascript
const simplifiedGeometry = geometry.simplifyTransformed(squaredTolerance, transform);
几何简化在像素级别进行------0.5 像素以下的细节被忽略,减少 GPU/CPU 绘制负担。
11.3 动画期间的渲染优化
javascript
// CanvasVectorLayerRenderer.prepareFrame()
if ((this.ready && !updateWhileAnimating && animating) ||
(!updateWhileInteracting && interacting)) {
this.animatingOrInteracting_ = true;
return true; // 跳过 Builder 重建,复用上一帧的指令
}
动画/交互期间,如果不设置 updateWhileAnimating / updateWhileInteracting,矢量图层会复用上一帧的渲染指令(replayGroup),避免每帧重建 Builder。
11.4 Opacity 离屏渲染
javascript
// CanvasVectorLayerRenderer.setDrawContext_()
if (this.opacity_ !== 1) {
this.targetContext_ = this.context; // 保存主 canvas
this.context = createCanvasContext2D(width, height); // 创建临时 canvas
}
// 渲染完成后
resetDrawContext_() {
this.targetContext_.globalAlpha = this.opacity_;
this.targetContext_.drawImage(this.context.canvas, 0, 0); // 合成到主 canvas
releaseCanvas(this.context); // 归还临时 canvas
canvasPool.push(this.context.canvas);
}
11.5 瓦片加载优先级
javascript
// Map.js handlePostRender()
if (animatingOrInteracting) {
const lowOnFrameBudget = Date.now() - frameState.time > 8;
maxTotalLoading = lowOnFrameBudget ? 0 : 8;
maxNewLoads = lowOnFrameBudget ? 0 : 2;
}
- 静止时:大量加载瓦片
- 动画/交互时:限制到最多 8 个并发加载、2 个新加载
- 帧预算超 8ms 时:完全停止瓦片加载,保证流畅
11.6 Icon 缓存过期
javascript
// renderer/Map.js:scheduleExpireIconCache()
if (iconImageCache.canExpireCache()) {
frameState.postRenderFunctions.push(expireIconCache);
}
图标缓存在每帧结束后检查并清理过期条目,防止内存泄漏。
总结:OpenLayers 渲染设计的核心思想
| 设计原则 | 实现方式 |
|---|---|
| 关注点分离 | Map(调度)→ CompositeMapRenderer(合成)→ LayerRenderer(单层渲染)→ Builder/Executor(指令化) |
| 延迟求值 | Builder 只记录指令,Executor 按需执行;ZIndexContext 延迟回放 |
| 缓存复用 | 坐标变换缓存、样式去重、文字标签缓存、canvas 池(canvasPool)、replayGroup 帧间复用 |
| 分层批处理 | 按 zIndex × 几何类型分组,批量 fill/stroke 减少 canvas 状态切换 |
| 性能自适应 | 动画/交互时跳过重建、限制瓦片加载、几何简化 |
| 双渲染管线 | Canvas 2D(功能完整、兼容性好)和 WebGL(高性能 GPU 加速) |