renderFeature介绍
# 概述
在前面提到了 Openlayers 中的矢量几何图形的渲染指令集部分,关于这些构建绘制指令集的类CanvasBuilderGroup
类,可以参考这篇文章。顾名思义,CanvasBuilderGroup
类就是一组管理各种几何图形指令集的构造器的集合,该类会在矢量图层VectorLayer.js
(src\ol\renderer\canvas\VectorLayer.js
)和矢量瓦片图层VectorTileLayer.js
(src\ol\renderer\canvas\VectorTileLayer.js
)中被实例化,然后依据feature
去构建指令集。
本文将以VetorLayer.js
为例,讲解一个feature
时如何生成指令集的过程。
# 源码分析
# CanvasVectorLayerRenderer
类
CanvasVectorLayerRenderer
类即VectorLayer.js
,用于构建矢量图层的渲染器,它继承于CanvasLayerRenderer
类,关于CanvasLayerRenderer
类,可以参考这篇文章。
CanvasVectorLayerRenderer
类的中的prepareFrame
方法会在Layer
类中的render
方法被调用,而Layer
类是BaseVectorLayer
类的父类,关于它们的介绍,分别可以参考这两篇文章和.而render
方法会在Map
的渲染器CompositeMapRenderer
类里的renderFrame
方法中被调用。总结一下就是prepareFrame
方法会在地图渲染时被调用,
prepareFrame
方法
prepareFrame
方法中关于feature
部分的代码如下:
class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
prepareFrame(frameState) {
const replayGroup = new CanvasBuilderGroup(
getRenderTolerance(resolution, pixelRatio),
extent,
resolution,
pixelRatio
);
const render = (feature, index) => {
let styles;
const styleFunction =
feature.getStyleFunction() || vectorLayer.getStyleFunction();
if (styleFunction) {
styles = styleFunction(feature, resolution);
}
if (styles) {
const dirty = this.renderFeature(
feature,
squaredTolerance,
styles,
replayGroup,
userTransform,
this.getLayer().getDeclutter(),
index
);
ready = ready && !dirty;
}
};
const features = vectorSource.getFeaturesInExtent(userExtent);
for (let i = 0, ii = features.length; i < ii; ++i) {
render(features[i], i);
}
const replayGroupInstructions = replayGroup.finish();
}
}
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
prepareFrame
方法会实例化CanvasBuilderGroup
类,实例对象relayGroup
表示一组待渲染的绘制命令集合,是图形绘制命令的载体。它用于管理和优化图形的渲染过程,将多个渲染步骤组合在一起,避免重复计算,可以提高渲染效率。然后定义了一个局部函数render
,
render
函数
render
函数接受两个参数feature
和index
,这里的feature
就是Feature
类的一个实例,关于Feature
类,可以参考这篇文章。render
方法内部会先判断feature
是否定义了样式函数styleFunction
,若没有定义,则使用矢量图层vectorLayer
的默认样式函数,然后执行styleFunction(feature,resolution)
语句,返回结果作为styles
,这一步也就是每当地图更新时,feature
或者layer
的styleFunction
会被调用,可以获取地图实时的分辨率resolution
;然后判断,若styles
存在,则调用内部方法this.renderFeature
方法。
prepareFrame
方法通过图层源获取用户可见范围内的features
,然后遍历features
,去调用上面定义的局部函数render
最后调用replayGroup.finish
方法获取需要绘制features
的绘制指令集和碰撞检测指令集等。
下面介绍下this.renderFeature
方法,该方法也是在CanvasVectorLayerRenderer
类中定义的一个内部方法。
其实现如下:
renderFeature
方法
class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
renderFeature(
feature,
squaredTolerance,
styles,
builderGroup,
transform,
declutter,
index
) {
if (!styles) {
return false;
}
let loading = false;
if (Array.isArray(styles)) {
for (let i = 0, ii = styles.length; i < ii; ++i) {
loading =
renderFeature(
builderGroup,
feature,
styles[i],
squaredTolerance,
this.boundHandleStyleImageChange_,
transform,
declutter,
index
) || loading;
}
} else {
loading = renderFeature(
builderGroup,
feature,
styles,
squaredTolerance,
this.boundHandleStyleImageChange_,
transform,
declutter,
index
);
}
return loading;
}
}
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
renderFeature
方法内部会先判断,若styles
样式不存在,则返回;然后判断样式styles
是否是数组,若是数组,则循环遍历样式styles
调用外部方法renderFeature
,所以这里如果定义多个样式,后面的样式会覆盖前面样式的相同属性;否则只需要调用一次外部方法renderFeature
,该外部方法renderFeature
接受参数builderGroup
、feature
和style
等等。builderGroup
就是在prepareFrame
方法中的实例对象replayGroup
.
# vector.js
上面提到的外部方法renderFeature
是在src\ol\renderer\vector.js
中定义的,vector.js
文件中定义一些方法用于进行不同几何图形的指令集构建。
- 全局变量
GEOMETRY_RENDERERS
vector.js
中定义了全局变量GEOMETRY_RENDERERS
如下:,该变量就是几何图形类型与渲染方法的映射关系。
const GEOMETRY_RENDERERS = {
Point: renderPointGeometry, // 渲染点
LineString: renderLineStringGeometry, // 渲染线
Polygon: renderPolygonGeometry, // 渲染多边形
MultiPoint: renderMultiPointGeometry, // 渲染多个点
MultiLineString: renderMultiLineStringGeometry, // 渲染多条线
MultiPolygon: renderMultiPolygonGeometry, // 渲染多个多边形
GeometryCollection: renderGeometryCollectionGeometry, // 渲染几何图形集合
Circle: renderCircleGeometry, // 渲染圆
};
2
3
4
5
6
7
8
9
10
renderFeature
方法
renderFeature
方法也就是暴露给CanvasVectorLayerRenderer
以及CanvasVectorTileLayerRenderer
类的外部方法renderFeature
方法,其实现如下:
export function renderFeature(
replayGroup,
feature,
style,
squaredTolerance,
listener,
transform,
declutter,
index
) {
const loadingPromises = [];
const imageStyle = style.getImage();
if (imageStyle) {
let loading = true;
const imageState = imageStyle.getImageState();
if (imageState == ImageState.LOADED || imageState == ImageState.ERROR) {
loading = false;
} else {
if (imageState == ImageState.IDLE) {
imageStyle.load();
}
}
if (loading) {
loadingPromises.push(imageStyle.ready());
}
}
const fillStyle = style.getFill();
if (fillStyle && fillStyle.loading()) {
loadingPromises.push(fillStyle.ready());
}
const loading = loadingPromises.length > 0;
if (loading) {
Promise.all(loadingPromises).then(() => listener(null));
}
renderFeatureInternal(
replayGroup,
feature,
style,
squaredTolerance,
transform,
declutter,
index
);
return loading;
}
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
renderFeature
函数的主要作用是检查并处理渲染过程中可能涉及到的图像和填充样式的加载。它会等待所有需要的资源加载完成后再执行实际的渲染。这个过程通过 Promise
来处理异步加载,确保渲染操作在所有资源准备好之后进行,即调用内部方法renderFeatureInternal
renderFeatureInternal
方法
renderFeatureInternal
是一个内部方法,其实现如下:
function renderFeatureInternal(
replayGroup,
feature,
style,
squaredTolerance,
transform,
declutter,
index
) {
const geometry = style.getGeometryFunction()(feature);
if (!geometry) {
return;
}
const simplifiedGeometry = geometry.simplifyTransformed(
squaredTolerance,
transform
);
const renderer = style.getRenderer();
if (renderer) {
renderGeometry(replayGroup, simplifiedGeometry, style, feature, index);
} else {
const geometryRenderer = GEOMETRY_RENDERERS[simplifiedGeometry.getType()];
geometryRenderer(
replayGroup,
simplifiedGeometry,
style,
feature,
index,
declutter
);
}
}
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
renderFeatureInternal
方法会先调用style.getGeometryFunction
方法获取feature
的集合对象,若集合对象不存在,则直接返回;然后获取几何对象的简化版本simplifiedGeometry
,在调用style.getRenderer
,若存在自定义渲染器,则调用renderGeometry
方法;否则获取几何对象的类型,再根据类型从全局变量GEOMETRY_RENDERERS
中找到几何图形渲染方法,并调用该方法。
renderGeometry
方法
renderGeometry
方法用于自定义渲染几何对象,其实现如下:
function renderGeometry(replayGroup, geometry, style, feature, index) {
if (geometry.getType() == "GeometryCollection") {
const geometries = geometry.getGeometries();
for (let i = 0, ii = geometries.length; i < ii; ++i) {
renderGeometry(replayGroup, geometries[i], style, feature, index);
}
return;
}
const replay = replayGroup.getBuilder(style.getZIndex(), "Default");
replay.drawCustom(
feature,
style.getRenderer(),
style.getHitDetectionRenderer(),
index
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
renderGeometry
方法是一个递归函数,会先判断几何对象的类型,若类型为geometryCollection
集合,则遍历该集合中的几何对象,调用自身,然后返回;若不是,则调用replayGroup.getBuilder
方法获取默认构建器,然后调用构建器的drawCustom
方法进行构造绘制指令集。
renderGeometryCollectionGeometry
方法
在renderFeatureInternal
方法中,若类型为GeometryCollection
,则会调用renderGeometryCollectionGeometry
方法,其实现如下:
function renderGeometryCollectionGeometry(
replayGroup,
geometry,
style,
feature,
declutterBuilderGroup,
index
) {
const geometries = geometry.getGeometriesArray();
let i, ii;
for (i = 0, ii = geometries.length; i < ii; ++i) {
const geometryRenderer = GEOMETRY_RENDERERS[geometries[i].getType()];
geometryRenderer(
replayGroup,
geometries[i],
style,
feature,
declutterBuilderGroup,
index
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
renderGeometryCollectionGeometry
方法会获取集合中的几何对象,然后遍历,获取每个几何对象的类型,再根据类型,去调用不同的渲染函数。
renderPointGeometry
方法
renderPointGeometry
方法用于渲染点几何图形,其实现如下:
function renderPointGeometry(
builderGroup,
geometry,
style,
feature,
index,
declutter
) {
const imageStyle = style.getImage();
const textStyle = style.getText();
const hasText = textStyle && textStyle.getText();
const declutterImageWithText =
declutter && imageStyle && hasText ? {} : undefined;
if (imageStyle) {
if (imageStyle.getImageState() != ImageState.LOADED) {
return;
}
const imageReplay = builderGroup.getBuilder(style.getZIndex(), "Image");
imageReplay.setImageStyle(imageStyle, declutterImageWithText);
imageReplay.drawPoint(geometry, feature, index);
}
if (hasText) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), "Text");
textReplay.setTextStyle(textStyle, declutterImageWithText);
textReplay.drawText(geometry, feature, index);
}
}
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
renderPointGeometry
方法会先根据参数style
获取imageStyle
和textStyle
。若imageStyle
存在,且图片资源以及加载了,则调用builder.getBuilder(style.getZIndex(),'Image')
获取CanvasImageBuilder
的实例,再调用实例的setImageStyle
方法和drawPoint
方法;若文本内容存在,则调用builder.getBuilder(style.getZIndex(),'Text')
获取CanvasTextBuilder
的实例,再调用实例的setTextStyle
方法和drawText
方法。
关于CanvasImageBuilder
构建器,可以参考这篇文章
关于CanvasTextBuilder
构建器,可以参考这篇文章
renderMultiPointGeometry
方法
renderMultiPointGeometry
方法和renderPointGeometry
方法的实现其实很相似,根据imageStyle
和textStyle
去获取构造器,而涉及到的构建器也是一样,分别是CanvasImageBuilder
和CanvasTextBuilder
。不同的是renderMultiPointGeometry
方法中图片构建器调用的实例方法是drawMultiPoint
方法。其实现如下:
function renderMultiPointGeometry(
builderGroup,
geometry,
style,
feature,
index,
declutter
) {
const imageStyle = style.getImage();
const hasImage = imageStyle && imageStyle.getOpacity() !== 0;
const textStyle = style.getText();
const hasText = textStyle && textStyle.getText();
const declutterImageWithText =
declutter && hasImage && hasText ? {} : undefined;
if (hasImage) {
if (imageStyle.getImageState() != ImageState.LOADED) {
return;
}
const imageReplay = builderGroup.getBuilder(style.getZIndex(), "Image");
imageReplay.setImageStyle(imageStyle, declutterImageWithText);
imageReplay.drawMultiPoint(geometry, feature, index);
}
if (hasText) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), "Text");
textReplay.setTextStyle(textStyle, declutterImageWithText);
textReplay.drawText(geometry, feature, index);
}
}
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
renderLineStringGeometry
方法
renderLineStringGeometry
方法用于渲染线,其实现如下:
function renderLineStringGeometry(
builderGroup,
geometry,
style,
feature,
index
) {
const strokeStyle = style.getStroke();
if (strokeStyle) {
const lineStringReplay = builderGroup.getBuilder(
style.getZIndex(),
"LineString"
);
lineStringReplay.setFillStrokeStyle(null, strokeStyle);
lineStringReplay.drawLineString(geometry, feature, index);
}
const textStyle = style.getText();
if (textStyle && textStyle.getText()) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), "Text");
textReplay.setTextStyle(textStyle);
textReplay.drawText(geometry, feature, index);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
renderLineStringGeometry
方法会先通过style.getStroke()
方法获取strokeStyle
,若它存在,则调用builderGroup.getBuilder(style.getZIndex(),'linString')
,即获取CanvasLinStringBuilder
构建器,然后调用构建器的setFillStrokeStyle
方法和drawLineString
方法;若存在文本内容,则获取CanvasTextBuilder
类构建器的实例,调用实例的setTextStyle
方法和drawText
方法。
关于CanvasLineStringBuilder
构建器,可以参考这篇文章
renderMultiLineStringGeometry
方法
renderMultiLineStringGeometry
方法和renderLineStringGeometry
方法类似,唯一不同的是前者调用CanvasLineStringBuilder
类构建器实例的drawMultiLineString
方法,而后者则是调用实例的drawLineString
方法,其实现如下:
function renderMultiLineStringGeometry(
builderGroup,
geometry,
style,
feature,
index
) {
const strokeStyle = style.getStroke();
if (strokeStyle) {
const lineStringReplay = builderGroup.getBuilder(
style.getZIndex(),
"LineString"
);
lineStringReplay.setFillStrokeStyle(null, strokeStyle);
lineStringReplay.drawMultiLineString(geometry, feature, index);
}
const textStyle = style.getText();
if (textStyle && textStyle.getText()) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), "Text");
textReplay.setTextStyle(textStyle);
textReplay.drawText(geometry, feature, index);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
renderPolygonGeometry
方法
renderPolygonGeometry
方法用于多边形的绘制或渲染,方法实现如下:
function renderPolygonGeometry(builderGroup, geometry, style, feature, index) {
const fillStyle = style.getFill();
const strokeStyle = style.getStroke();
if (fillStyle || strokeStyle) {
const polygonReplay = builderGroup.getBuilder(style.getZIndex(), "Polygon");
polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
polygonReplay.drawPolygon(geometry, feature, index);
}
const textStyle = style.getText();
if (textStyle && textStyle.getText()) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), "Text");
textReplay.setTextStyle(textStyle);
textReplay.drawText(geometry, feature, index);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
renderPolygonGeometry
方法会先获取参数style
上的fillStyle
、strokeStyle
和textStyle
;若fillStyle
或者strokeStyle
存在,则调用builderGroup.getBuilder(style.getZIndex(),'Polygon')
获取CanvasPolygonBuilder
类的构建器实例,然后调用实例的setFillStrokeStyle
方法和drawPolygon
方法;若存在文本内容,则获取CanvasTextBuilder
类的构建器实例,调用实例的setTextStyle
方法和drawText
方法。
关于CanvasPolygonBuilder
构建器,可以参考这篇文章
renderMultiPolygonGeometry
方法
renderMultiPolygonGeometry
方法的实现思路和renderPolygonGeometry
方法类似,不同的是,前者调用的是多边形构建器实例的drawMultiPolygon
方法,而后者则是调用drawPolygon
方法。其实现如下:
function renderMultiPolygonGeometry(
builderGroup,
geometry,
style,
feature,
index
) {
const fillStyle = style.getFill();
const strokeStyle = style.getStroke();
if (strokeStyle || fillStyle) {
const polygonReplay = builderGroup.getBuilder(style.getZIndex(), "Polygon");
polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
polygonReplay.drawMultiPolygon(geometry, feature, index);
}
const textStyle = style.getText();
if (textStyle && textStyle.getText()) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), "Text");
textReplay.setTextStyle(textStyle);
textReplay.drawText(geometry, feature, index);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
renderCircleGeometry
方法
在renderFeatureInternal
方法中,若类型为Circle
,则会调用renderCircleGeometry
方法,其实现如下:
function renderCircleGeometry(builderGroup, geometry, style, feature, index) {
const fillStyle = style.getFill();
const strokeStyle = style.getStroke();
if (fillStyle || strokeStyle) {
const circleReplay = builderGroup.getBuilder(style.getZIndex(), "Circle");
circleReplay.setFillStrokeStyle(fillStyle, strokeStyle);
circleReplay.drawCircle(geometry, feature, index);
}
const textStyle = style.getText();
if (textStyle && textStyle.getText()) {
const textReplay = builderGroup.getBuilder(style.getZIndex(), "Text");
textReplay.setTextStyle(textStyle);
textReplay.drawText(geometry, feature);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
renderCircleGeometry
方法会先参数style
上获取fillStyle
和strokeStyle
以及textStyle
,然后根据这些样式,获取CanvasPolygonBuilder
类的构建器实例circleReplay
和CanvasTextBuilder
类的构建器实例textReplay
,然后调用实例上的方法。
# 总结
feature
要素的绘制指令的构建大概就是上述这些内容,不同类型的几何图形绑定的feature
涉及到的样式类型和指令构建器也各不相同。需要弄清楚类型和指令构建器的对应关系。