二维码

第三章 图层类(Layer)

本章摘要

图层是图形系统的关键,帮助组织和管理图形内容。本章阐述图层概念、管理、渲染,并深入解析anyGraph图形开发引擎中图层类的设计,包括属性、数据源和渲染器。读者将学习图层类设计、实现、数据源及渲染技术,以及它们如何协同工作以实现高效的图形管理。

图形系统开发实战课程 - 进阶篇 第三章 图层类(Layer)

第三章 图层类(Layer)

  在上一章中,我们讲到了图层(Layers)是一种用于组织和管理图像内容的办法。可以将不同的图像元素分开,使得它们可以独立地进行绘制、编辑和操作。每个图层都可以包含一个或多个图像对象,这些对象可以是几何对象、文本、图像等。图层可以互相叠加,并且可以通过控制透明度、颜色和大小等属性来创建各种视觉效果。

  本章以 anyGraph 中图层类的设计为例讲述图层在图形中的作用。

1. 类的设计

  面向对象程序设计中的抽象是指从具体事物中抽取其共性的过程。按照这个方法,我们对图层的属性、数据和行为进行分析后,归纳出在图形系统中,图层的特征可分为三类:图层属性、图层数据、图层渲染。图层属性包括图层名称、渲染顺序、可见范围等信息,图层数据包含了该图层中的图形对象集合,图层渲染是指将图形对象集合渲染至画板Canvas中的操作。

  这三个特征可分别对应一个Class,在其封装内部逻辑,这三个类命名分别为Layer类LayerSource类LayerRenderer类,这三者的关系如下图所示:

classDiagram

Layer --> LayerRenderer
Layer --> LayerSource

  按照上一章对图形的定义,一个图形包含了多个图层,负责图层的集合管理。集合中每个图层均包含了上述三个类的实例。

类关系图

  按照数据类型差异,图层的数据源可以是几何数据源,可以是位图数据源,也可以是地图瓦片数据源。数据类型的差异会导致在渲染时采用不同的渲染方法,因此图层数据源图层渲染是一种强对应关系,例如图层矢量数据源VectorSource对应的是图层矢量渲染类VectorRenderer类,图层瓦片数据源TileSource对于图层瓦片渲染类TileRenderer

  下面这张类图包含了这几个类及其父类。

classDiagram

BaseSource <|-- VectorSource
BaseSource <|-- TileSource
BaseSource <|-- ImageSource

LayerRenderer <|-- ImageRenderer
LayerRenderer <|-- TileRenderer
LayerRenderer <|-- VectorRenderer

Layer --> VectorRenderer
Layer --> VectorSource

class Layer{
    zIndex: int
    name: String
    style: Object
    opacity: float
    source: BaseSource
    usePixelCoord: boolean
    renderer: LayerRenderer
    setStyle(style)
    getVisible()
    visibleAtResolution()
    setOffset(x, y)
}

class BaseSource {
    dataBuffer:Array
    imageBuffer:Array
    getData()
    getLayer()
}

class LayerRenderer {
    _canvas: Canvas
    composeBuffer()
}

class VectorSource{
    format: GeometryFormat
    loadData(features)
    add(geomList)
    clearData(id)
    getExtentData(extent)
    getBBox()
    buildIndex()
}

class TileSource{
    loadData(features)
    add(geomList)
    clearData(id)
}

class ImageSource{
    loadData(features)
    add(geomList)
    clearData(id)
}

class VectorRenderer{
    _canvas:Canvas
    prepareFrame()
    composeBuffer(frameState)
    renderFrame()
    clearContext(ctx)
    setStyle()
    filter()
}

class TileRenderer{
    _canvas:Canvas
    _getRenderedTiles()
    composeBuffer(frameState)
}

class ImageRenderer{
    _canvas:Canvas
    _getRenderedImages()
    composeBuffer(frameState)
}

2. 图层类

类名为:Layer,源代码位于src目录。

  图层类 Layer 是图层中这几个类的核心类,提供了图层信息、图层控制、图层样式等方面的功能。

(1) 初始化

1
constructor(options)

  该类的构造函数接受一个 Object 类型的参数,其值包括:

名称 类型 说明
name String 名称
zIndex int 图层顺序,zIndex较大的图层显示在较小的图层之上
usePixelCoord Boolean 是否使用像素作为坐标
opacity float 透明度,取值范围为 0~1
minResolution float 图层可见的最小密度
maxResolution float 图层可见的最大密度
visible Boolean 图层是否可见
style Object 图层样式
source Source 图层数据源

