图形开发学院|GraphAnyWhere
- 课程名称:图形系统开发实战课程:进阶篇(上)
- 课程章节:“图形样式”
- 原文地址:https://www.graphanywhere.com/graph/advanced/2-10.html
第十章 应用实例:交通路网
\quad 在前面几章的讲解中,我们已经讲述了图形系统中的一些重要概念,以及图形平移缩放、拾取、视点控制等图形交互功能。本章通过一个实际应用案例,采用 anyGraph
实现对交通路网数据展示功能,同时复习和回顾之前几个章节讲述的一些技巧。
\quad 交通路网业务通常划分在地理信息系统的业务领域,由于本示例并不涉及到地理分析计算方面的功能,因此交通路网也可以划分在图形系统业务领域中。
1 数据分析
\quad 本实例中采用的交通路网样例数据包括了某科技园区约9万平方米的地理范围,其数据类型可分为:道路中心线、道路路面、道路中的虚线、道路中的实线、道路中方向箭头共5类数据。其WEB墨卡托坐标范围为:[12683000, 2574000, 12685600, 2577400]
。
\quad 上述这些数据均采用采用 GeoJSON
格式,GeoJSON
是一种对各种地理数据结构进行编码的格式,该格式采用Javascript对象表示法(JavaScript Object Notation, 简称JSON),是一种地理空间信息数据交换格式。GeoJSON对象
包含了几何、特征或者特征集合等信息。
1.1 道路中心线
\quad 道路中心线数据为线类型数据,描述了道路的走向,其几何信息较为简单,通常在仅需显示道路位置和走向的场景中使用道路中心线代替实际的道路路面数据。下图采用土黄色线条即为道路中心线,该线设置了一定的线宽使之看起来与道路相似:
\quad 道路中心线数据以 GeoJSON
格式存储在文件中,一个 GeoJSON
对象表示一条道路。该对象中不仅包含了道路中心线的坐标数据,还在特征属性中包含了道路名称信息,例如下面这个 GeoJSON
对象中, properties
属性中的 name
属性指的是 道路名称,features
属性中的 coordinates
属性指的是该道路的坐标。
\quad 此外,该GeoJSON
对象中还包含了crs
属性,表示该坐标的坐标参考系统,关于这部分的内容我们将在后续课程中讲解,在这里只需知道该道路采用了 WGS84
地理坐标系,在绘制图形的时候需要经过投影转换为平面坐标系。
\quad 道路中心线 GeoJSON
对象如下所示:
{"type": "FeatureCollection","crs": {"type": "name","properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}},"features": [{"type": "Feature","id": "63-3250080","properties": {"name": "海德三道"},"geometry": {"type": "LineString","coordinates": [[113.9358854, 22.5218712],[113.9358857, 22.5218712],[113.9359452, 22.5218569],[113.9408402, 22.5206243],[113.9409046, 22.5205918],[113.9409457, 22.5205707],[113.9409801, 22.5205532],[113.9410334, 22.5205264],[113.9414433, 22.5203182]]}}]
}
1.2 道路路面数据
\quad 道路路面数据为面类型数据,描述的是实际道路的外观,由于道路的几何信息较为复杂,通常在需要显示道路细节数据时,才显示道路路面数据。道路路面如下图所示:
\quad 道路路面数据以 GeoJSON
格式存储在文件中,一条道路由多个 GeoJSON
对象组成。该对象主要信息就是道路路面的坐标数据,由features
属性中的 coordinates
属性指定,此外该 GeoJSON
对象中也包含了crs
属性。
\quad 在测绘道路路面多边形数据时,由于比例尺不同,测绘的精度会有很大差异,对于大比例尺的数据,在某些复杂的十字路口,其面数据的坐标点甚至可能会达到甚至超过数百个点,因此在不需要显示道路细节时,往往会以显示道路中心线的方式代替显示道路路面,不仅仅加快了渲染速度,而且是的图形画面更为简洁,可更好突出图形主体信息。道路路面 GeoJSON
对象如下所示:
{"type": "FeatureCollection","crs": {"type": "name","properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}},"features": [{"type": "Feature","id": "1-2000007-F","geometry": {"type": "Polygon","coordinates": [[[113.9440488, 22.5358627],[113.9424885, 22.5358365],[113.9419384, 22.5358351],[113.9419385, 22.5357417],[113.9424895, 22.5357431],……[113.9440506, 22.5357693]]]}}]
}
1.3 道路虚线数据
\quad 道路虚线数据为线类型数据,描述的交通部门在实际道路路面中绘制的虚线,表示车辆可以变道数据,下图在道路路面中显示了道路虚线:
\quad 道路虚线数据以 GeoJSON
格式存储在文件中,一条道路包含了多个 GeoJSON
虚线对象。该对象主要信息就是道路虚线的坐标数据,由features
属性中的 coordinates
属性指定,此外该GeoJSON
对象中也包含了crs
属性。道路虚线 GeoJSON
对象如下所示:
{"type": "Feature","id": "1-2000007-1","geometry": {"type": "LineString","coordinates": [[113.94405, 22.5358002],[113.9424891, 22.535774],[113.9419385, 22.5357726]]}
}
1.4 道路实线数据
\quad 道路实线数据为线类型数据,描述的交通部门在实际道路路面中绘制的实线,表示车辆不可以变道数据,下图在道路路面中显示了道路实线:
\quad 道路实线数据以 GeoJSON
格式存储在文件中,一条道路包含了多个 GeoJSON
实线对象。该对象主要信息就是道路实线的坐标数据,由features
属性中的 coordinates
属性指定,此外该GeoJSON
对象中也包含了crs
属性。道路实线 GeoJSON
对象如下所示:
{"type": "Feature","id": "1-2000007-0","geometry": {"type": "LineString","coordinates": [[113.9440506, 22.5357686],[113.9424895, 22.5357424],[113.9419385, 22.535741]]}
}
1.5 道路路面方向指示箭头数据
\quad 道路路面方向指示箭头数据为点类型数据,描述的交通部门在实际道路路面中绘制的方向箭头,用于提示驾驶员车辆可前进的方向,下图在道路路面中显示了道路路面方向指示箭头:
\quad 道路路面方向指示箭头数据以 GeoJSON
格式存储在文件中,一条道路包含了多个 GeoJSON
方向指示箭头对象。
\quad 该对象中不仅包含了方向指示箭头的坐标数据,还在特征属性中包含了指示箭头的符号,该符号是png位图格式,下面这个 GeoJSON
对象中, properties
属性中的 symbol
属性指的是 符号对应的位图文件名称,angle
属性指定该符号的旋转角度(单位:弧度), features
属性中的 coordinates
属性指的是该方向指示箭头的坐标。此外该 GeoJSON
对象中也包含了 crs
属性。
\quad 道路路面方向指示箭头 GeoJSON
对象如下所示:
{"type": "FeatureCollection","crs": {"type": "name","properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}},"features": [{"type": "Feature","id": "1-6000000-T0","properties": {"symbol": "1200-0","angle": "-1.491277","size": "2.000000"},"geometry": {"type": "Point","coordinates": [113.9446868, 22.5244148]}}]
}
2 功能实现
\quad 在本示例中,我们将实现交通路网数据的显示、图形平移和缩放、指定初始图形视点、分层控制、根据缩放级别显示相应图层等功能。
2.1 初始视点
\quad anyGraph
的最核心类为 Graph
,图形的展示均从创建该类的实例开始。在第七章 图形交互操作: 视点控制与动画中讲述了“图形初始化时控制视点”的内容,当初始化视点为显示全图时,可在实例化Graph
时通过fullView=true
进行设置。
\quad 以下为实例化 Graph
类,并将初始视点设置为显示全图的代码:
//初始化graph对象,初始视点为显示全图
let graph = new Graph({"target": "graphWrapper","originAtLeftTop": false,"fullView":true
});
\quad 目前许多地图应用,其初始视点通常设置位当前的位置,下面我们来实现这个功能。
\quad 我们首先来分析一下样式数据, 在将WGS84坐标转换为WEB墨卡托投影坐标后,本样例数据的坐标范围为:[12683000, 2574000, 12685600, 2577400]
,其最大宽度为2600米,最大高度为3400米。目前比较主流的的屏幕分辨率为 1920 × 1080 1920 \times 1080 1920×1080 ,在减去浏览器标题和工具栏等位置后,其最大可用范围约为 1900 × 960 1900 \times 960 1900×960 。 在第五章 图形交互操作:平移和缩放中我们讲述了图形分辨率是图形坐标系与像素坐标系的比值,因此在主流的屏幕中当显示全图时,其分辨率为 2600 ÷ 1900 2600 \div 1900 2600÷1900 和 3400 ÷ 960 3400 \div 960 3400÷960 的较大值,即为 3.54 3.54 3.54 。
\quad 最大分辨率为3.54,初始化时通常显示的是一个较小的范围,因此我们假设缺省分辨率为1.2。
\quad 通过浏览器全局变量 navigator
可获取到当前的地理位置坐标,由于样例数据的范围比较小,我们假设一个指定的坐标就是我们当前的位置。
\quad 以下为实例化 Graph
类,并将初始视点设置为指定的位置和大小的代码:
let currentCoord = [12684305, 2576407];
let defaultResolution = 1.2;// 初始化graph对象,指定视点位置
let graph = new Graph({"target": "graphWrapper","originAtLeftTop": false,"view": new View({center: currentCoord, //地图初始中心点resolution: defaultResolution //地图初始显示密度})
});
2.2 装载数据
\quad 在第二章 图形管理类(Graph)中讲述图层的概念,图层(Layers)是一种用于组织和管理图像内容的办法。图层可以将不同的图像元素分开,使得它们可以独立地进行绘制、编辑和操作。
\quad 在本示例中,根据数据的特诊,可将交通路网图形划分为以下几层:
道路中心线层
\quad 道路中心线数据为线类型数据,由于其几何信息较为简单,通常在仅需显示道路位置和走向的时候使用,而在显示道路更为详细的数据时需切换显示道路路面数据。
\quad 在第三章 图层类(Layer)中讲述图层控制的可见性时,讲述过在构造函数中可通过最小分辨率 minResolution
和 最大分辨率 maxResolution
属性来控制图层可见性。
\quad 以下为实例化道路路中线图层,设置了 minResolution = 1.88
,这就意味着在图形缩放时,当前分辨率大于等于 1.88
时将会显示该图层,当前分辨率小于 1.88
时将不显示该图层。
let projection = new WebMercator();
let path = "../../data/sz_road_part/";// 图层:道路-路中线
let layerRoadCenter = new Layer({source: new VectorSource({"fileUrl": path + "export_roadcenter.geojson","projection": projection,"format": new GeoJSONFormat()}),zIndex: 24,name: "道路-路中线",style: { "color": "#999900", "lineWidth": 10 },minResolution: 1.880,visible: true
});
graph.addLayer(layerRoadCenter);
道路路面层
\quad 刚刚我们分析到,在显示道路路中线图层时不显示道路路面图层,而当不显示道路路中线图层时,则需要显示道路路面图层。这个功能依旧通过设置分辨率来实现。
\quad 对于道路路面,需设置当分辨率小于 1.88
时才进行显示,因此需要通过属性 maxResolution
来控制。以下代码中设置了 maxResolution = 1.88
,这就意味着在图形缩放时,当前分辨率小于 1.88
时才会显示该图层。
// 图层:道路-路面
let layerRoadFace = new Layer({source: new VectorSource({"fileUrl": path + "export_redge_face.geojson","projection": projection,"format": new GeoJSONFormat()}),zIndex: 10,name: "道路-路面",style: { "color": "rgba(48, 48, 48, 1)", "fillColor": "rgba(48, 48, 48, 1)", "fillStyle": 1, "lineWidth": 0 },maxResolution: 1.880
});
graph.addLayer(layerRoadFace);
下图为运行效果:
道路虚线层
\quad 道路虚线层显示时必须依附于道路路面,否则就没有意义,因此仍需通过分辨率来控制图层显示。由于道路路面层的最大显示分辨率为 1.88
,因此道路虚线层的最大分比率也必须设置为小于 1.88
时才显示出来 。由于分辨率为1.88
时,其路面还很窄小,因此下面这段代码中将 maxResolution
设置为了 0.62
。
\quad 在第三章 图层类(Layer)中讲述图层的样式,在渲染图层中的数据对象时,既可以为图层中的每一个数据对象指定样式,也可以为图层指定样式。如果数据对象和图层均指定了样式,则数据对象的样式优先。在这几个图层中均是在实例化 DataSource
时通过指定 fileUrl
装载数据,由于 GeoJSON
数据中是不包含样式信息的,因此均是通过指定图层样式指定图形对象的渲染样式。
\quad 对于道路虚线层,不仅仅需要指定线的颜色和线宽,还需指定线型为虚线,其代码如下:
// 图层:道路-虚线
let layerRoadDash = new Layer({source: new VectorSource({"fileUrl": path + "export_line_dash.geojson","projection": projection,"format": new GeoJSONFormat()}),zIndex: 18,name: "道路-虚线",style: { "color": "#FFFFFF", "dash": [6, 6, 6, 6], "lineWidth": 1 },maxResolution: 0.62
});
graph.addLayer(layerRoadDash);
道路实线层
\quad 道路实线层显示属性和道路虚线层类似,也必须依附于道路路面,因此也将 maxResolution
设置为了 0.62
,其图层样式均需设置线的颜色和线宽,缺省线型即为实线。其代码如下:
// 图层:道路-实线
let layerRoadLine = new Layer({source: new VectorSource({"fileUrl": path + "export_line.geojson","projection": projection,"format": new GeoJSONFormat()}),zIndex: 20,name: "道路-实线",style: { "color": "white", "lineWidth": 1 },maxResolution: 0.62
});
graph.addLayer(layerRoadLine);
道路路面方向指示箭头层
\quad 在第四章:图形基本形状中讲述了图形系统中常见的基本形状包括:点、折线、多边形、矩形、圆形、文本、图像等类型。在上面的这几个图层中 “道路路面” 对应的基本形状为 多边形、“道路中心线”、“道路虚线”、“道路实线” 对应的基本形状为 折线类型,而 “道路路面方向指示箭头” 对应的基本形状为点类型。
\quad 点类型在渲染时通常会渲染为圆和三角形等常见的几何形状,而 anyGraph
在渲染点时还提供了内置类型和图标两种渲染方式。内置类型包括了正方形、正五边形、正六边形、四角星、五角星、笑脸、花朵等形状;而图形类型则会将点渲染为位图图标,从而渲染出更为丰富的形状。下图为anyGraph
提供的 内置类型:
下图为anyGraph
渲染的 图标:
\quad 道路路面方向指示箭头为点数据类型,其数据对象的properties
属性中包含了 symbol
属性指定符号对应的位图文件名称,angle
属性指定该符号的旋转角度(单位:弧度)。这些符号显示效果如下图所示:
\quad 由于 symbol
和 angle
属性并非 GeoJSON
格式的标准属性,因此 GeoJSONFormat
并不会将这两个属性加载到数据对象中。可手动调用 VectorSource
类的 loadFile()方法
,在其回调函数中指定点对象的src
和rotation
属性,从而将其渲染为位图。
\quad 道路路面方向指示箭头也必须依附于道路路面,可在实线和虚线渲染后在进行渲染,因此将 maxResolution
设置为了 0.4
。初始化道路路面方向指示箭头层的代码如下:
// 图层:地面方向箭头指示
let layerRoadTurn = new Layer({source: new VectorSource({"projection": projection,"format": new GeoJSONFormat()}),zIndex: 40,name: "地面方向箭头指示",style: { "color": "#DAA520", "size": 16, "centerAsOrigin": true },maxResolution: 0.4,visible: true
});
graph.addLayer(layerRoadTurn);// 加载地面方向箭头数据
layerRoadTurn.getSource().loadFile(path + "export_turn.geojson", function (file) {let datas = layerRoadTurn.getSource().getData();if (datas.length > 0) {// 根据geom的properties修改geom的属性datas.forEach(obj => {let symbol = obj.properties.symbol;let idx = symbol.indexOf("-");let fileName = (idx > 0) ? symbol.substring(0, idx) : symbol;obj.src = path + "images/" + fileName + ".png";obj.rotation = MathUtil.toDegrees(obj.properties.angle);layerRoadTurn.getSource().add2Cache(obj.imgUrl);})}
});
\quad 由于默认情况下 anyGraph
图标点对象的大小不会随着图形的缩放而缩放,始终渲染为指定的大小,多边形则会随着图形的缩放而缩放。在本示例中,随着图形的放大,道路路面将随着放大,我们也希望道路路面方向指示箭头也能进行放大。这个需求可通过图层的动态样式功能来实现,动态样式本质上是一个回调函数,anyGraph
在图形渲染之前将调用该回调函数。因此可在该回调函数中修改图形对象的样式属性,实现动态样式的功能。增加动态样式后的代码如下所示:
// 图层:地面方向箭头指示
let layerRoadTurn = new Layer({source: new VectorSource({"projection": projection,"format": new GeoJSONFormat()}),zIndex: 40,name: "地面方向箭头指示",style: {"color": "#DAA520", "size": 16, "centerAsOrigin": true, "dynamicFn": function (obj, objStyle, viewState) {let res = 0.1;let scale = 0.3;if (viewState.resolution > res * Math.pow(2, 5)) {scale = scale * 0.125;} else if (viewState.resolution > res * Math.pow(2, 4)) {scale = scale * 0.25;} else if (viewState.resolution > res * Math.pow(2, 2)) {scale = scale * 0.5;} else if (viewState.resolution > res * Math.pow(2, 1)) {scale = scale * 1;} else if (viewState.resolution > res * Math.pow(2, 0)) {scale = scale * 2;} else if (viewState.resolution > res * 0.4) {scale = scale * 4;} else {scale = scale * 8;}objStyle.scale = scale;return true;}},maxResolution: 0.4,visible: true
});
graph.addLayer(layerRoadTurn);// 加载地面方向箭头数据
layerRoadTurn.getSource().loadFile(path + "export_turn.geojson", function (file) {let datas = layerRoadTurn.getSource().getData();if (datas.length > 0) {// 根据geom的properties修改geom的属性datas.forEach(obj => {let symbol = obj.properties.symbol;let idx = symbol.indexOf("-");let fileName = (idx > 0) ? symbol.substring(0, idx) : symbol;obj.src = path + "images/" + fileName + ".png";obj.rotation = MathUtil.toDegrees(obj.properties.angle);layerRoadTurn.getSource().add2Cache(obj.imgUrl);})}
});
增加动态样式后的运行效果如下图所示:
2.3 路名标注
\quad 至此,交通路网所提供的的各类数据均已加载完毕了。然而目前的运行效果还缺少一些标注信息,能否将道路名称显示在道路上面呢?
\quad 在数据分析1.1中我们看到道路中心线数据中是包含道路名称的,可使用该数据显示道路名称。
\quad 由于道路中心线图层在初始化时设置了最小密度属性 minResolution = 1.88
,这就意味着在图形缩放时,当前分辨率大于等于 1.88
时将会显示该图层,当前分辨率小于 1.88
时将不显示该图层。如果在该图层中显示标注,也将受到分辨率的限制,因此我们还需增加“道路名称” 图层,通过该层显示道路名称。
\quad anyGraph
的 基本图形形状 “折线” (Polyline) 不仅仅提供了绘制折线的功能,还为折线提供标注的功能。该功能可读取折线的 label
属性,以及折线样式中的 labelStyle
属性,当这两者都存在时将绘制折线标注信息。
\quad 由于已经加载了道路路中线图层,为了避免重复绘制路中线,在“道路名称”图层中设置样式 lineWidth = -1
,从而避免重复绘制折线。同时我们也为该图层设置了动态样式,随着图形的缩放,道路名称也将分几个层次显示。初始化“道路名称”图层的代码如下所示:
let layerRoadName = new Layer({source: new VectorSource({"fileUrl": path + "export_roadcenter.geojson","projection": projection,"format": new GeoJSONFormat()}),zIndex: 50,name: "道路-道路名称",style: {"color": "#FFFFFF00","lineWidth": -1,"labelStyle": { "font": "20px 黑体", "fillColor": "#FF9900" }, "dynamicFn": function (obj, objStyle, viewState) {let res = 1.88;let font;let fillColor;if (viewState.resolution > res * 2) {font = "14px 黑体";fillColor = "#000000";} else if (viewState.resolution > res) {font = "20px 黑体";fillColor = "#000000";} else if (viewState.resolution > res * 0.5) {font = "24px 黑体";fillColor = "#FF9900";} else if (viewState.resolution > res * 0.1) {font = "28px 黑体";fillColor = "#FF9900";} else {font = "32px 黑体";fillColor = "#FF9900";}objStyle.labelStyle.font = font;objStyle.labelStyle.fillColor = fillColor;return true;}},visible: true
});
graph.addLayer(layerRoadName);
\quad 增加 “道路名称” 图层后的运行效果如下图所示:
2.4 图形交互操作:平移和缩放
\quad 在第五章 图形交互操作:平移和缩放中讲述了图形缩放的原理,并讲述了 通过坐标转换实现图形平移缩放,以及通过Canvas渲染上下文对象提供的矩阵变换功能实现图形的平移和缩放功能。
\quad 在讲述通过坐标转换实现图形平移缩放功能时,讲述了比较简单的通过一次函数实现坐标转换功能,也讲述了通过更为通用的3阶矩阵实现平移缩放旋转的坐标转换方法,有兴趣的朋友们可以到那篇文章中复习这些内容。
\quad 在 anyGraph
中分别采用这两种方法实现了图形的缩放与漫游功能,在初始化各图层后即提供了该功能,因此这部分功能无需添加任何代码,开箱即用。
2.5 控件
\quad anyGraph
作为一个成长中的矢量化图形系统二次开发组件,基本功能已基本完成了,而各种扩展功能也正在不断完善之中。以下代码为 交通路网图形增加了三个控件,分别为:图层控制控件、缩放操作控件、显示坐标位置控件。
// 图层控件
graph.addControl(new LayerControl());
graph.addControl(new MousePositionControl());
graph.addControl(new ZoomControl());
\quad 添加控件后的界面效果如下图所示:
图层控制
\quad 图层控制控件是一种用户界面组件,用于在图形中控制图层的显示和隐藏。通过选择或取消各图层名称前面的复选框,可以动态显示或隐藏图形中的图层,从而实现图形的定制和个性化展示。
缩放操作
\quad 缩放操作控件是一种用户界面组件,用于在图形中控制图层的放大和缩小。作为对鼠标缩放和手势缩放的补充,缩放操作控件更加简单和直观,它是基于中心点的缩放,在缩放时还伴有动画效果。
显示坐标位置
\quad 显示坐标位置控件是一种用户界面组件,用于在图形中显示当前鼠标或触摸点的坐标位置。使用显示坐标位置控件,用户可以在图形中快速了解当前位置的坐标。这对于需要精确定位或测量图形上的距离和面积的应用场景非常有用。
2.6 效果展示
初始界面
初始显示指定坐标和密度的位置,如下图所示:
显示道路路中线
执行图形缩小操作后,显示出整个路网的全图,此时仅显示道路路中线和道路名称,如下图所示:
显示道路路面
逐渐放大图形,此时将显示出路面图形,同时隐藏道路路中线图层,如下图所示:
显示道路实线和虚线
继续放大图形,此时将显示出道路实线和道路虚线图层,如下图所示:
显示道路路面方向指示箭头
继续放大图形,此时将显示出道路路面方向指示箭头图层,如下图所示:
功能演示动画
下面这个gif,以动图方式演示了上述功能。
\quad “图形系统实战开发-进阶篇 第十章 应用实例:交通路网” 的内容讲解到这里就结束了,如果觉得对你有帮助有收获,可以关注我们的官方账号,持续关注更多精彩内容。
相关资料
▶ 系列教程及代码资料:https://GraphAnyWhere.com
▶ 图形系统开发实战课程:进阶篇(上)——前言
▶ 图形系统开发实战课程:进阶篇(上)——1.基础知识
▶ 图形系统开发实战课程:进阶篇(上)——2.图形管理类(Graph)
▶ 图形系统开发实战课程:进阶篇(上)——3.图层类(Layer)
▶ 图形系统开发实战课程:进阶篇(上)——4.图形基本形状
▶ 图形系统开发实战课程:进阶篇(上)——5.图形交互操作:平移和缩放
▶ 图形系统开发实战课程:进阶篇(上)——6.图形交互操作:拾取
▶ 图形系统开发实战课程:进阶篇(上)——7.图形交互操作: 视点控制与动画
▶ 图形系统开发实战课程:进阶篇(上)——8.图形样式
▶ 图形系统开发实战课程:进阶篇(上)——9.空间算法(一)
作者信息
作者 : 图形开发学院
CSDN: https://blog.csdn.net/2301_81340430?type=blog
官网:https://graphanywhere.com