使用矢量层¶

Docs in progress for 'QGIS testing'. Visit https://docs.qgis.org/3.4 for QGIS 3.4 docs and translations.

本节总结了可以使用向量层执行的各种操作。

这里的大多数工作都是基于 QgsVectorLayer 班级。

检索有关属性的信息

通过调用 fields() 在一 QgsVectorLayer 对象:

# "layer" is a QgsVectorLayer instance
for field in layer.fields():
    print(field.name(), field.typeName())

在矢量层上迭代

迭代向量层中的特性是最常见的任务之一。下面是执行此任务并显示有关每个功能的一些信息的简单基本代码示例。这个 layer 假设变量具有 QgsVectorLayer 对象。

layer = iface.activeLayer()
features = layer.getFeatures()

for feature in features:
    # retrieve every feature with its geometry and attributes
    print("Feature ID: ", feature.id())
    # fetch geometry
    # show some information about the feature geometry
    geom = feature.geometry()
    geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType())
    if geom.type() == QgsWkbTypes.PointGeometry:
        # the geometry type can be of single or multi type
        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")
    # fetch attributes
    attrs = feature.attributes()
    # attrs is a list. It contains all the attribute values of this feature
    print(attrs)

选择功能

在QGIS桌面上,可以通过不同的方式选择功能:用户可以单击一个功能,在地图画布上绘制一个矩形,或者使用表达式过滤器。所选功能通常以不同的颜色突出显示(默认为黄色),以吸引用户对所选内容的注意。

有时,通过编程选择功能或更改默认颜色可能很有用。

要选择所有功能,请 selectAll() 方法可以使用:

# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
layer.selectAll()

要使用表达式进行选择,请使用 selectByExpression() 方法:

# Assumes that the active layer is points.shp file from the QGIS test suite
# (Class (string) and Heading (number) are attributes in points.shp)
layer = iface.activeLayer()
layer.selectByExpression('"Class"=\'B52\' and "Heading" > 10 and "Heading" <70', QgsVectorLayer.SetSelection)

更改可使用的选择颜色 setSelectionColor() 方法 QgsMapCanvas 如下例所示:

iface.mapCanvas().setSelectionColor( QColor("red") )

要将要素添加到给定图层的“选定要素”列表中,可以调用 select() 将功能ID列表传递给它:

selected_fid = []

# Get the first feature id from the layer
for feature in layer.getFeatures():
    selected_fid.append(feature.id())
    break

# Add these features to the selected list
layer.select(selected_fid)

要清除所选内容:

layer.removeSelection()

访问属性

属性可以通过其名称引用:

print(feature['name'])

或者,可以通过索引引用属性。这比使用名称快一点。例如,要获取第一个属性:

print(feature[0])

迭代选定的功能

如果只需要选定的功能,则可以使用 selectedFeatures() 矢量层的方法:

selection = layer.selectedFeatures()
print(len(selection))
for feature in selection:
    # do whatever you need with the feature

迭代功能子集

如果您想要迭代一个层中的一个给定的特性子集,例如那些在给定区域内的特性子集,您必须添加一个 QgsFeatureRequest 对象到 getFeatures() 打电话。下面是一个例子:

areaOfInterest = QgsRectangle(450290,400520, 450750,400780)

request = QgsFeatureRequest().setFilterRect(areaOfInterest)

for feature in layer.getFeatures(request):
    # do whatever you need with the feature

为了提高速度,交叉点通常只使用特征的边界框。但是有一个标志 ExactIntersect 确保只返回相交特征:

request = QgsFeatureRequest().setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.ExactIntersect)

setLimit() 您可以限制请求的功能的数量。下面是一个例子:

request = QgsFeatureRequest()
request.setLimit(2)
for feature in layer.getFeatures(request):
    # loop through only 2 features

如果您需要一个基于属性的过滤器,而不是(或另外)一个空间过滤器,如上面的示例所示,您可以构建一个 QgsExpression 对象并将其传递给 QgsFeatureRequest 构造函数。下面是一个例子:

