5 使用矢量图层
目录
5.6.1 从QgsVectorFileWriter实例创建
节总结了可以使用矢量图层执行各种操作。
这里的大部分工作都是基于QgsVectorLayer
类的方法。
5.1 检索相关属性信息
你可以通过调用QgsVectorLayer
对象的fields()
方法检索一个矢量图层相关字段的信息:
# “layer”是一个QgsVectorLayer实例
for field in layer.fields():
print(field.name(), field.typeName())
5.2 遍历矢量图层
迭代矢量图层要素是最常见的任务之一。下面是执行此任务的简单基本代码示例,并显示有关每个功能的一些信息。该layer
变量被假定为具有一个QgsVectorLayer
对象。
layer = iface.activeLayer()
features = layer.getFeatures()
for feature in features:
# 检索每一个要素的几何形状和属性
print("Feature ID: ", feature.id())
# 获取几何
geom = feature.geometry()
geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType())
if geom.type() == QgsWkbTypes.PointGeometry:
# 几何类型可以是单个或多个类型,
if geomSingleType:
x = geom.asPoint()
print("Point: ", x)
else:
x = geom.asMultiPoint()
print("MultiPoint: ", x)
elif geom.type() == QgsWkbTypes.LineGeometry:
if geomSingleType:
x = geom.asPolyline()
print("Line: ", x, "length: ", geom.length())
else:
x = geom.asMultiPolyline()
print("MultiLine: ", x, "length: ", geom.length())
elif geom.type() == QgsWkbTypes.PolygonGeometry:
if geomSingleType:
x = geom.asPolygon()
print("Polygon: ", x, "Area: ", geom.area())
else:
x = geom.asMultiPolygon()
print("MultiPolygon: ", x, "Area: ", geom.area())
else:
print("Unknown or invalid geometry")
# 获取属性
attrs = feature.attributes()
# attrs是一个列表。它包含要素的所有属性值
print(attrs)
5.3 选择要素
在QGIS桌面中,可以通过不同方式选择要素:用户可以单击要素,在地图画布上绘制矩形或使用表达式过滤器。所选要素通常以不同颜色突出显示(默认为黄色),以引起用户对已选要素的注意。
有时,以编程方式选择要素或更改默认颜色会很有用。
选择所有要素,可以使用selectAll()
方法:
# 获取当前图层(必须是矢量图层)
layer = iface.activeLayer()
layer.selectAll()
使用表达式进行选择,使用selectByExpression()方法:
# 假设当前图层是来自QGIS测试套件的points.shp文件
#(Class(字符串)和Heading(数字)是points.shp中的属性)
layer = iface.activeLayer()
layer.selectByExpression('"Class"=\'B52\' and "Heading" > 10 and "Heading" <70', QgsVectorLayer.SetSelection)
更改选择颜色,可以使用QgsMapCanvas
的setSelectionColor()
方法 :
iface.mapCanvas().setSelectionColor( QColor("red") )
为给定图层的所选要素列表添加要素,您可以调用select()
添加到要素ID列表:
selected_fid = []
#获取从层的第一要素ID
for feature in layer.getFeatures():
selected_fid.append(feature.id())
break
#将这些要素添加到选定的列表
layer.select(selected_fid)
清除选择:
layer.removeSelection()
5.3.1 访问属性
属性可以通过其名称来引用:
print(feature['name'])
或者,可以通过索引引用属性。这比使用名称快一点。例如,获取第一个属性:
print(feature[0])
5.3.2 遍历选中的要素
如果您只需要已选择的要素,则可以使用矢量图层中的方法selectedFeatures()
:
selection = layer.selectedFeatures()
print(len(selection))
for feature in selection:
#做任何你需要的功能
5.3.3 遍历一部分要素
如果要迭代图层中特定的要素子集(例如给定区域内的要素),则必须添加QgsFeatureRequest
对象到getFeatures()方法
。下面是一个例子:
areaOfInterest = QgsRectangle(450290,400520, 450750,400780)
request = QgsFeatureRequest().setFilterRect(areaOfInterest)
for feature in layer.getFeatures(request):
#使用该功能执行任何操作
为了速度,交叉点通常仅使用要素的范围(bbox)来完成。但是有一个标志ExactIntersect
可以确保只返回相交的要素:
request = QgsFeatureRequest().setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.ExactIntersect)
使用setLimit()
您可以限制用户要素的数量。下面是一个例子:
request = QgsFeatureRequest()
request.setLimit(2)
for feature in layer.getFeatures(request):
#循环只有2个要素
如果你需要一个基于属性的过滤器来代替(或另外)一个空间过滤器,如上面的例子所示,你可以构建一个QgsExpression
对象并将其传递给QgsFeatureRequest
函数。下面是一个例子:
#表达式将过滤字段“location_name”
#包含单词“Lake”(不区分大小写)
exp = QgsExpression('location_name ILIKE \'%Lake%\'')
request = QgsFeatureRequest(exp)
有关支持语法(QgsExpression
)的详细信息,请参阅表达式,过滤和计算值。
该请求可用于定义为每个要素的数据检索,因此迭代器返回所有要素,但返回每个要素的部分数据。
#仅返回选定的字段以增加请求
request.setSubsetOfAttributes([0,2])
#更多用户友好的版本
request.setSubsetOfAttributes(['name','id'],layer.fields())
#不返回几何对象以增加请求
request.setFlags(QgsFeatureRequest.NoGeometry)
#仅获取ID为45的要素
request.setFilterFid(45)
#这些选项可以是链式的
request.setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.NoGeometry).setFilterFid(45).setSubsetOfAttributes([0,2])
5.4 修改矢量图层
大多数矢量数据提供者都支持编辑图层数据。有时它们仅支持编辑子集。使用capabilities()
功能可以找出支持的功能集。
caps = layer.dataProvider().capabilities()
#检查是否支持特定功能:
if caps & QgsVectorDataProvider.DeleteFeatures:
print('The layer supports DeleteFeatures')
有关所有可用功能的列表,请参阅 :API Documentation of QgsVectorDataProvider
打印图层功能的文本描述,结果是以逗号分隔的列表,您可以使用capabilitiesString()
,如下例所示:
caps_string = layer.dataProvider().capabilitiesString()
# Print:
# 'Add Features, Delete Features, Change Attribute Values, Add Attributes,
# Delete Attributes, Rename Attributes, Fast Access to Features at ID,
# Presimplify Geometries, Presimplify Geometries with Validity Check,
# Transactions, Curved Geometries'
通过使用以下任何方法进行矢量图层编辑,更改将直接提交到基础数据存储(文件,数据库等)。如果您只想进行临时更改,请跳到下一节5.4.4 使用编辑缓冲区修改矢量图层。
小贴士:如果您在QGIS内部(从控制台或从插件中),可能需要强制重绘地图画布,以便查看您对几何、样式或属性所做的更改:
# 如果启用了缓存,简单的画布刷新可能不足以触发重绘,并且必须清除图层的缓存图像。
if iface.mapCanvas().isCachingEnabled():
layer.triggerRepaint()
else:
iface.mapCanvas().refresh()
5.4.1 添加要素
创建一些QgsFeature
实例并将它们的列表传递给提供者的 addFeatures()
方法。它将返回两个值:result(true / false)和添加的要素列表(它们的ID由数据存储设置)。
设置要素的属性,初始化要素,可以通过传递QgsFields
对象(可以从fields()
矢量图层的方法获取 )或调用initAttributes()
传递要添加的字段数。
if caps & QgsVectorDataProvider.AddFeatures:
feat = QgsFeature(layer.fields())
feat.setAttributes([0, 'hello'])
#或按key或index设置单个属性:
feat.setAttribute('name', 'hello')
feat.setAttribute(0, 'hello')
feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(123, 456)))
(res, outFeats) = layer.dataProvider().addFeatures([feat])
5.4.2 删除要素
删除某些要素,只需提供其功能ID列表即可。
if caps & QgsVectorDataProvider.DeleteFeatures:
res = layer.dataProvider().deleteFeatures([5, 10])
5.4.3 修改要素
可以更改要素的几何图形或更改某些属性。以下示例首先更改索引为0和1的属性值,然后更改要素的几何。
fid = 100 #我们将修改的要素ID
if caps & QgsVectorDataProvider.ChangeAttributeValues:
attrs = { 0 : "hello", 1 : 123 }
layer.dataProvider().changeAttributeValues({ fid : attrs })
if caps & QgsVectorDataProvider.ChangeGeometries:
geom = QgsGeometry.fromPointXY(QgsPointXY(111,222))
layer.dataProvider().changeGeometryValues({ fid : geom })
小贴士:支持QgsVectorLayerEditUtils类进行仅几何编辑
如果您只需要更改几何图形,可以考虑使用QgsVectorLayerEditUtils
,它提供一些有用的方法来编辑几何图形(平移、插入或移动顶点等)。
5.4.4 使用编辑缓冲区修改矢量图层
在QGIS应用程序中编辑矢量时,必须首先为特定图层开始编辑模式,然后进行一些修改,最后提交(或回滚)更改。您所做的所有更改在您提交之前都不会写入——它们保留在图层的内存编辑缓冲区中。也可以通过编程方式使用此功能——它仅仅是是矢量图层编辑的另一种方法,可以补充直接使用数据提供者。在为矢量图层编辑提供一些GUI工具时使用此选项,因为这将允许用户决定是否提交/回滚并允许使用undo / redo。提交更改时,编辑缓冲区中的所有更改都将保存到数据提供者中。
这些方法类似于我们在提供程序中看到的方法,但它们在QgsVectorLayer
对象上调用。
使这些方法起作用,图层必须处于编辑模式。开始编辑模式,使用startEditing()
方法。要停止编辑,请使用commitChanges()
或rollBack()
方法。第一个将提交对数据源的所有更改,而第二个将丢弃它们,并且根本不会修改数据源。
要确定图层是否处于编辑模式,使用isEditable()
方法。
这里有一些示例演示,如何使用这些编辑方法。
from qgis.PyQt.QtCore import QVariant
#添加两个要素(QgsFeature实例)
layer.addFeatures([feat1,feat2])
#删除指定ID的要素
layer.deleteFeature(fid)
#为要素设置新几何(QgsGeometry实例)。
layer.changeGeometry(fid, geometry)
#将给定字段索引(int)的属性更新为给定值
layer.changeAttributeValue(fid, fieldIndex, value)
#添加新的字段
layer.addAttribute(QgsField("mytext", QVariant.String))
#删除字段
layer.deleteAttribute(fieldIndex)
为了使撤消/重做正常工作,上述调用必须包含在撤消命令中。(如果您不关心撤消/重做并希望立即存储更改,那么通过5.4 修改矢量图层,您将可以更轻松地完成工作 。)
以下是使用撤消功能的方法:
layer.beginEditCommand("Feature triangulation")
#...调用图层的编辑方法......
if problem_occurred:
layer.destroyEditCommand()
return
#...更多编辑...
layer.endEditCommand()
beginEditCommand()
方法将创建一个内部“活动”命令,并将记录矢量图层中的后续更改。随着对endEditCommand()
命令的调用被推送到撤销栈,用户将能够从GUI撤消/重做它。如果在执行更改时出现问题, destroyEditCommand()
方法将删除该命令并回滚此命令处于活动状态时所做的所有更改。
您还可以使用with edit(layer)
——将提交和回滚包装成更具语义的代码块中,如下例所示:
with edit(layer):
feat = next(layer.getFeatures())
feat[0] = 5
layer.updateFeature(feat)
结束后将自动调用commitChanges()
。如果发生任何异常,它将进行rollBack()
所有更改。如果遇到问题commitChanges()
(当方法返回False时)将引发异常QgsEditError
。
5.4.5 添加和删除字段
添加字段(属性),您需要指定字段定义列表。要删除字段,只需提供字段索引列表。
from qgis.PyQt.QtCore import QVariant
if caps & QgsVectorDataProvider.AddAttributes:
res = layer.dataProvider().addAttributes(
[QgsField("mytext", QVariant.String),
QgsField("myint", QVariant.Int)])
if caps & QgsVectorDataProvider.DeleteAttributes:
res = layer.dataProvider().deleteAttributes([0])
在数据提供程者中添加或删除字段后,需要更新图层的字段,因为更改不会自动传播。
layer.updateFields()
小贴士:使用with
命令直接保存更改
使用with edit(layer):
更改将在结束后调用commitChanges()
自动提交。如果发生任何异常,它将所有rollBack()更改。请参见5.4.4 使用编辑缓冲区修改矢量图层
5.5 使用空间索引
如果需要对矢量图层进行频繁查询,空间索引可以显著提高代码的性能。例如,想象一下,您正在编写插值算法,并且对于给定位置,您需要知道点图层中最近的10个点,以便使用这些点来计算插值。如果没有空间索引,QGIS找到这10个点的唯一方法是计算从每个点到指定位置的距离,然后比较这些距离。这可能是一项非常耗时的任务,特别是如果需要在多个位置重复这项任务。如果图层存在空间索引,则操作更有效。
可以将没有空间索引的图层视为电话簿,其中不对电话号码进行排序或索引。找到给定人员的电话号码的唯一方法是从头开始阅读,直到找到它为止。
默认情况下,QGIS矢量图层不会创建空间索引,但您可以轻松创建它们。这是你要做的:
-
使用
QgsSpatialIndex()
类创建空间索引:index = QgsSpatialIndex()
-
向索引添加要素——索引获取
QgsFeature
对象并将其添加到内部数据结构。您可以手动创建对象,也可以使用先前提供者的getFeatures()
方法。index.insertFeature(feat)
-
或者,您可以批量加载图层的所有要素
index = QgsSpatialIndex(layer.getFeatures())
-
一旦空间索引填充了一些值,您就可以进行一些查询
#以数组形式返回五个最近要素的ID nearest = index.nearestNeighbor(QgsPointXY(25.4, 12.7), 5) #以数组形式返回与矩形相交的要素 intersect = index.intersects(QgsRectangle(22.5, 15.3, 23.1, 17.2))
5.6 创建矢量图层
有几种方法可以生成矢量图层数据集:
-
QgsVectorFileWriter
类:用于将矢量文件写到硬盘的一个方便的类,保存整个矢量图层通过静态调用writeAsVectorFormat()
,或创建类的实例并调用addFeature()
方法。该类支持OGR支持的所有矢量格式(GeoPackage,Shapefile,GeoJSON,KML等)。 -
QgsVectorLayer
类:实例化一个数据提供者,它解释了提供的数据源路径(url)以连接和访问数据。它可以用来创建临时的、基于内存存储的图层(memory
),还可以连接到OGR数据集(ogr
),数据库(postgres
,spatialite
,mysql
,mssql
),更多(wfs
,gpx
,delimitedtext
...)。
5.6.1 从QgsVectorFileWriter
实例创建
#写入GeoPackage(默认)
error = QgsVectorFileWriter.writeAsVectorFormat(layer, "/path/to/folder/my_data", "")
if error[0] == QgsVectorFileWriter.NoError:
print("success!")
#使用UTF-8编码写入ESRI Shapefile格式数据集
error = QgsVectorFileWriter.writeAsVectorFormat(layer, "/path/to/folder/my_esridata","UTF-8", driverName="ESRI Shapefile")
if error[0] == QgsVectorFileWriter.NoError:
print("success again!")
第三个(强制)参数指定输出的编码。只有一些驱动程序需要这个才能正确操作 - Shapefile就是其中之一(其他驱动程序将忽略此参数)。如果使用国际(非US-ASCII)字符,则指定正确的编码非常重要。
还可以指定目标CRS——如果将一个有效的QgsCoordinateReferenceSystem
实例作为第四个参数,则将该层转换为该CRS。
有关有效的驱动程序的名称,请调用supportedFiltersAndFormats
方法或查阅OGR支持的格式——您应该将“Code”列中的值作为驱动程序名称传递。
(可选)您可以设置是仅导出选中的要素,传递更多驱动程序特定的选项以进行创建,还是告诉编写者不要创建属性...还有许多其他(可选)参数; 请参阅QgsVectorFileWriter
的详细信息
5.6.2 直接从要素创建
from qgis.PyQt.QtCore import QVariant
#为要素属性定义字段。需要QgsFields对象
fields = QgsFields()
fields.append(QgsField("first", QVariant.Int))
fields.append(QgsField("second", QVariant.String))
"""创建一个矢量文件编写器的实例,它将创建矢量文件
参数:
1. 新文件的路径(如果已存在则失败)
2. 属性编码
3. 字段映射
4. 几何类型 - WKBTYPE枚举
5. 图层的空间参考(QgsCoordinateReferenceSystem的实例) - 可选
6. 输出文件的驱动程序名称"""
writer = QgsVectorFileWriter("my_shapes.shp", "UTF-8", fields, QgsWkbTypes.Point, driverName="ESRI Shapefile")
if writer.hasError() != QgsVectorFileWriter.NoError:
print("Error when creating shapefile: ", w.errorMessage())
#添加一个要素
fet = QgsFeature()
fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
fet.setAttributes([1, "text"])
writer.addFeature(fet)
#删除writer以将要素保存到磁盘
del writer
5.6.3 从QgsVectorLayer
实例创建
在QgsVectorLayer
类支持的所有数据提供者中,让我们关注基于内存的图层。内存提供者主要供插件或第三方应用程序开发人员使用。它不会将数据存储在磁盘上,允许开发人员为某些临时图层快速在后台使用。
提供者支持string,int和double字段。
内存提供者还支持空间索引,通过调用提供者的createSpatialIndex()
函数来启用。创建空间索引后,您将能够更快地迭代较小区域内的要素(因为没有必要遍历所有要素,只有遍历指定矩形内的要素)。
通过将"memory"
作为QgsVectorLayer
构造函数的参数来创建内存提供者。
这个构造函数也需要定义图层几何类型的URI,与多种中的一种:"Point"
,"LineString"
,"Polygon"
,"MultiPoint"
, "MultiLineString"
,或"MultiPolygon"
。
URI还可以指定坐标参考系统、字段和索引。语法是:
-
crs=definition
指定坐标参考系统,其中定义可以是接受的任何形式
QgsCoordinateReferenceSystem.createFromString
-
index=yes
指定提供者将使用空间索引
-
field=name:type(length,precision)
指定图层的属性。该属性具有名称,并且可选的类型(integer, double, or string),长度和精度。可能有多个字段定义。
以下URI示例包含所有这些选项
"Point?crs=epsg:4326&field=id:integer&field=name:string(20)&index=yes"
以下示例代码说明了如何创建和填充内存提供者
from qgis.PyQt.QtCore import QVariant
# 创建图层
vl = QgsVectorLayer("Point", "temporary_points", "memory")
pr = vl.dataProvider()
# 添加字段
pr.addAttributes([QgsField("name", QVariant.String),QgsField("age", QVariant.Int),QgsField("size", QVariant.Double)])
vl.updateFields() #告诉矢量图层从提供者获取更改
#添加一个要素
fet = QgsFeature()
fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
fet.setAttributes(["Johny", 2, 0.3])
pr.addFeatures([fet])
#在添加新要素时更新图层的范围,因为提供者中的范围更改不会传播到图层
vl.updateExtents()
最后,让我们检查一切是否顺利
#显示一些统计
print("fields:", len(pr.fields()))
print("features:", pr.featureCount())
e = vl.extent()
print("extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())
#遍历要素
features = vl.getFeatures()
for fet in features:
print("F:", fet.id(), fet.attributes(), fet.geometry().asPoint())
5.7 矢量图层的外观(符号系统)
渲染矢量图层时,数据的外观由渲染器和与图层关联的符号提供 。符号是负责绘制要素的可视化表现的类,而渲染器确定将用于特定要素的符号。
可以获得给定图层的渲染器,如下所示:
renderer = layer.renderer()
有了这个参考,让我们来探讨一下
print("Type:", renderer.type())
QGIS核心库中有几种已知的渲染器类型:
类型 | 类 | 描述 |
---|---|---|
singleSymbol | QgsSingleSymbolRenderer | 使用相同的符号呈现所有要素 |
categorizedSymbol | QgsCategorizedSymbolRenderer | 使用每个类别的不同符号呈现要素 |
graduatedSymbol | QgsGraduatedSymbolRenderer | 为每个值范围使用不同的符号呈现要素 |
可能还有一些自定义渲染器类型,所以永远不要假设只有这些类型。您可以查询应用程序QgsRendererRegistry
以查找当前可用的渲染器:
print(QgsApplication.rendererRegistry().renderersList())
# Print:
['nullSymbol',
'singleSymbol',
'categorizedSymbol',
'graduatedSymbol',
'RuleRenderer',
'pointDisplacement',
'pointCluster',
'invertedPolygonRenderer',
'heatmapRenderer',
'25dRenderer']
可以以文本形式获取渲染器的内容 - 可用于调试
print(renderer.dump())
5.7.1 单一符号渲染器
您可以通过调用symbol()
方法获取用于渲染的符号,使用setSymbol()
方法更改它(C ++开发人员注意:渲染器将获取符号的所有权。)
您可以通过调用setSymbol()
并传递适当的符号实例来更改特定矢量图层的符号。点,线和多边形图层的符号可以通过调用相应的类(QgsMarkerSymbol
,QgsLineSymbol
和 QgsFillSymbol
)的 createSimple()
方法来创建
传递给createSimple()
的字典参数,设置符号的样式属性。
例如,您可以通过调用setSymbol()
并传递QgsMarkerSymbol
实例, 来替换特定点图层的符号,如下面的代码示例所示:
symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'red'})
layer.renderer().setSymbol(symbol)
#显示更改
layer.triggerRepaint()
name
表示符号的形状,可以是以下任何一种:
-
circle
-
square
-
cross
-
rectangle
-
diamond
-
pentagon
-
triangle
-
equilateral_triangle
-
star
-
regular_star
-
arrow
-
filled_arrowhead
-
x
获取符号实例的第一个符号图层的完整属性列表,可以按照示例代码进行操作:
print(layer.renderer().symbol().symbolLayers()[0].properties())
# Prints
{'angle': '0',
'color': '0,128,0,255',
'horizontal_anchor_point': '1',
'joinstyle': 'bevel',
'name': 'circle',
'offset': '0,0',
'offset_map_unit_scale': '0,0',
'offset_unit': 'MM',
'outline_color': '0,0,0,255',
'outline_style': 'solid',
'outline_width': '0',
'outline_width_map_unit_scale': '0,0',
'outline_width_unit': 'MM',
'scale_method': 'area',
'size': '2',
'size_map_unit_scale': '0,0',
'size_unit': 'MM',
'vertical_anchor_point': '1'}
如果要更改某些属性,这可能很有用:
#您可以更改单个属性...
layer.renderer().symbol().symbolLayer(0).setSize(3)
#...但并非所有属性都可以从方法访问,
#你也可以完全替换符号:
props = layer.renderer().symbol().symbolLayer(0).properties()
props['color'] = 'yellow'
props['name'] = 'square'
layer.renderer().setSymbol(QgsMarkerSymbol.createSimple(props))
#显示更改
layer.triggerRepaint()
5.7.2 分类符号渲染器
使用分类渲染器时,可以查询和设置:使用 classAttribute()
和setClassAttribute()
方法。
获取类别列表:
for cat in renderer.categories():
print("{}: {} :: {}".format(cat.value(), cat.label(), cat.symbol()))
其中value()
是类别之间用于区别的值, label()
是用于类别描述的文本,symbol()
方法返回所分配的符号。
渲染器通常还存储用于分类的原始符号和颜色渐变:sourceColorRamp()
和sourceSymbol()
方法。
5.7.3 渐变符号渲染器
此渲染器与上面描述的分类符号渲染器非常相似,但它不是每个类的一个属性值,而是使用值范围,因此只能用于数字属性。
了解有关渲染器中使用的范围的更多信息
for ran in renderer.ranges():
print("{} - {}: {} {}".format(
ran.lowerValue(),
ran.upperValue(),
ran.label(),
ran.symbol()
))
您可以再次使用 classAttribute
(查找分类属性名称) sourceSymbol
和sourceColorRamp
方法。此外,还有一种mode
方法可以确定范围的创建方式:使用等间隔,分位数或其他方法。
如果您希望创建自己的渐变符号渲染器,则可以执行此操作,如下面的示例代码段所示(这将创建一个简单的两个类别)
from qgis.PyQt import QtGui
myVectorLayer = QgsVectorLayer(myVectorPath, myName, 'ogr')
myTargetField = 'target_field'
myRangeList = []
myOpacity = 1
#让我们的第一符号和范围...
myMin = 0.0
myMax = 50.0
myLabel = 'Group 1'
myColour = QtGui.QColor('#ffee00')
mySymbol1 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
mySymbol1.setColor(myColour)
mySymbol1.setOpacity(myOpacity)
myRange1 = QgsRendererRange(myMin, myMax, mySymbol1, myLabel)
myRangeList.append(myRange1)
#now创建另一个符号和范围...
myMin = 50.1
myMax = 100
myLabel = 'Group 2'
myColour = QtGui.QColor('#00eeff')
mySymbol2 = QgsSymbol.defaultSymbol(
myVectorLayer.geometryType())
mySymbol2.setColor(myColour)
mySymbol2.setOpacity(myOpacity)
myRange2 = QgsRendererRange(myMin, myMax, mySymbol2, myLabel)
myRangeList.append(myRange2)
myRenderer = QgsGraduatedSymbolRenderer('', myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRenderer.EqualInterval)
myRenderer.setClassAttribute(myTargetField)
myVectorLayer.setRenderer(myRenderer)
QgsProject.instance().addMapLayer(myVectorLayer)
5.7.4 使用符号
对于符号的表示,QgsSymbol
基类有三个派生类:
-
QgsMarkerSymbol——
用于点功能 -
QgsLineSymbol
——用于线路功能 -
QgsFillSymbol
——用于面要素
每个符号由一个或多个符号图层(从QgsSymbolLayer
派生的类)。符号图层执行实际渲染,符号类本身仅用作符号图层的容器。
拥有一个符号实例(例如来自渲染器),可以探索它:type
方法说明它是标记、线还是填充符号。有一种dump
方法可以返回符号的简短描述。获取符号图层列表:
for i in range(symbol.symbolLayerCount()):
lyr = symbol.symbolLayer(i)
print("{}: {}".format(i, lyr.layerType()))
找出符号的颜色使用color
方法,setColor
改变其颜色。使用标记符号,您还可以使用size
和angle
方法查询符号大小和旋转。对于线符号,width
方法返回线宽。
默认情况下,大小和宽度以毫米为单位,角度以度为单位。
使用符号图层
如前所述,符号层(QgsSymbolLayer
的子类)决定要素的外观。有一些基本的符号图层类用于一般用途。可以实现新的符号图层类型,从而任意定制要素的呈现方式。layerType()
方法唯一地标识符号图层类——基本类和默认类SimpleMarker
,SimpleLine
以及SimpleFill
符号图层类型。
您可以使用以下代码获取可以为给定符号图层类创建的符号图层类型的完整列表:
from qgis.core import QgsSymbolLayerRegistry
myRegistry = QgsApplication.symbolLayerRegistry()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbol.Marker):
print(item)
输出:
EllipseMarker
FilledMarker
FontMarker
GeometryGenerator
SimpleMarker
SvgMarker
VectorField
QgsSymbolLayerRegistry
类管理一个所有可用的符号层类型的数据库。
要访问符号图层数据,请使用其properties()
返回属性的键值字典的方法,该字典决定外观。每个符号图层类型都有一组特定的属性。此外,还有通用的方法color
,size
,angle
和 width
,与他们定制者的副本。当然,尺寸和角度仅适用于标记符号图层和线符号图层的宽度。
创建自定义符号图层类型
想象一下,您想要自定义数据的呈现方式。您可以创建自己的符号图层类,以完全按照您的意愿绘制要素。以下是绘制具有指定半径的红色圆圈的标记示例:
from qgis.core import QgsMarkerSymbolLayer
from qgis.PyQt.QtGui import QColor
class FooSymbolLayer(QgsMarkerSymbolLayer):
def __init__(self, radius=4.0):
QgsMarkerSymbolLayer.__init__(self)
self.radius = radius
self.color = QColor(255,0,0)
def layerType(self):
return "FooMarker"
def properties(self):
return { "radius" : str(self.radius) }
def startRender(self, context):
pass
def stopRender(self, context):
pass
def renderPoint(self, point, context):
# 渲染取决于是否选择了符号 (QGIS >= 1.5)
color = context.selectionColor() if context.selected() else self.color
p = context.renderContext().painter()
p.setPen(color)
p.drawEllipse(point, self.radius, self.radius)
def clone(self):
return FooSymbolLayer(self.radius)
layerType
方法确定符号图层的名称; 它必须在所有符号层中是唯一的。properties
方法用于属性的持久化。clone
方法必须返回符号图层的副本,其中所有属性完全相同。最后,渲染方法: startRender
在渲染第一个要素之前被调用,stopRender
渲染完成时被调用,renderPoint
渲染时被调用。点的坐标已经转换为输出坐标。
对于折线和多边形,唯一的区别在于渲染方法:您将使用 renderPolyline
——接收线列表,renderPolygon
——接收外环上的点列表作为第一个参数和内环列表(或无)作为第二个参数。
通常可以方便地添加用于设置符号图层类型属性的GUI,以允许用户自定义外观:在上面的示例中,我们可以让用户设置圆半径。以下代码实现了这样的小控件
from qgis.gui import QgsSymbolLayerWidget
class FooSymbolLayerWidget(QgsSymbolLayerWidget):
def __init__(self, parent=None):
QgsSymbolLayerWidget.__init__(self, parent)
self.layer = None
#设置简单的UI
self.label = QLabel("Radius:")
self.spinRadius = QDoubleSpinBox()
self.hbox = QHBoxLayout()
self.hbox.addWidget(self.label)
self.hbox.addWidget(self.spinRadius)
self.setLayout(self.hbox)
self.connect(self.spinRadius, SIGNAL("valueChanged(double)"), \
self.radiusChanged)
def setSymbolLayer(self, layer):
if layer.layerType() != "FooMarker":
return
self.layer = layer
self.spinRadius.setValue(layer.radius)
def symbolLayer(self):
return self.layer
def radiusChanged(self, value):
self.layer.radius = value
self.emit(SIGNAL("changed()"))
此窗口小控件可以嵌入到符号属性对话框中。在符号属性对话框中选择符号图层类型时,它会创建符号图层的实例和符号图窗口小控件的实例。然后它调用setSymbolLayer
方法将符号图层分配给窗口小控件。在该方法中,小控件应该更新UI以反映符号层的属性。symbolLayer
方法用于通过属性对话框再次检索符号图层,以将其用于符号。
在每次更改属性时,窗口小控件都应发出changed()
信号,让属性对话框更新符号预览。
现在我们只缺少最后的粘合剂:让QGIS了解这些新类。这是通过将符号图层添加到注册表来完成的。也可以在不将其添加到注册表的情况下使用符号图层,但某些功能不起作用:例如,使用自定义符号图层加载项目文件或无法在GUI中编辑图层的属性。
我们必须为符号图层创建元数据
from qgis.core import QgsSymbol, QgsSymbolLayerAbstractMetadata, QgsSymbolLayerRegistry
class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):
def __init__(self):
QgsSymbolLayerAbstractMetadata.__init__(self, "FooMarker", QgsSymbol.Marker)
def createSymbolLayer(self, props):
radius = float(props["radius"]) if "radius" in props else 4.0
return FooSymbolLayer(radius)
def createSymbolLayer(self, props):
radius = float(props["radius"]) if "radius" in props else 4.0
return FooSymbolLayer(radius)
QgsApplication.symbolLayerRegistry().addSymbolLayerType(FooSymbolLayerMetadata())
您应该将图层类型(与图层返回的相同)和符号类型(marker/line/fill)传递给父类的构造函数。createSymbolLayer()
方法负责使用props字典中指定的属性创建符号图层的实例。并且有一种createSymbolLayerWidget()
方法可以返回此符号图层类型的设置小控件。
最后一步是将此符号图层添加到注册表中 -——我们完成了。
5.7.5 创建自定义渲染器
如果要自定义如何选择符号以呈现要素的规则,则创建新的渲染器可能很有用。可能希望这样做的一些用例:符号由字段组合确定,符号大小根据当前比例而变化等。
下面的代码显示了一个简单的自定义渲染器,它可以创建两个标记符号,并为每个要素随机选择其中一个
import random
from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer
class RandomRenderer(QgsFeatureRenderer):
def __init__(self, syms=None):
QgsFeatureRenderer.__init__(self, "RandomRenderer")
self.syms = syms if syms else [QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))]
def symbolForFeature(self, feature):
return random.choice(self.syms)
def startRender(self, context, vlayer):
for s in self.syms:
s.startRender(context)
def stopRender(self, context):
for s in self.syms:
s.stopRender(context)
def usedAttributes(self):
return []
def clone(self):
return RandomRenderer(self.syms)
from qgis.gui import QgsRendererWidget
class RandomRendererWidget(QgsRendererWidget):
def __init__(self, layer, style, renderer):
QgsRendererWidget.__init__(self, layer, style)
if renderer is None or renderer.type() != "RandomRenderer":
self.r = RandomRenderer()
else:
self.r = renderer
# setup UI
self.btn1 = QgsColorButton()
self.btn1.setColor(self.r.syms[0].color())
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.btn1)
self.setLayout(self.vbox)
self.btn1.clicked.connect(self.setColor1)
def setColor1(self):
color = QColorDialog.getColor(self.r.syms[0].color(), self)
if not color.isValid(): return
self.r.syms[0].setColor(color)
self.btn1.setColor(self.r.syms[0].color())
def renderer(self):
return self.r
父类QgsFeatureRenderer
的构造函数需要一个渲染器名称(在渲染器中必须是唯一的)。symbolForFeature
方法是决定将用于特定特征的符号的 方法。 startRender
和stopRender
负责符号渲染的初始化/完成。usedAttributes
方法可以返回渲染器期望存在的字段名称列表。最后,clone
方法应返回渲染器的副本。
与符号图层一样,可以附加GUI以配置渲染器。它必须来源于QgsRendererWidget
。以下示例代码创建一个允许用户设置第一个符号的按钮
from qgis.gui import QgsRendererWidget, QgsColorButton
class RandomRendererWidget(QgsRendererWidget):
def __init__(self, layer, style, renderer):
QgsRendererWidget.__init__(self, layer, style)
if renderer is None or renderer.type() != "RandomRenderer":
self.r = RandomRenderer()
else:
self.r = renderer
# setup UI
self.btn1 = QgsColorButton()
self.btn1.setColor(self.r.syms[0].color())
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.btn1)
self.setLayout(self.vbox)
self.connect(self.btn1, SIGNAL("clicked()"), self.setColor1)
def setColor1(self):
color = QColorDialog.getColor(self.r.syms[0].color(), self)
if not color.isValid(): return
self.r.syms[0].setColor(color)
self.btn1.setColor(self.r.syms[0].color())
def renderer(self):
return self.r
构造函数接收当前图层(QgsVectorLayer
),全局样式(QgsStyle
)和当前渲染器的实例。如果没有渲染器或渲染器具有不同的类型,它将被我们的新渲染器替换,否则我们将使用当前渲染器(已经是我们需要的类型)。应更新窗口小控件内容以显示渲染器的当前状态。当接受渲染器对话框时,将renderer
调用窗口小控件的方法以获取当前渲染器 - 它将被分配给该图层。
最后一个缺失的位是渲染器元数据和注册表中的注册,否则使用渲染器加载图层将不起作用,用户将无法从渲染器列表中选择它。让我们完成RandomRenderer示例
from qgis.core import QgsRendererAbstractMetadata,QgsRendererRegistry,QgsApplication
class RandomRendererMetadata(QgsRendererAbstractMetadata):
def __init__(self):
QgsRendererAbstractMetadata.__init__(self, "RandomRenderer", "Random renderer")
def createRenderer(self, element):
return RandomRenderer()
def createRendererWidget(self, layer, style, renderer):
return RandomRendererWidget(layer, style, renderer)
QgsApplication.rendererRegistry().addRenderer(RandomRendererMetadata())
与符号图层类似,抽象元数据构造函数等待渲染器名称,对用户可见的名称以及渲染器图标的可选的名称。createRenderer
方法传递一个QDomElement
实例,该实例可用于从DOM树恢复渲染器的状态。createRendererWidget
方法创建配置小控件。如果渲染器没有GUI,它不必存在或可以返回None
。
要将图标与渲染器关联,可以在QgsRendererAbstractMetadata
构造函数中将其指定为第三个(可选)参数 - RandomRendererMetadata __init__()
函数中的基类构造函数将变为
QgsRendererAbstractMetadata.__init__(self,
"RandomRenderer",
"Random renderer",
QIcon(QPixmap("RandomRendererIcon.png", "png")))
也可以使用元数据类的方法setIcon
在以后的任何时间关联该图标。图标可以从文件加载(如上所示),也可以从Qt资源加载 (PyQt5包含Python的.qrc编译器)。
5.8 更多主题
TODO:
-
创建/修改符号
-
使用style(
QgsStyle
) -
使用颜色斜坡(
QgsColorRamp
) -
探索符号图层和渲染器注册表
写在最后:文中难免有翻译错误,欢迎您的指正。完整版翻译请移步PyQGIS开发者手册-完整版