下面是创建一个图层的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let layer = new Layer({
source: new VectorSource({
"fileUrl": path + "export_roadcenter.geojson",
"projection": projection,
"format": new GeoJSONFormat()
}),
zIndex: 10025,
name: "道路-路中线",
style: { "color": "#00BFFF", "lineWidth": 2 },
minResolution: 0.880,
maxResolution: 3.240,
visible: true
});
graph.addLayer(layer);

(2) 图层信息

下面列出了几个常用的获取图层信息的方法:

名称 说明
getName() 名称
getZIndex() 图层顺序
isUsePixelCoord() 是否使用像素作为坐标

渲染顺序

  该属性在构造函数中通过 zIndex属性指定,通过 getZIndex() 可获取图层的渲染顺序。在将图层合成至图形时,先绘制该值较小的图层,然后绘制该值较大的图层。

是否采用像素坐标

  该属性在构造函数中通过 usePixelCoord 属性指定,通过 isUsePixelCoord() 可获取该图层是否采用像素坐标。采用像素坐标时,该图层不会进行矩阵变换,而是直接渲染至Canvas指定位置,这也意味着该图层中的图形位置和大小不会随着图形的缩放和漫游而发生变化。之前多个示例的效果图中都包含了一个带有网格和水印的背景层,就设置了usePixelCoord=true,因此不管对图形进行怎样的缩放和漫游操作,其背景始终是不变的。

(3) 图层控制

下面列出了几个图层控制的方法:

名称 说明
getVisible() 是否显示
setVisible(visible) 设置是否显示
getMaxResolution() 获取最大分辨率值
setMaxResolution(maxResolution) 设置渲染该图层的最大分辨率值
getMinResolution() 获取最小分辨率值
setMinResolution(minResolution) 设置渲染该图层的最小分辨率值

可见性

  该属性可在构造函数中通过 visible 属性指定 或 通过 setVisible() 设置图层可见性。如果该值为false,则不合成至图形中。

分辨率

  分辨率指的Canvas中像素对应图形的宽度/高度的比值,通过分辨率属性同样可以控制图层的可见性。在构造函数中可指定最小分辨率 minResolution 和 最大分辨率 maxResolution 属性指定。

  1. 在地图图纸中通常使用 比例尺 来描述地图的精度,比例尺是表示图上一条线段的长度与地面相应线段的实际长度之比。公式为:比例尺=图上距离/实际距离。一般来讲,越是大的比例尺地图,几何精度越高。
  2. 而在计算中通常使用 分辨率 来描述地图的精度,分辨率指的Canvas中1个像素对应图形的宽度/高度的比值。同样分辨率越大,几何精度越高。
  3. 在计算机中通常不采用比例尺来描述图形的精度,这是因为“比例尺=图上距离/实际距离",而显示器屏幕的大小是不确定的,同一张图在14寸的显示器上和27寸的显示器上显示全图时,由于分母相同分子不相同,因此其比值是不一样的。

(4) 图层样式

名称 说明
getStyle() 获取图层渲染样式
setStyle(style) 设置图层渲染样式
getOpacity() 获取图层透明度 (between 0 and 1).
setOpacity(opacity) 设置透明度(between 0 and 1)

  该属性在构造函数中通过 style 属性指定,也可通过 setStyle(style) 方法赋值。样式主要指的是几何对象的颜色、线宽、线型等渲染时的属性,而对于文字而言,样式还包括文本的大小、字型、对齐等属性。

  在渲染图层中的图形对象时,既可以为图层中的每一个图形对象指定样式,也可以为图层指定样式。如果图形对象和图层均指定了样式,则优先使用图形对象的样式。

3 数据源

  图层数据包含了某图层渲染时的图形对象集合,该集合中的所有对象都位于同一个坐标系中。

  该属性在构造函数中通过 source 属性指定,缺省为 VectorDataSource 类型, 图层类 Layer 提供了getSource() 方法可获取该图层对应的数据源对象。

名称 说明
getSource() 获取图层数据源

(1) 矢量数据源

类名为:VectorDataSource,位于src/source目录。

  矢量数据源是为矢量图层提供具体的数据来源,包含了在图形上展示几何对象和文本、图像等图形对象集合。

初始化

1
constructor(options)

  该类的构造函数接受一个 Object 类型的参数,其值包括:

名称 类型 说明
format FeatureFormat 格式化对象
fileUrl String 数据文件Url
data Array 数据列表
projection Projection 投影类型对象