# The expression will filter the features where the field "location_name"
# contains the word "Lake" (case insensitive)
exp = QgsExpression('location_name ILIKE \'%Lake%\'')
request = QgsFeatureRequest(exp)

表达式、筛选和计算值 有关支持的语法的详细信息 QgsExpression .

请求可用于定义为每个特性检索的数据,因此迭代器返回所有特性,但为每个特性返回部分数据。

# Only return selected fields to increase the "speed" of the request
request.setSubsetOfAttributes([0,2])

# More user friendly version
request.setSubsetOfAttributes(['name','id'],layer.fields())

# Don't return geometry objects to increase the "speed" of the request
request.setFlags(QgsFeatureRequest.NoGeometry)

# Fetch only the feature with id 45
request.setFilterFid(45)

# The options may be chained
request.setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.NoGeometry).setFilterFid(45).setSubsetOfAttributes([0,2])

修改向量层

大多数矢量数据提供程序支持编辑层数据。有时它们只支持可能的编辑操作的一个子集。使用 capabilities() 函数以确定支持哪些功能集

caps = layer.dataProvider().capabilities()
# Check if a particular capability is supported:
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'

通过使用以下任何一种方法进行矢量层编辑,可以将更改直接提交到底层数据存储(文件、数据库等)。如果您只想进行临时更改,请跳到下一节解释如何进行更改。 modifications with editing buffer .

注解

如果您在QGIS中工作(从控制台或插件),可能需要强制重新绘制地图画布,以查看对几何图形、样式或属性所做的更改:

# If caching is enabled, a simple canvas refresh might not be sufficient
# to trigger a redraw and you must clear the cached image for the layer
if iface.mapCanvas().isCachingEnabled():
    layer.triggerRepaint()
else:
    iface.mapCanvas().refresh()

添加特征

创造一些 QgsFeature 实例并将它们的列表传递给提供程序的 addFeatures() 方法。它将返回两个值:结果(真/假)和添加的功能列表(它们的ID由数据存储设置)。

要设置功能的属性,可以初始化通过 QgsFields 对象(您可以从 fields() 矢量层方法)或调用 initAttributes() 传递要添加的字段数。

if caps & QgsVectorDataProvider.AddFeatures:
    feat = QgsFeature(layer.fields())
    feat.setAttributes([0, 'hello'])
    # Or set a single attribute by key or by index:
    feat.setAttribute('name', 'hello')
    feat.setAttribute(0, 'hello')
    feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(123, 456)))
    (res, outFeats) = layer.dataProvider().addFeatures([feat])

删除特征

要删除某些功能,只需提供其功能ID的列表。

if caps & QgsVectorDataProvider.DeleteFeatures:
    res = layer.dataProvider().deleteFeatures([5, 10])

修改特征

可以更改特征的几何图形或更改某些属性。下面的示例首先使用索引0和1更改属性值,然后更改功能的几何图形。

fid = 100   # ID of the feature we will modify

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 它提供了一些编辑几何图形的有用方法(平移、插入或移动顶点等)。

使用编辑缓冲区修改矢量层

在QGIS应用程序中编辑向量时,必须首先开始特定层的编辑模式,然后进行一些修改,最后提交(或回滚)更改。在提交之前,您所做的所有更改都不会被写入——它们保留在层的内存编辑缓冲区中。也可以通过编程的方式使用这个功能——这只是矢量层编辑的另一种方法,它补充了数据提供者的直接使用。在提供一些用于矢量层编辑的GUI工具时使用此选项,因为这将允许用户决定是否提交/回滚,并允许使用撤消/重做。提交更改后,来自编辑缓冲区的所有更改都将保存到数据提供程序。

这些方法与我们在提供程序中看到的方法类似,但它们是在 QgsVectorLayer 代替对象。

要使这些方法工作,层必须处于编辑模式。要启动编辑模式,请使用 startEditing() 方法。要停止编辑,请使用 commitChanges()rollBack() 方法。第一个将把您的所有更改提交到数据源,而第二个将放弃它们,并且根本不会修改数据源。

要确定图层是否处于编辑模式,请使用 isEditable() 方法。

