CanvasLayerRenderer类
# 概述
本文主要介绍CanvasLayerRenderer
类的实现,该类是创建矢量、瓦片、矢量瓦片、矢量图和静态图像图层渲染器的基类。
# 源码剖析
CanvasLayerRenderer
类是继承于LayerRenderer
类,其构造函数接受一个参数layer
,然后将其传给父类,定义了如下变量:
this.container
:地图容器this.renderedResolution
:渲染后的分辨率this.tempTransform
:临时转换函数,通过createTransform()
创建pixelTransform
:像素转换方法,渲染像素到视口CSS
像素的转换。inversePixelTransform
:视口CSS
像素到渲染像素的转换方法this.context
:canvas
的上下文deferredContext_
:延迟(渲染)的上下文this.containerReused
:表示地图元素是否重复利用,初始值为false
this.frameState
:帧状态
# createTransform
方法
createTransform
方法本质上就是返回一个一维数组(矩阵),如下所示;
export function create() {
return [1, 0, 0, 1, 0, 0];
}
2
3
# CanvasLayerRenderer
类中的方法
getImageData
方法:通过行列号col
、row
获取图像中对应位置的数据,主要是通过canvas
的getImageData
方法实现,这个可以参考getBackground
方法:通过参数frameState
帧状态获取背景background
,返回颜色字符串或者undefined
useContainer
方法
其实现如下:
useContainer(target, transform, backgroundColor) {
const layerClassName = this.getLayer().getClassName();
let container, context;
if (
target &&
target.className === layerClassName &&
(!backgroundColor ||
(target &&
target.style.backgroundColor &&
equals(
asArray(target.style.backgroundColor),
asArray(backgroundColor),
)))
) {
const canvas = target.firstElementChild;
if (canvas instanceof HTMLCanvasElement) {
context = canvas.getContext('2d');
}
}
if (context && context.canvas.style.transform === transform) {
// Container of the previous layer renderer can be used.
this.container = target;
this.context = context;
this.containerReused = true;
} else if (this.containerReused) {
// Previously reused container cannot be used any more.
this.container = null;
this.context = null;
this.containerReused = false;
} else if (this.container) {
this.container.style.backgroundColor = null;
}
if (!this.container) {
container = document.createElement('div');
container.className = layerClassName;
let style = container.style;
style.position = 'absolute';
style.width = '100%';
style.height = '100%';
context = createCanvasContext2D();
const canvas = context.canvas;
container.appendChild(canvas);
style = canvas.style;
style.position = 'absolute';
style.left = '0';
style.transformOrigin = 'top left';
this.container = container;
this.context = context;
}
if (
!this.containerReused &&
backgroundColor &&
!this.container.style.backgroundColor
) {
this.container.style.backgroundColor = backgroundColor;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
useContainer
方法接受三个参数:target
目标容器、transform
属性、backgroundColor
背景。
useContainer
方法就是获取(或者叫设置)渲染容器,先是获取图层的className
,如果图层的className
和target
的className
是一致,然后二者的背景色一致,那么就获取target
的第一个子元素,如果其元素类型是canvas
,则设置变量context
的值;然后再根据context
判断元素的transform
属性值,若它和参数transform
相等,则设置this.container
、this.context
的值,修改this.containerReused
为true
;若它们的transform
不等,则判断this.containerReused
,若其值为true
,则不渲染该图层,重置this.context
等变量;然后判断this.container
的值,重置背景.
后面useContainer
还进行一个判断,若container
不存在,则创建一个DOM
元素,设置其样式以及设置this.context
等值;最后若参数backgroundColor
存在且容器没有渲染过,容器背景样式也不存在,就设置容器的背景样式;
clipUnrotated
方法:接受三个参数:context
上下文,frameState
帧状态和extent
边界范围;该方法就是对边界范围采用canvas
的clip
方法进行裁剪范围,在裁剪之前经过两次的坐标转换,两次的transform
分别为frameState.coordinateToPixelTransform
和this.inversePixelTransform
prepareContainer
方法:顾名思义就是预设container
,接受两个参数frameState
、target
;获取frameState
的范围、分辨率、旋转角度以及像素比,然后通过一些列的矩阵变化计算得到canvasTransform
,然后调用useContainer
方法,最后设置canvas
的宽高dispatchRenderEvent_
方法dispatchRenderEvent
方法接受三个参数:type
类型、context
上下文和frameState
帧状态。方法内部先是通过getLayer
获取图层,然后判断图层是否注册了type
类的监听事件,若注册了,则实例化RenderEvent
类,(RenderEvent
类就是基于Event
类扩展了几个参数)然后调用layer.dispatchEvent
方法派发事件,其实现如下:
dispatchRenderEvent_(type, context, frameState) {
const layer = this.getLayer();
if (layer.hasListener(type)) {
const event = new RenderEvent(
type,
this.inversePixelTransform,
frameState,
context,
);
layer.dispatchEvent(event);
}
}
2
3
4
5
6
7
8
9
10
11
12
preRender
和postRender
方法:内部都是调用dispatchRenderEvent_
进行事件派发renderDeferredInternal
方法:未实现,内部渲染延迟方法getRenderContext
方法:获取渲染上下文,接受frameState
作为参数,若帧状态的declutter
存在但是去杂上下文this.deferredContext_
不存在,则实例化ZIndexContext
类,最后判断帧状态的declutter
是否存在,若存在则获取this.deferredContext
的上下文,否则返回this.context
renderDeferred
方法:延迟渲染方法主要用于 Openlayers 的去杂操作getRenderTransform
方法:根据中心点、宽高、偏移、像素比等,获取一个transform
作用于元素
# CanvasLayerRenderer
的完整实现如下
class CanvasLayerRenderer extends LayerRenderer {
constructor(layer) {
super(layer);
this.container = null;
this.renderedResolution;
this.tempTransform = createTransform();
this.inversePixelTransform = createTransform();
this.context = null;
this.deferredContext_ = null;
this.containerReused = false;
this.frameState = null;
}
getImageData(image, col, row) {
if (!pixelContext) {
createPixelContext();
}
pixelContext.clearRect(0, 0, 1, 1);
let data;
try {
pixelContext.drawImage(image, col, row, 1, 1, 0, 0, 1, 1);
data = pixelContext.getImageData(0, 0, 1, 1).data;
} catch (err) {
pixelContext = null;
return null;
}
return data;
}
getBackground(frameState) {
const layer = this.getLayer();
let background = layer.getBackground();
if (typeof background === "function") {
background = background(frameState.viewState.resolution);
}
return background || undefined;
}
useContainer(target, transform, backgroundColor) {
const layerClassName = this.getLayer().getClassName();
let container, context;
if (
target &&
target.className === layerClassName &&
(!backgroundColor ||
(target &&
target.style.backgroundColor &&
equals(
asArray(target.style.backgroundColor),
asArray(backgroundColor)
)))
) {
const canvas = target.firstElementChild;
if (canvas instanceof HTMLCanvasElement) {
context = canvas.getContext("2d");
}
}
if (context && context.canvas.style.transform === transform) {
this.container = target;
this.context = context;
this.containerReused = true;
} else if (this.containerReused) {
this.container = null;
this.context = null;
this.containerReused = false;
} else if (this.container) {
this.container.style.backgroundColor = null;
}
if (!this.container) {
container = document.createElement("div");
container.className = layerClassName;
let style = container.style;
style.position = "absolute";
style.width = "100%";
style.height = "100%";
context = createCanvasContext2D();
const canvas = context.canvas;
container.appendChild(canvas);
style = canvas.style;
style.position = "absolute";
style.left = "0";
style.transformOrigin = "top left";
this.container = container;
this.context = context;
}
if (
!this.containerReused &&
backgroundColor &&
!this.container.style.backgroundColor
) {
this.container.style.backgroundColor = backgroundColor;
}
}
clipUnrotated(context, frameState, extent) {
const topLeft = getTopLeft(extent);
const topRight = getTopRight(extent);
const bottomRight = getBottomRight(extent);
const bottomLeft = getBottomLeft(extent);
applyTransform(frameState.coordinateToPixelTransform, topLeft);
applyTransform(frameState.coordinateToPixelTransform, topRight);
applyTransform(frameState.coordinateToPixelTransform, bottomRight);
applyTransform(frameState.coordinateToPixelTransform, bottomLeft);
const inverted = this.inversePixelTransform;
applyTransform(inverted, topLeft);
applyTransform(inverted, topRight);
applyTransform(inverted, bottomRight);
applyTransform(inverted, bottomLeft);
context.save();
context.beginPath();
context.moveTo(Math.round(topLeft[0]), Math.round(topLeft[1]));
context.lineTo(Math.round(topRight[0]), Math.round(topRight[1]));
context.lineTo(Math.round(bottomRight[0]), Math.round(bottomRight[1]));
context.lineTo(Math.round(bottomLeft[0]), Math.round(bottomLeft[1]));
context.clip();
}
prepareContainer(frameState, target) {
const extent = frameState.extent;
const resolution = frameState.viewState.resolution;
const rotation = frameState.viewState.rotation;
const pixelRatio = frameState.pixelRatio;
const width = Math.round((getWidth(extent) / resolution) * pixelRatio);
const height = Math.round((getHeight(extent) / resolution) * pixelRatio);
composeTransform(
this.pixelTransform,
frameState.size[0] / 2,
frameState.size[1] / 2,
1 / pixelRatio,
1 / pixelRatio,
rotation,
-width / 2,
-height / 2
);
makeInverse(this.inversePixelTransform, this.pixelTransform);
const canvasTransform = toTransformString(this.pixelTransform);
this.useContainer(target, canvasTransform, this.getBackground(frameState));
if (!this.containerReused) {
const canvas = this.context.canvas;
if (canvas.width != width || canvas.height != height) {
canvas.width = width;
canvas.height = height;
} else {
this.context.clearRect(0, 0, width, height);
}
if (canvasTransform !== canvas.style.transform) {
canvas.style.transform = canvasTransform;
}
}
}
dispatchRenderEvent_(type, context, frameState) {
const layer = this.getLayer();
if (layer.hasListener(type)) {
const event = new RenderEvent(
type,
this.inversePixelTransform,
frameState,
context
);
layer.dispatchEvent(event);
}
}
preRender(context, frameState) {
this.frameState = frameState;
if (frameState.declutter) {
return;
}
this.dispatchRenderEvent_(RenderEventType.PRERENDER, context, frameState);
}
postRender(context, frameState) {
if (frameState.declutter) {
return;
}
this.dispatchRenderEvent_(RenderEventType.POSTRENDER, context, frameState);
}
renderDeferredInternal(frameState) {}
getRenderContext(frameState) {
if (frameState.declutter && !this.deferredContext_) {
this.deferredContext_ = new ZIndexContext();
}
return frameState.declutter
? this.deferredContext_.getContext()
: this.context;
}
renderDeferred(frameState) {
if (!frameState.declutter) {
return;
}
this.dispatchRenderEvent_(
RenderEventType.PRERENDER,
this.context,
frameState
);
if (frameState.declutter && this.deferredContext_) {
this.deferredContext_.draw(this.context);
this.deferredContext_.clear();
}
this.renderDeferredInternal(frameState);
this.dispatchRenderEvent_(
RenderEventType.POSTRENDER,
this.context,
frameState
);
}
getRenderTransform(
center,
resolution,
rotation,
pixelRatio,
width,
height,
offsetX
) {
const dx1 = width / 2;
const dy1 = height / 2;
const sx = pixelRatio / resolution;
const sy = -sx;
const dx2 = -center[0] + offsetX;
const dy2 = -center[1];
return composeTransform(
this.tempTransform,
dx1,
dy1,
sx,
sy,
-rotation,
dx2,
dy2
);
}
disposeInternal() {
delete this.frameState;
super.disposeInternal();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# 总结
本文介绍了CanvasLayerRenderer
的一些方法,矩阵的转换计算较为繁琐,暂不深入。