Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 核心基类

  • Control控件篇

  • Geom几何图形篇

  • Layer图层篇

  • Renderer篇

    • VectorContext类
    • Build类
    • TextBuilder类
    • BuilderGroup类
    • PolygonBuilder类
    • LineStringBuilder类
    • ImageBuilder类
    • renderFeature介绍
      • 概述
      • 源码分析
        • CanvasVectorLayerRenderer类
        • vector.js
      • 总结
    • CanvasLayerRenderer类
    • LayerRender类
  • Feature篇

  • style样式篇

  • 《Openlayers源码》笔记
  • Renderer篇
东流
2025-01-16
目录

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();
  }
}
1
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;
  }
}
1
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, // 渲染圆
};
1
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;
}
1
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
    );
  }
}
1
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
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

renderGeometry方法是一个递归函数,会先判断几何对象的类型,若类型为geometryCollection集合,则遍历该集合中的几何对象,调用自身,然后返回;若不是,则调用replayGroup.getBuilder方法获取默认构建器,然后调用构建器的drawCustom方法进行构造绘制指令集。

关于默认构建器CanvasBuilder可以参考这篇文章

  • 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
    );
  }
}
1
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);
  }
}
1
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);
  }
}
1
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);
  }
}
1
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);
  }
}
1
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);
  }
}
1
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);
  }
}
1
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);
  }
}
1
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涉及到的样式类型和指令构建器也各不相同。需要弄清楚类型和指令构建器的对应关系。

编辑 (opens new window)
上次更新: 2025/01/17, 06:07:15
ImageBuilder类
CanvasLayerRenderer类

← ImageBuilder类 CanvasLayerRenderer类→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式