这里有一些例子演示如何使用这些编辑方法。

from qgis.PyQt.QtCore import QVariant

# add two features (QgsFeature instances)
layer.addFeatures([feat1,feat2])
# delete a feature with specified ID
layer.deleteFeature(fid)

# set new geometry (QgsGeometry instance) for a feature
layer.changeGeometry(fid, geometry)
# update an attribute with given field index (int) to a given value
layer.changeAttributeValue(fid, fieldIndex, value)

# add new field
layer.addAttribute(QgsField("mytext", QVariant.String))
# remove a field
layer.deleteAttribute(fieldIndex)

为了使undo/redo正常工作,上述调用必须包装成undo命令。(如果您不关心撤消/重做,并且希望立即存储更改,那么您可以通过 editing with data provider

以下是如何使用撤消功能:

layer.beginEditCommand("Feature triangulation")

# ... call layer's editing methods ...

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

这个 beginEditCommand() 方法将创建一个内部的“活动”命令,并记录向量层中的后续更改。打电话给 endEditCommand() 命令被推到撤销堆栈上,用户将能够从GUI撤销/恢复它。如果在进行更改时出现问题, destroyEditCommand() 方法将删除命令并回滚此命令处于活动状态时所做的所有更改。

您也可以使用 with edit(layer) -语句将commit和rollback包装成更语义化的代码块,如下例所示:

with edit(layer):
  feat = next(layer.getFeatures())
  feat[0] = 5
  layer.updateFeature(feat)

这将自动调用 commitChanges() 最后。如果发生任何异常,它将 rollBack() 所有的变化。如果在 commitChanges() (当方法返回false时)a QgsEditError 将引发异常。

添加和删除字段

要添加字段(属性),需要指定字段定义列表。要删除字段,只需提供字段索引列表。

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()

小技巧

Directly save changes using with based command

使用 with edit(layer): 将自动提交更改并调用 commitChanges() 最后。如果发生任何异常,它将 rollBack() 所有的变化。见 使用编辑缓冲区修改矢量层 .

使用空间索引

如果需要频繁查询向量层,空间索引可以显著提高代码的性能。例如,假设您正在编写插值算法,并且对于给定位置,您需要知道点图层中的10个最近点,以便使用这些点计算插值值。如果没有空间索引,qgis找到这10个点的唯一方法是计算每个点到指定位置的距离,然后比较这些距离。这可能是一项非常耗时的任务,尤其是需要在多个位置重复执行时。如果存在层的空间索引,则操作更有效。

把没有空间索引的层想象成电话簿,其中电话号码没有被排序或索引。找到一个给定的人的电话号码的唯一方法是从头读到找到为止。

默认情况下,不会为QGIS矢量层创建空间索引,但您可以轻松创建它们。这就是你要做的:

写入矢量层

您可以使用 QgsVectorFileWriter 班级。它支持OGR支持的任何其他类型的向量文件(shapefile、geojson、r kml和其他)。

有两种方法可以导出矢量层:

内存提供者

内存提供程序主要供插件或第三方应用程序开发人员使用。它不在磁盘上存储数据,允许开发人员将其用作某些临时层的快速后端。

提供程序支持字符串、int和双字段。

内存提供程序还支持空间索引,通过调用提供程序的 createSpatialIndex() 功能。创建空间索引后,您将能够更快地迭代较小区域中的功能(因为不需要遍历所有功能,只需要遍历指定矩形中的功能)。

内存提供程序是通过传递 "memory" 作为提供程序字符串 QgsVectorLayer 建造师。

构造函数还接受一个定义层的几何体类型的URI,其中之一: "Point""LineString""Polygon""MultiPoint""MultiLineString""MultiPolygon" .

URI还可以指定URI中内存提供程序的坐标引用系统、字段和索引。语法是:

CRS=定义
指定坐标参考系,其中定义可以是 QgsCoordinateReferenceSystem.createFromString
索引=是
指定提供程序将使用空间索引
字段=名称:类型(长度、精度)
指定层的属性。该属性有一个名称,也可以有一个类型(整数、双精度或字符串)、长度和精度。可能有多个字段定义。

下面的URI示例包含了所有这些选项

"Point?crs=epsg:4326&field=id:integer&field=name:string(20)&index=yes"

下面的示例代码说明如何创建和填充内存提供程序

from qgis.PyQt.QtCore import QVariant

# create layer
vl = QgsVectorLayer("Point", "temporary_points", "memory")
pr = vl.dataProvider()

# add fields
pr.addAttributes([QgsField("name", QVariant.String),
                    QgsField("age",  QVariant.Int),
                    QgsField("size", QVariant.Double)])
vl.updateFields() # tell the vector layer to fetch changes from the provider

# add a feature
fet = QgsFeature()
fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
fet.setAttributes(["Johny", 2, 0.3])
pr.addFeatures([fet])

# update layer's extent when new features have been added
# because change of extent in provider is not propagated to the layer
vl.updateExtents()

最后,让我们检查一下是否一切顺利

# show some stats
print("fields:", len(pr.fields()))
print("features:", pr.featureCount())
e = vl.extent()
print("extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())

# iterate over features
features = vl.getFeatures()
for fet in features:
    print("F:", fet.id(), fet.attributes(), fet.geometry().asPoint())

矢量层的外观(符号)

当呈现向量层时,数据的外观由 renderersymbols 与层关联。符号是一个类,负责绘制特征的视觉表示,而渲染器则决定将哪个符号用于特定的特征。

给定层的渲染器可以获得如下所示:

renderer = layer.renderer()

有了这个参考,让我们来探讨一下

print("Type:", renderer.type())

QGIS核心库中有几种已知的渲染器类型:

类型 等级 描述
单字节 QgsSingleSymbolRenderer 使用相同符号呈现所有功能
分类符号 QgsCategorizedSymbolRenderer 为每个类别使用不同的符号呈现功能
刻度符号 QgsGraduatedSymbolRenderer 对每个值范围使用不同的符号呈现功能

也可能有一些自定义渲染器类型,所以不要假设只有这些类型。您可以查询应用程序的 QgsRendererRegistry 要查找当前可用的渲染器:

print(QgsApplication.rendererRegistry().renderersList())
# Print:
['nullSymbol',
'singleSymbol',
'categorizedSymbol',
'graduatedSymbol',
'RuleRenderer',
'pointDisplacement',
'pointCluster',
'invertedPolygonRenderer',
'heatmapRenderer',
'25dRenderer']

可以以文本形式获取渲染器内容的转储--对于调试很有用

print(renderer.dump())

单符号渲染器

您可以通过调用 symbol() 方法并更改为 setSymbol() 方法(C++DEVS注释:渲染器获取符号的所有权)。

您可以通过调用 setSymbol() 传递适当符号实例的实例。符号用于 点, linepolygon 可以通过调用 createSimple() 相应类的函数 QgsMarkerSymbolQgsLineSymbolQgsFillSymbol .

字典传给 createSimple() 设置符号的样式属性。

例如,可以替换特定 point 按调用分层 setSymbol() 传递 QgsMarkerSymbol ,如下代码示例所示:

symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'red'})
layer.renderer().setSymbol(symbol)
# show the change
layer.triggerRepaint()