说明:

  • format 属性,用于在 loadFile()loadData() 时解析数据的格式化对象;
  • data 属性,如果指定了该属性,则构造后立即将该数据装载至 Source 中,优先级高于 fileUrl 属性;
  • fileUrl 属性,如果指定了该属性,则构造后立即下载该数据文件,并装载至 Source 中;
  • projection 属性,如果指定了该属性,则从 datafileUrl 加载数据时,将进行坐标投影变换。

  下面这个示例在构造VectorSource时,指定了 fileUrl 属性,因此在构造之后,数据将渲染至图形中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="module">
import { Graph, VectorSource, Layer, debug } from "../../../src/index.js";
// graph对象
let graph = new Graph({
"target": "graphWrapper",
"layers": [
new Layer({
"source": new VectorSource({
"fileUrl": "../../../data/geom.json"
})
})
]
});

// 图形渲染
graph.render();
</script>

属性

名称 说明
dataBuffer 矢量数据集合
imageBuffer 图像数据集合
quadTree 空间索引对象
format 格式化对象

这几个属性中 dataBufferimageBufferquadTree均属于内部对象。

  format 对象是 FeatureFormat 类的实例,该对象可将外部矢量数据解析为 anyGraph 所使用的内部数据格式, 缺省值为GeometryFormat。可在构造VectorDataSource 实例时指定 format 属性,或是通过 setFormat(format) 指定 format 属性。

方法

名称 说明
add(geom) 增加Geomtory对象至数据源中
loadFile(fileUrl, success, failure) 从文件中读取矢量数据
loadData(features) 装载Geomtory数据至数据源中
clearData(id) 清除指定ID数据,如果ID为空则清除数据源中所有数据
buildIndex() 构建四叉树索引
getExtentData(extent) 获取指定范围内的数据
getBBox() 获取数据源中的最大空间范围
add2Cache(filePath, imageUid) 将图片数据加至缓存中
getImageFromCache(src) 从缓存中获取Image对象
loadImage(src, callback, callback) 加载Image对象
toData(options = {}) 以矢量数据格式返回当前数据源中的数据

  下面介绍几个常用的方法:

loadFile()

  该方法下载数据文件通过format对象解析后,加载至 Source 中。

loadFile(fileUrl, success, failure)

  下面这个示例,将从Url中下载数据文件,通过缺省的format对象解析后装载至VectorSource中,并进行图形渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="module">
import { Graph, VectorSource, Layer, debug } from "../../../src/index.js";

// 初始化graph对象
let graph = new Graph({
"target": "graphWrapper",
});

// 增加数据层
let layer = graph.addLayer({"name":"数据层"});
layer.getSource().loadFile("../../../data/geom.json");

// 图形渲染
graph.render();
</script>

  该方法从文件中读取矢量数据,该方法通过XMLHttpRequest从指定的fileUrl中下载文件,并通过format对象解析文件中的图形对象。

loadData()

  该方法将矢量数据通过format对象解析后,加载至 Source 中。

loadData(features)

  下面这个示例,将数据通过缺省的format对象解析后装载至VectorSource中,并进行图形渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script type="module">
import { Graph, Layer, VectorSource, debug } from "../../../src/index.js";

// 初始化graph对象
let graph = new Graph({
"target": "graphWrapper"
});

// 增加数据层
let layer = graph.addLayer({"name":"数据层"});
layer.getSource().loadData([
{ "type": "Polygon", "coords": [[1, 1], [161, 1], [81, 81]], "style": { "fillStyle": 1, "fillColor": "#caff67" } },
{ "type": "Polygon", "coords": [[1, 2], [81, 82], [1, 162]], "style": { "fillStyle": 1, "fillColor": "#67becf" } },
{ "type": "Polygon", "coords": [[162, 1], [162, 81], [122, 121], [122, 41]], "style": { "fillStyle": 1, "fillColor": "#ef3d61" } },
{ "type": "Polygon", "coords": [[121, 42], [121, 122], [81, 82]], "style": { "fillStyle": 1, "fillColor": "#f9f51a" } },
{ "type": "Polygon", "coords": [[82, 82], [122, 122], [82, 162], [42, 122]], "style": { "fillStyle": 1, "fillColor": "#a54c09" } },
{ "type": "Polygon", "coords": [[42, 122], [82, 162], [2, 162]], "style": { "fillStyle": 1, "fillColor": "#fa8ccc" } },
{ "type": "Polygon", "coords": [[162, 82], [162, 162], [82, 162]], "style": { "fillStyle": 1, "fillColor": "#f6ca29" } }
]);

// 图形渲染
graph.render();
</script>

add()

add(geom)

  该方法将一个或多个Geometry的对象加入数据源中。

buildIndex()

buildIndex()

  该方法将建立空间索引。建立空间索引可以提高检测视点范围内数据的效率,排除视点外的数据,缩短图形的渲染时间,特别适合大数据量的情况。在loadFile()中会自动调用该方法建立空间索引。

toData()

toData(options = {})

  该方法将返回当前数据源中Geometry对象JSON格式数据,可用于数据持久化。

数据格式

  矢量数据源 VectorDataSource 在装载数据的时候,可以根据 Format 对象装载不同格式的数据,anyGraph1.0可支持的格式包括:

  • Geometry对象格式,其Format数据类为 GeometryFormat 类,这也是 VectorDataSource 的缺省数据格式。
  • GeoJSON格式,一种很常用的地理空间数据交换格式,其Format数据类为 GeoJSONFormat类。
  • SVG格式,可缩放矢量图形(Scalable Vector Graphics)是一种基于XML的矢量图像格式,其Format数据类为 SvgFormat类。
  • CIMG(CIM/G)格式,是国家电网公司发布一种基于CIM的图形交换格式,其Format数据类为 CimgFormat类。
  • AXFG格式,是一种很经典的基于json的图形交换格式,其Format数据类为 AxfgFormat类。

下面的示例将装载和显示一个SVG格式文件,其源代码如下:

{.line-numbers}
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
<!DOCTYPE html>
<html>
<head>
<title>SVG</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<script type="module">
import { Graph, VectorSource, Layer, SvgFormat, debug } from "../../src/index.js";
// 数据源
let fileUrl = "../../data/flower.svg";
// graph对象
let graph = new Graph({
"target": "graphWrapper",
"layers": [
new Layer({
"source": new VectorSource({
"dataType": "xml",
"fileUrl": fileUrl,
"format": new SvgFormat()
})
})
]
});
// 显示辅助网格
debug.generateGrid(Object.assign({ "interval": 10, "graph": graph }, graph.getSize()));
</script>
</head>
<body style="overflow: hidden; margin:0px;" oncontextmenu="return false;">
<div id="graphWrapper" style="position:absolute; width:100%; height:100%; border:solid 0px #CCC;"></div>
</body>
</html>

该代码运行后将在浏览器中全屏显示一幅SVG图形,其运行效果如下图:

运行效果

(2) 图像数据源

(3) 瓦片数据源

4 图层渲染

图层渲染负责将数据集合中的图形对象渲染至画板Canvas中,图层类 Layer 提供了getRenderer() 方法可获取该图层对应的图层渲染器对象。

名称 说明
getRenderer() 获取取图层渲染器

(1) 矢量数据图层渲染

类名为:VectorRenderer,位于src/renderer目录。

  矢量数据图层渲染类负责将图层中 VectorSource 数据渲染至图层的Canvas画布中。

  该类中最重要的属性是 _canvas ,这是一个Canvas对象,是该图层的临时画布,图层中的数据先由 composeBuffer() 方法合成至该临时画布中,然后在合并至图形主画布中。

  该类中最重要的方法是 composeBuffer(frameState),该方法由 GraphRenderer 负责调用,将数据合成至Canvas画布,其主要分为三个步骤。

  1. 根据 frameState 参数中的 extent 属性(当前显示范围)从 DataSource 查询 Geometry对象 数据,如果 DataSource 建立了空间索引,该步骤将极大提高数据检索效率;

  2. 对这些 Geometry对象 数据进行坐标变换,将图形坐标转换为Canvas画布中的像素坐标;

  3. 逐个将这些 Geometry对象 数据绘制至Canvas画布中。

  步骤二中的将 Geometry对象 的图形坐标转换为像素坐标功能,由于不同类型的 Geometry对象 其空间属性不相同,因此其转换方法不相同;步骤三中逐个将 Geometry对象 绘制至Canvas画布中也因为不同类型的 Geometry对象 其绘制方法不相同。因此我们根据Geometry 对象类型设计了针对其类型的 Geometry 子类,这些子类均从 Geometry 继承, 在各个子类中实现了将图形坐标转换为像素坐标的 toPixel() 方法和图形绘制 draw() 方法,关于这部分的详细介绍我们将在下一章 “图形基本形状” 中进行讲解。

(2) 图像数据图层渲染

(3) 瓦片数据图层渲染


  “图形系统实战开发-进阶篇 第三章 图层” 的内容讲解到这里就结束了,如果觉得对你有帮助有收获,可以关注我们的官方账号,持续关注更多精彩内容。


本文为“图形开发学院”(www.graphanywhere.com)网站原创文章,遵循CC BY-NC-ND 4.0版权协议,商业转载请联系作者获得授权,非商业转载请附上原文出处链接及本声明。

历史发布版本

第1版发布时间:2024-02-01
第2版发布时间:2024-06-12