name indicates the shape of the marker, and can be any of the following:

  • 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'}

如果要更改某些属性,这可能很有用:

# You can alter a single property...
layer.renderer().symbol().symbolLayer(0).setSize(3)
# ... but not all properties are accessible from methods,
# you can also replace the symbol completely:
props = layer.renderer().symbol().symbolLayer(0).properties()
props['color'] = 'yellow'
props['name'] = 'square'
layer.renderer().setSymbol(QgsMarkerSymbol.createSimple(props))
# show the changes
layer.triggerRepaint()

分类符号呈现器

使用分类的渲染器时,可以查询和设置用于分类的属性:使用 classAttribute()setClassAttribute() 方法。

获取类别列表

for cat in renderer.categories():
    print("{}: {} :: {}".format(cat.value(), cat.label(), cat.symbol()))

Where value() is the value used for discrimination between categories, label() is a text used for category description and symbol() method returns the assigned symbol.

渲染器通常还存储用于分类的原始符号和颜色渐变: sourceColorRamp()sourceSymbol() 方法。

渐变符号渲染器

此渲染器与上面描述的分类符号渲染器非常相似,但它不使用每个类一个属性值,而是使用值的范围,因此只能与数字属性一起使用。

了解有关渲染器中使用的范围的更多信息

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
# Make our first symbol and range...
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 make another symbol and range...
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)

使用符号

对于符号的表示,有 QgsSymbol 具有三个派生类的基类:

Every symbol consists of one or more symbol layers (classes derived from QgsSymbolLayer). The symbol layers do the actual rendering, the symbol class itself serves only as a container for the symbol layers.

有一个符号的实例(例如,从渲染器中),可以探索它: type 方法说明它是标记、线条还是填充符号。有一个 dump 方法,返回符号的简短描述。要获取符号层列表,请执行以下操作:

for i in range(symbol.symbolLayerCount()):
    lyr = symbol.symbolLayer(i)
    print("{}: {}".format(i, lyr.layerType()))

要找出符号的颜色,请使用 color 方法及 setColor 改变它的颜色。另外,使用标记符号,可以查询符号的大小和旋转方式 sizeangle 方法,对于线条符号 width 方法返回线条宽度。

默认情况下,尺寸和宽度以毫米为单位,角度以度为单位。

使用符号层

如前所述,符号层(子类 QgsSymbolLayer )确定特征的外观。有几个基本的符号层类可供一般使用。可以实现新的符号层类型,从而任意自定义特性的呈现方式。这个 layerType() 方法唯一标识符号层类——基本类和默认类是 SimpleMarkerSimpleLineSimpleFill 符号层类型。

可以使用以下代码为给定的符号层类创建完整的符号层类型列表:

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 class manages a database of all available

符号层类型。

要访问符号层数据,请使用其 properties() 方法,返回确定外观的属性的键值字典。每个符号层类型都有一组特定的属性。此外,还有一些通用方法 colorsizeanglewidth 和他们的二传手。当然,尺寸和角度仅适用于标记符号层,宽度适用于线符号层。

创建自定义符号层类型

假设您希望自定义数据的呈现方式。您可以创建自己的符号层类,该类将完全按照您的意愿绘制功能。下面是一个用指定半径绘制红色圆的标记示例

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):
      # Rendering depends on whether the symbol is selected (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 方法确定符号层的名称,它必须在所有符号层中唯一。属性用于持久化属性。 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

        # setup a simple 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())

您应该将层类型(与层返回的类型相同)和符号类型(标记/线/填充)传递给父类的构造函数。 createSymbolLayer() 负责创建具有在 props 字典。还有 createSymbolLayerWidget() 方法,返回此符号层类型的设置小部件。

最后一步是将这个符号层添加到注册表中---我们就完成了。

创建自定义渲染器

如果要自定义规则如何选择用于呈现功能的符号,则可以创建新的呈现器实现。您可能希望在某些用例中这样做:符号是由字段组合决定的,符号的大小取决于当前的比例等。

下面的代码显示了一个简单的自定义渲染器,它创建两个标记符号,并为每个功能随机选择其中一个标记符号。

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() 方法创建配置小部件。它不必存在或者可以返回 None 如果渲染器不带GUI。

要将图标与渲染器关联,可以在 QgsRendererAbstractMetadata 构造函数作为第三个(可选)参数---randomrendermetadata中的基类构造函数 __init__() 函数变为

QgsRendererAbstractMetadata.__init__(self,
       "RandomRenderer",
       "Random renderer",
       QIcon(QPixmap("RandomRendererIcon.png", "png")))

也可以在以后的任何时候使用 setIcon() 元数据类的方法。图标可以从文件加载(如上所示),也可以从 Qt resource (pyqt5包括.qrc编译器for python)。

警告

Despite our constant efforts, information beyond this line may not be updated for QGIS 3. Refer to https://qgis.org/pyqgis/master for the python API documentation or, give a hand to update the chapters you know about. Thanks.

进一步话题

待办事项: