6. 使用矢量层

提示

如果您在pyqgis控制台之外,则此页面上的代码片段需要以下导入:

 1from qgis.core import (
 2  QgsApplication,
 3  QgsDataSourceUri,
 4  QgsCategorizedSymbolRenderer,
 5  QgsClassificationRange,
 6  QgsPointXY,
 7  QgsProject,
 8  QgsExpression,
 9  QgsField,
10  QgsFields,
11  QgsFeature,
12  QgsFeatureRequest,
13  QgsFeatureRenderer,
14  QgsGeometry,
15  QgsGraduatedSymbolRenderer,
16  QgsMarkerSymbol,
17  QgsMessageLog,
18  QgsRectangle,
19  QgsRendererCategory,
20  QgsRendererRange,
21  QgsSymbol,
22  QgsVectorDataProvider,
23  QgsVectorLayer,
24  QgsVectorFileWriter,
25  QgsWkbTypes,
26  QgsSpatialIndex,
27  QgsVectorLayerUtils
28)
29
30from qgis.core.additions.edit import edit
31
32from qgis.PyQt.QtGui import (
33    QColor,
34)

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

这里的大部分工作都是基于 QgsVectorLayer 班级。

6.1. 检索有关属性的信息

您可以通过调用检索有关与矢量图层关联的字段的信息 fields() 在一个 QgsVectorLayer 对象:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
for field in vlayer.fields():
    print(field.name(), field.typeName())
1ID Integer64
2fk_region Integer64
3ELEV Real
4NAME String
5USE String

这个 displayField()mapTipTemplate() 方法提供有关在 显示属性 标签。

加载矢量图层时,QGIS始终会选择一个字段作为 Display Name ,而 HTML Map Tip 默认情况下为空。使用这些方法,您可以很容易地获得这两种方法:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
print(vlayer.displayField())
NAME

备注

如果您更改了 Display Name 从一个字段到一个表达式,您必须使用 displayExpression() 而不是 displayField()

6.2. 在向量层上迭代

迭代矢量层中的要素是最常见的任务之一。下面是执行此任务的简单基本代码示例,并显示了有关每个功能的一些信息。这个 layer 变量被假定为具有 QgsVectorLayer 对象。

 1# "layer" is a QgsVectorLayer instance
 2layer = iface.activeLayer()
 3features = layer.getFeatures()
 4
 5for feature in features:
 6    # retrieve every feature with its geometry and attributes
 7    print("Feature ID: ", feature.id())
 8    # fetch geometry
 9    # show some information about the feature geometry
10    geom = feature.geometry()
11    geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType())
12    if geom.type() == QgsWkbTypes.PointGeometry:
13        # the geometry type can be of single or multi type
14        if geomSingleType:
15            x = geom.asPoint()
16            print("Point: ", x)
17        else:
18            x = geom.asMultiPoint()
19            print("MultiPoint: ", x)
20    elif geom.type() == QgsWkbTypes.LineGeometry:
21        if geomSingleType:
22            x = geom.asPolyline()
23            print("Line: ", x, "length: ", geom.length())
24        else:
25            x = geom.asMultiPolyline()
26            print("MultiLine: ", x, "length: ", geom.length())
27    elif geom.type() == QgsWkbTypes.PolygonGeometry:
28        if geomSingleType:
29            x = geom.asPolygon()
30            print("Polygon: ", x, "Area: ", geom.area())
31        else:
32            x = geom.asMultiPolygon()
33            print("MultiPolygon: ", x, "Area: ", geom.area())
34    else:
35        print("Unknown or invalid geometry")
36    # fetch attributes
37    attrs = feature.attributes()
38    # attrs is a list. It contains all the attribute values of this feature
39    print(attrs)
40    # for this test only print the first feature
41    break
Feature ID:  1
Point:  <QgsPointXY: POINT(7 45)>
[1, 'First feature']

6.3. 选择要素

在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列表传递给它:

1selected_fid = []
2
3# Get the first feature id from the layer
4feature = next(layer.getFeatures())
5if feature:
6    selected_fid.append(feature.id())
7
8# Add that features to the selected list
9layer.select(selected_fid)

要清除选择,请执行以下操作:

layer.removeSelection()

6.3.1. 访问属性

属性可以通过它们的名称来引用:

print(feature['name'])
First feature

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

print(feature[1])
First feature

6.3.2. 迭代所选要素

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

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

6.3.3. 迭代要素子集

如果要迭代某个图层中的给定要素子集(例如给定区域内的要素),则必须添加 QgsFeatureRequest 对象添加到 getFeatures() 打电话。下面是一个例子:

1areaOfInterest = QgsRectangle(450290,400520, 450750,400780)
2
3request = QgsFeatureRequest().setFilterRect(areaOfInterest)
4
5for feature in layer.getFeatures(request):
6    # do whatever you need with the feature
7    pass

为了提高速度,通常只使用要素的边界框进行交集。然而,有一面旗帜 ExactIntersect 这可确保仅返回相交要素:

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

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

request = QgsFeatureRequest()
request.setLimit(2)
for feature in layer.getFeatures(request):
    print(feature)
<qgis._core.QgsFeature object at 0x7f9b78590948>
<qgis._core.QgsFeature object at 0x7faef5881670>

如果需要使用基于属性的筛选器来替代(或附加)空间筛选器,如上面的示例所示,则可以构建 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

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

 1# Only return selected fields to increase the "speed" of the request
 2request.setSubsetOfAttributes([0,2])
 3
 4# More user friendly version
 5request.setSubsetOfAttributes(['name','id'],layer.fields())
 6
 7# Don't return geometry objects to increase the "speed" of the request
 8request.setFlags(QgsFeatureRequest.NoGeometry)
 9
10# Fetch only the feature with id 45
11request.setFilterFid(45)
12
13# The options may be chained
14request.setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.NoGeometry).setFilterFid(45).setSubsetOfAttributes([0,2])

6.4. 修改向量层

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

caps = layer.dataProvider().capabilities()
# Check if a particular capability is supported:
if caps & QgsVectorDataProvider.DeleteFeatures:
    print('The layer supports DeleteFeatures')
The layer supports DeleteFeatures

有关所有可用功能的列表,请参阅 API Documentation of QgsVectorDataProvider

要在逗号分隔的列表中打印图层的功能文本描述,可以使用 capabilitiesString() 如下例所示:

1caps_string = layer.dataProvider().capabilitiesString()
2# Print:
3# 'Add Features, Delete Features, Change Attribute Values, Add Attributes,
4# Delete Attributes, Rename Attributes, Fast Access to Features at ID,
5# Presimplify Geometries, Presimplify Geometries with Validity Check,
6# Transactions, Curved Geometries'

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

备注

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

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

6.4.1. 添加功能

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

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

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

6.4.2. 删除要素

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

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

6.4.3. 修改要素

可以更改要素的几何图形或更改某些属性。以下示例首先更改索引为0和1的属性的值,然后更改要素的几何图形。

1fid = 100   # ID of the feature we will modify
2
3if caps & QgsVectorDataProvider.ChangeAttributeValues:
4    attrs = { 0 : "hello", 1 : 123 }
5    layer.dataProvider().changeAttributeValues({ fid : attrs })
6
7if caps & QgsVectorDataProvider.ChangeGeometries:
8    geom = QgsGeometry.fromPointXY(QgsPointXY(111,222))
9    layer.dataProvider().changeGeometryValues({ fid : geom })

小技巧

Favor QgsVectorLayerEditUtils class for geometry-only edits

如果只需要更改几何图形,则可以考虑使用 QgsVectorLayerEditUtils 它提供了一些有用的方法来编辑几何图形(平移、插入或移动顶点等)。

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

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

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

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

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

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

 1from qgis.PyQt.QtCore import QVariant
 2
 3feat1 = feat2 = QgsFeature(layer.fields())
 4fid = 99
 5feat1.setId(fid)
 6
 7# add two features (QgsFeature instances)
 8layer.addFeatures([feat1,feat2])
 9# delete a feature with specified ID
10layer.deleteFeature(fid)
11
12# set new geometry (QgsGeometry instance) for a feature
13geometry = QgsGeometry.fromWkt("POINT(7 45)")
14layer.changeGeometry(fid, geometry)
15# update an attribute with given field index (int) to a given value
16fieldIndex =1
17value ='My new name'
18layer.changeAttributeValue(fid, fieldIndex, value)
19
20# add new field
21layer.addAttribute(QgsField("mytext", QVariant.String))
22# remove a field
23layer.deleteAttribute(fieldIndex)

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

以下是使用撤消功能的方法:

 1layer.beginEditCommand("Feature triangulation")
 2
 3# ... call layer's editing methods ...
 4
 5if problem_occurred:
 6  layer.destroyEditCommand()
 7  # ... tell the user that there was a problem
 8  # and return
 9
10# ... more editing ...
11
12layer.endEditCommand()

这个 beginEditCommand() 方法将创建一个内部“活动”命令,并将记录向量层中的后续更改。通过调用 endEditCommand() 该命令被推送到撤消堆栈上,用户将能够从图形用户界面撤消/重做它。如果在进行更改时出现错误, destroyEditCommand() 方法将移除该命令,并回滚在该命令处于活动状态时所做的所有更改。

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

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

这将自动调用 commitChanges() 最后。如果发生任何异常,它将 rollBack() 所有的变化。如果在以下情况下遇到问题 commitChanges() (当该方法返回FALSE时) QgsEditError 将引发异常。

6.4.5. 添加和删除字段

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

1from qgis.PyQt.QtCore import QVariant
2
3if caps & QgsVectorDataProvider.AddAttributes:
4    res = layer.dataProvider().addAttributes(
5        [QgsField("mytext", QVariant.String),
6        QgsField("myint", QVariant.Int)])
7
8if caps & QgsVectorDataProvider.DeleteAttributes:
9    res = layer.dataProvider().deleteAttributes([0])
 1# Alternate methods for removing fields
 2# first create temporary fields to be removed (f1-3)
 3layer.dataProvider().addAttributes([QgsField("f1",QVariant.Int),QgsField("f2",QVariant.Int),QgsField("f3",QVariant.Int)])
 4layer.updateFields()
 5count=layer.fields().count() # count of layer fields
 6ind_list=list((count-3, count-2)) # create list
 7
 8# remove a single field with an index
 9layer.dataProvider().deleteAttributes([count-1])
10
11# remove multiple fields with a list of indices
12layer.dataProvider().deleteAttributes(ind_list)

在数据提供程序中添加或删除字段后,需要更新图层的字段,因为更改不会自动传播。

layer.updateFields()

小技巧

Directly save changes using with based command

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

6.5. 使用空间索引

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

将没有空间索引的层想象为电话簿,其中的电话号码没有排序或索引。找到某个人的电话号码的唯一方法是从头开始阅读,直到找到为止。

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

  • 使用创建空间索引 QgsSpatialIndex 班级:

    index = QgsSpatialIndex()
    
  • 将要素添加到索引-索引Take QgsFeature 对象,并将其添加到内部数据结构中。您可以手动创建该对象,也可以使用以前调用提供程序的 getFeatures() 方法。

    index.addFeature(feat)
    
  • 或者,您也可以使用批量加载一次加载一个图层的所有要素

    index = QgsSpatialIndex(layer.getFeatures())
    
  • 在空间索引中填充了一些值后,即可执行一些查询

    1# returns array of feature IDs of five nearest features
    2nearest = index.nearestNeighbor(QgsPointXY(25.4, 12.7), 5)
    3
    4# returns array of IDs of features which intersect the rectangle
    5intersect = index.intersects(QgsRectangle(22.5, 15.3, 23.1, 17.2))
    

您也可以使用 QgsSpatialIndexKDBush 空间索引。该索引类似于 standard QgsSpatialIndex 但是:

  • 支座 only 单点要素

  • static (构建后不能向索引添加任何附加功能)

  • much faster!

  • 允许直接检索原始要素的点,而不需要其他要素请求

  • 支持True distance based 搜索,即从搜索点返回半径内的所有点

6.6. QgsVectorLayerUtils类

这个 QgsVectorLayerUtils 类包含一些非常有用的方法,您可以对矢量层使用这些方法。

例如, createFeature() 方法准备一个 QgsFeature 要添加到矢量层,请保留每个字段的所有最终约束和默认值:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
feat = QgsVectorLayerUtils.createFeature(vlayer)

这个 getValues() 方法允许您快速获取字段或表达式的值:

1vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
2# select only the first feature to make the output shorter
3vlayer.selectByIds([1])
4val = QgsVectorLayerUtils.getValues(vlayer, "NAME", selectedOnly=True)
5print(val)
(['AMBLER'], True)

6.7. 创建矢量层

有几种方法可以生成矢量层数据集:

  • 这个 QgsVectorFileWriter 类:用于将向量文件写入磁盘的便捷类,使用静态调用 writeAsVectorFormatV3() 它保存整个向量层,或创建类的实例并向 addFeature() 。这个类支持GDAL支持的所有矢量格式(GeoPackage、Shapefile、GeoJSON、KML等)。

  • 这个 QgsVectorLayer 类:实例化一个数据提供程序,该数据提供程序解释提供的数据源路径(URL)以连接和访问数据。它可用于创建基于内存的临时图层 (memory )并连接到GDAL矢量数据集 (ogr )、数据库 (postgresspatialitemysqlmssql )和更多 (wfsgpxdelimitedtext ...)。

6.7.1. 从一个实例 QgsVectorFileWriter

 1# SaveVectorOptions contains many settings for the writer process
 2save_options = QgsVectorFileWriter.SaveVectorOptions()
 3transform_context = QgsProject.instance().transformContext()
 4# Write to a GeoPackage (default)
 5error = QgsVectorFileWriter.writeAsVectorFormatV3(layer,
 6                                                  "testdata/my_new_file.gpkg",
 7                                                  transform_context,
 8                                                  save_options)
 9if error[0] == QgsVectorFileWriter.NoError:
10    print("success!")
11else:
12  print(error)
 1# Write to an ESRI Shapefile format dataset using UTF-8 text encoding
 2save_options = QgsVectorFileWriter.SaveVectorOptions()
 3save_options.driverName = "ESRI Shapefile"
 4save_options.fileEncoding = "UTF-8"
 5transform_context = QgsProject.instance().transformContext()
 6error = QgsVectorFileWriter.writeAsVectorFormatV3(layer,
 7                                                  "testdata/my_new_shapefile",
 8                                                  transform_context,
 9                                                  save_options)
10if error[0] == QgsVectorFileWriter.NoError:
11    print("success again!")
12else:
13  print(error)
 1# Write to an ESRI GDB file
 2save_options = QgsVectorFileWriter.SaveVectorOptions()
 3save_options.driverName = "FileGDB"
 4# if no geometry
 5save_options.overrideGeometryType = QgsWkbTypes.Unknown
 6save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
 7save_options.layerName = 'my_new_layer_name'
 8transform_context = QgsProject.instance().transformContext()
 9gdb_path = "testdata/my_example.gdb"
10error = QgsVectorFileWriter.writeAsVectorFormatV3(layer,
11                                                gdb_path,
12                                                transform_context,
13                                                save_options)
14if error[0] == QgsVectorFileWriter.NoError:
15  print("success!")
16else:
17  print(error)

方法转换字段以使其与不同的格式兼容 FieldValueConverter 。例如,要将数组变量类型(例如,在postgres中)转换为文本类型,可以执行以下操作:

 1LIST_FIELD_NAME = 'xxxx'
 2
 3class ESRIValueConverter(QgsVectorFileWriter.FieldValueConverter):
 4
 5  def __init__(self, layer, list_field):
 6    QgsVectorFileWriter.FieldValueConverter.__init__(self)
 7    self.layer = layer
 8    self.list_field_idx = self.layer.fields().indexFromName(list_field)
 9
10  def convert(self, fieldIdxInLayer, value):
11    if fieldIdxInLayer == self.list_field_idx:
12      return QgsListFieldFormatter().representValue(layer=vlayer,
13                                                    fieldIndex=self.list_field_idx,
14                                                    config={},
15                                                    cache=None,
16                                                    value=value)
17    else:
18      return value
19
20  def fieldDefinition(self, field):
21    idx = self.layer.fields().indexFromName(field.name())
22    if idx == self.list_field_idx:
23      return QgsField(LIST_FIELD_NAME, QVariant.String)
24    else:
25      return self.layer.fields()[idx]
26
27converter = ESRIValueConverter(vlayer, LIST_FIELD_NAME)
28opts = QgsVectorFileWriter.SaveVectorOptions()
29opts.fieldValueConverter = converter

还可以指定目标CRS-如果 QgsCoordinateReferenceSystem 作为第四个参数传递,则将该层转换为该CRS。

有关有效的驱动程序名称,请调用 supportedFiltersAndFormats() 方法,或参考 supported formats by OGR -您应该将“Code”列中的值作为驱动程序名称进行传递。

您还可以设置是否仅导出选定的功能、传递更多用于创建的驱动程序特定选项或告诉编写器不要创建属性...还有许多其他(可选)参数;请参阅 QgsVectorFileWriter 有关详细信息,请参阅文档。

6.7.2. 直接从要素

 1from qgis.PyQt.QtCore import QVariant
 2
 3# define fields for feature attributes. A QgsFields object is needed
 4fields = QgsFields()
 5fields.append(QgsField("first", QVariant.Int))
 6fields.append(QgsField("second", QVariant.String))
 7
 8""" create an instance of vector file writer, which will create the vector file.
 9Arguments:
101. path to new file (will fail if exists already)
112. field map
123. geometry type - from WKBTYPE enum
134. layer's spatial reference (instance of
14   QgsCoordinateReferenceSystem)
155. coordinate transform context
166. save options (driver name for the output file, encoding etc.)
17"""
18
19crs = QgsProject.instance().crs()
20transform_context = QgsProject.instance().transformContext()
21save_options = QgsVectorFileWriter.SaveVectorOptions()
22save_options.driverName = "ESRI Shapefile"
23save_options.fileEncoding = "UTF-8"
24
25writer = QgsVectorFileWriter.create(
26  "testdata/my_new_shapefile.shp",
27  fields,
28  QgsWkbTypes.Point,
29  crs,
30  transform_context,
31  save_options
32)
33
34if writer.hasError() != QgsVectorFileWriter.NoError:
35    print("Error when creating shapefile: ",  writer.errorMessage())
36
37# add a feature
38fet = QgsFeature()
39
40fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
41fet.setAttributes([1, "text"])
42writer.addFeature(fet)
43
44# delete the writer to flush features to disk
45del writer

6.7.3. 从一个实例 QgsVectorLayer

在支持的所有数据提供程序中 QgsVectorLayer 类,让我们将重点放在基于内存层。Memory Provider主要供插件或第三方应用程序开发人员使用。它不将数据存储在磁盘上,允许开发人员将其用作一些临时层的快速后端。

该提供程序支持字符串、整型和双精度字段。

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

内存提供程序是通过传递 "memory" 作为提供程序字符串提供给 QgsVectorLayer 构造函数。

构造函数还采用定义层的几何类型的URI,其中之一为: "Point""LineString""Polygon""MultiPoint""MultiLineString""MultiPolygon""None"

URI还可以在URI中指定内存提供程序的坐标参考系、字段和索引。其语法为:

CRS=定义

指定坐标参考系,其中定义可以是接受的任何形式 QgsCoordinateReferenceSystem.createFromString()

索引=是

指定提供程序将使用空间索引

字段=名称:类型(长度、精度)

指定层的属性。该属性有一个名称,还可以有一个类型(整型、双精度或字符串)、长度和精度。可能有多个字段定义。

下面的URI示例合并了所有这些选项

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

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

 1from qgis.PyQt.QtCore import QVariant
 2
 3# create layer
 4vl = QgsVectorLayer("Point", "temporary_points", "memory")
 5pr = vl.dataProvider()
 6
 7# add fields
 8pr.addAttributes([QgsField("name", QVariant.String),
 9                    QgsField("age",  QVariant.Int),
10                    QgsField("size", QVariant.Double)])
11vl.updateFields() # tell the vector layer to fetch changes from the provider
12
13# add a feature
14fet = QgsFeature()
15fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
16fet.setAttributes(["Johny", 2, 0.3])
17pr.addFeatures([fet])
18
19# update layer's extent when new features have been added
20# because change of extent in provider is not propagated to the layer
21vl.updateExtents()

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

 1# show some stats
 2print("fields:", len(pr.fields()))
 3print("features:", pr.featureCount())
 4e = vl.extent()
 5print("extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())
 6
 7# iterate over features
 8features = vl.getFeatures()
 9for fet in features:
10    print("F:", fet.id(), fet.attributes(), fet.geometry().asPoint())
fields: 3
features: 1
extent: 10.0 10.0 10.0 10.0
F: 1 ['Johny', 2, 0.3] <QgsPointXY: POINT(10 10)>

6.8. 矢量层的外观(符号系统)

渲染向量层时,数据的外观由 renderersymbols 与该层相关联。符号是负责绘制特征的可视表示的类,而渲染器确定将使用什么符号来表示特定的特征。

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

renderer = layer.renderer()

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

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

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

类型

班级

描述

单一符号

QgsSingleSymbolRenderer

使用相同符号渲染所有要素

分类符号

QgsCategorizedSymbolRenderer

为每个类别使用不同的符号渲染要素

毕业符号

QgsGraduatedSymbolRenderer

对每个值范围使用不同的符号来渲染要素

也可能有一些自定义的呈现器类型,所以永远不要假设只有这些类型。您可以查询应用程序的 QgsRendererRegistry 要查找当前可用的渲染器,请执行以下操作:

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

可以获得文本形式的呈现器内容的转储-这对调试很有用

renderer.dump()
SINGLE: MARKER SYMBOL (1 layers) color 190,207,80,255

6.8.1. 单个符号渲染器

可以通过调用以下方法获取用于呈现的符号 symbol() 方法并将其更改为 setSymbol() 方法(对于C++开发人员注意:呈现器获得符号的所有权。)

可以通过调用以下方法更改特定向量层使用的符号 setSymbol() 传递适当符号实例的实例。的符号 pointlinepolygon 层可以通过调用 createSimple() 相应类的函数 QgsMarkerSymbolQgsLineSymbolQgsFillSymbol

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

例如,您可以替换由特定 point 层,通过调用 setSymbol() 传递一个 QgsMarkerSymbol ,如下面的代码示例所示:

symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'red'})
layer.renderer().setSymbol(symbol)
# show the change
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())
{'angle': '0', 'cap_style': 'square', 'color': '255,0,0,255', 'horizontal_anchor_point': '1', 'joinstyle': 'bevel', 'name': 'square', 'offset': '0,0', 'offset_map_unit_scale': '3x:0,0,0,0,0,0', 'offset_unit': 'MM', 'outline_color': '35,35,35,255', 'outline_style': 'solid', 'outline_width': '0', 'outline_width_map_unit_scale': '3x:0,0,0,0,0,0', 'outline_width_unit': 'MM', 'scale_method': 'diameter', 'size': '2', 'size_map_unit_scale': '3x:0,0,0,0,0,0', 'size_unit': 'MM', 'vertical_anchor_point': '1'}

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

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

6.8.2. 分类符号渲染器

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

获取类别列表的步骤

1categorized_renderer = QgsCategorizedSymbolRenderer()
2# Add a few categories
3cat1 = QgsRendererCategory('1', QgsMarkerSymbol(), 'category 1')
4cat2 = QgsRendererCategory('2', QgsMarkerSymbol(), 'category 2')
5categorized_renderer.addCategory(cat1)
6categorized_renderer.addCategory(cat2)
7
8for cat in categorized_renderer.categories():
9    print("{}: {} :: {}".format(cat.value(), cat.label(), cat.symbol()))
1: category 1 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>
2: category 2 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>

哪里 value() 是用于区分类别的值, label() 是用于类别描述的文本, symbol() 方法返回分配的符号。

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

6.8.3. 分级符号渲染器

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

查找有关在渲染器中使用的范围的详细信息

 1graduated_renderer = QgsGraduatedSymbolRenderer()
 2# Add a few categories
 3graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 0-100', 0, 100), QgsMarkerSymbol()))
 4graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 101-200', 101, 200), QgsMarkerSymbol()))
 5
 6for ran in graduated_renderer.ranges():
 7    print("{} - {}: {} {}".format(
 8        ran.lowerValue(),
 9        ran.upperValue(),
10        ran.label(),
11        ran.symbol()
12      ))
0.0 - 100.0: class 0-100 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>
101.0 - 200.0: class 101-200 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>

您可以再次使用 classAttribute() (要查找分类属性名称), sourceSymbol()sourceColorRamp() 方法:研究方法。此外,还有 mode() 确定如何创建范围的方法:使用等间隔、分位数或其他方法。

如果您希望创建自己的分级符号渲染器,可以如下面的示例片段所示(它创建了一个简单的两类排列)

 1from qgis.PyQt import QtGui
 2
 3myVectorLayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
 4myTargetField = 'ELEV'
 5myRangeList = []
 6myOpacity = 1
 7# Make our first symbol and range...
 8myMin = 0.0
 9myMax = 50.0
10myLabel = 'Group 1'
11myColour = QtGui.QColor('#ffee00')
12mySymbol1 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
13mySymbol1.setColor(myColour)
14mySymbol1.setOpacity(myOpacity)
15myRange1 = QgsRendererRange(myMin, myMax, mySymbol1, myLabel)
16myRangeList.append(myRange1)
17#now make another symbol and range...
18myMin = 50.1
19myMax = 100
20myLabel = 'Group 2'
21myColour = QtGui.QColor('#00eeff')
22mySymbol2 = QgsSymbol.defaultSymbol(
23     myVectorLayer.geometryType())
24mySymbol2.setColor(myColour)
25mySymbol2.setOpacity(myOpacity)
26myRange2 = QgsRendererRange(myMin, myMax, mySymbol2, myLabel)
27myRangeList.append(myRange2)
28myRenderer = QgsGraduatedSymbolRenderer('', myRangeList)
29myClassificationMethod = QgsApplication.classificationMethodRegistry().method("EqualInterval")
30myRenderer.setClassificationMethod(myClassificationMethod)
31myRenderer.setClassAttribute(myTargetField)
32
33myVectorLayer.setRenderer(myRenderer)

6.8.4. 使用符号

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

Every symbol consists of one or more symbol layers (派生自 QgsSymbolLayer )。符号层执行实际渲染,符号类本身仅用作符号层的容器。

有了符号的实例(例如,来自呈现器),就可以探索它: type() 方法指示它是标记符号、线条符号还是填充符号。有一个 dump() 方法,该方法返回符号的简要说明。要获取符号层列表,请执行以下操作:

marker_symbol = QgsMarkerSymbol()
for i in range(marker_symbol.symbolLayerCount()):
    lyr = marker_symbol.symbolLayer(i)
    print("{}: {}".format(i, lyr.layerType()))
0: SimpleMarker

要找出符号的颜色使用 color() 方法和方法 setColor() 改变它的颜色。此外,对于标记符号,还可以使用 size()angle() 方法:研究方法。对于线条符号, width() 方法返回线宽。

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

6.8.4.1. 使用符号图层

如前所述,符号层(的子类 QgsSymbolLayer )确定要素的外观。有几个通用的基本符号层类。可以实现新的符号层类型,从而任意定制将如何呈现特征。这个 layerType() 方法唯一标识符号层类-基本的和默认的是 SimpleMarkerSimpleLineSimpleFill 符号层类型。

您可以使用以下代码获取可以为给定元件层类创建的元件层类型的完整列表:

1from qgis.core import QgsSymbolLayerRegistry
2myRegistry = QgsApplication.symbolLayerRegistry()
3myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
4for item in myRegistry.symbolLayersForType(QgsSymbol.Marker):
5    print(item)
 1AnimatedMarker
 2EllipseMarker
 3FilledMarker
 4FontMarker
 5GeometryGenerator
 6MaskMarker
 7RasterMarker
 8SimpleMarker
 9SvgMarker
10VectorField

这个 QgsSymbolLayerRegistry 类管理所有可用符号层类型的数据库。

若要访问符号层数据,请使用其 properties() 方法,该方法返回确定外观的属性的键值字典。每种符号层类型都有其使用的一组特定属性。此外,还有泛型方法 color()size()angle()width() ,与他们的二传手同行。当然,大小和角度仅适用于标记符号层,而宽度仅适用于线符号层。

6.8.4.2. 创建自定义符号图层类型

假设您想要自定义数据的呈现方式。您可以创建自己的符号图层类,它将完全按照您的需要绘制要素。以下是绘制具有指定半径的红色圆圈的标记示例

 1from qgis.core import QgsMarkerSymbolLayer
 2from qgis.PyQt.QtGui import QColor
 3
 4class FooSymbolLayer(QgsMarkerSymbolLayer):
 5
 6  def __init__(self, radius=4.0):
 7      QgsMarkerSymbolLayer.__init__(self)
 8      self.radius = radius
 9      self.color = QColor(255,0,0)
10
11  def layerType(self):
12     return "FooMarker"
13
14  def properties(self):
15      return { "radius" : str(self.radius) }
16
17  def startRender(self, context):
18    pass
19
20  def stopRender(self, context):
21      pass
22
23  def renderPoint(self, point, context):
24      # Rendering depends on whether the symbol is selected (QGIS >= 1.5)
25      color = context.selectionColor() if context.selected() else self.color
26      p = context.renderContext().painter()
27      p.setPen(color)
28      p.drawEllipse(point, self.radius, self.radius)
29
30  def clone(self):
31      return FooSymbolLayer(self.radius)

这个 layerType() 方法确定符号层的名称;它在所有符号层中必须是唯一的。这个 properties() 方法用于持久化属性。这个 clone() 方法必须返回符号层的副本,并且所有属性都完全相同。最后是渲染方法: startRender() 在呈现第一个特征之前被调用, stopRender() 当渲染完成时,以及 renderPoint() 被调用以执行呈现。点(S)的坐标已转换为输出坐标。

对于折线和面,唯一的区别是渲染方法不同:您可以使用 renderPolyline() 它接收行的列表,而 renderPolygon() 接收作为第一个参数的外环上的点的列表和作为第二个参数的内环的列表(或无)。

通常,添加一个用于设置符号层类型的属性的图形用户界面是很方便的,以允许用户自定义外观:在上面的例子中,我们可以让用户设置圆半径。以下代码实现了这样的小部件

 1from qgis.gui import QgsSymbolLayerWidget
 2
 3class FooSymbolLayerWidget(QgsSymbolLayerWidget):
 4    def __init__(self, parent=None):
 5        QgsSymbolLayerWidget.__init__(self, parent)
 6
 7        self.layer = None
 8
 9        # setup a simple UI
10        self.label = QLabel("Radius:")
11        self.spinRadius = QDoubleSpinBox()
12        self.hbox = QHBoxLayout()
13        self.hbox.addWidget(self.label)
14        self.hbox.addWidget(self.spinRadius)
15        self.setLayout(self.hbox)
16        self.connect(self.spinRadius, SIGNAL("valueChanged(double)"), \
17            self.radiusChanged)
18
19    def setSymbolLayer(self, layer):
20        if layer.layerType() != "FooMarker":
21            return
22        self.layer = layer
23        self.spinRadius.setValue(layer.radius)
24
25    def symbolLayer(self):
26        return self.layer
27
28    def radiusChanged(self, value):
29        self.layer.radius = value
30        self.emit(SIGNAL("changed()"))

此小部件可以嵌入到符号属性对话框中。当在符号属性对话框中选择符号层类型时,它将创建符号层的实例和符号层构件的实例。然后,它调用 setSymbolLayer() 方法将符号层分配给小工具。在该方法中,小部件应该更新UI以反映符号层的属性。这个 symbolLayer() 方法用于通过属性对话框再次检索符号层,以将其用于符号。

每次更改属性时,小部件都应该发出 changed() 用于让属性对话框更新符号预览的信号。

现在,我们只缺少最后一道粘合剂:让QGIS了解这些新类。这是通过将符号层添加到注册表来完成的。也可以在不将符号层添加到注册表的情况下使用符号层,但某些功能将不起作用:例如,加载带有自定义符号层的项目文件或无法在图形用户界面中编辑层的属性。

我们必须为符号图层创建元数据

 1from qgis.core import QgsSymbol, QgsSymbolLayerAbstractMetadata, QgsSymbolLayerRegistry
 2
 3class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):
 4
 5  def __init__(self):
 6    super().__init__("FooMarker", "My new Foo marker", QgsSymbol.Marker)
 7
 8  def createSymbolLayer(self, props):
 9    radius = float(props["radius"]) if "radius" in props else 4.0
10    return FooSymbolLayer(radius)
11
12fslmetadata = FooSymbolLayerMetadata()
QgsApplication.symbolLayerRegistry().addSymbolLayerType(fslmetadata)

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

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

6.8.5. 创建自定义呈现器

如果您想要定制如何选择用于渲染特性的符号的规则,那么创建一个新的呈现器实现可能会很有用。您可能想要这样做的一些用例:符号是由字段的组合确定的,符号的大小根据当前比例而变化等。

以下代码显示了一个简单的自定义渲染器,该渲染器创建两个标记符号并为每个要素随机选择一个

 1import random
 2from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer
 3
 4
 5class RandomRenderer(QgsFeatureRenderer):
 6  def __init__(self, syms=None):
 7    super().__init__("RandomRenderer")
 8    self.syms = syms if syms else [
 9      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point)),
10      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))
11    ]
12
13  def symbolForFeature(self, feature, context):
14    return random.choice(self.syms)
15
16  def startRender(self, context, fields):
17    super().startRender(context, fields)
18    for s in self.syms:
19      s.startRender(context, fields)
20
21  def stopRender(self, context):
22    super().stopRender(context)
23    for s in self.syms:
24      s.stopRender(context)
25
26  def usedAttributes(self, context):
27    return []
28
29  def clone(self):
30    return RandomRenderer(self.syms)

父级的构造函数 QgsFeatureRenderer 类需要一个呈现器名称(在呈现器中必须是唯一的)。这个 symbolForFeature() 方法是决定将什么符号用于特定功能的方法。 startRender()stopRender() 负责符号渲染的初始化/结束。这个 usedAttributes() 方法可以返回呈现器期望出现的字段名列表。最后, clone() 方法应返回呈现器的副本。

与符号层一样,可以附加一个用于配置呈现器的图形用户界面。它必须是从 QgsRendererWidget 。下面的示例代码创建一个按钮,允许用户设置第一个符号

 1from qgis.gui import QgsRendererWidget, QgsColorButton
 2
 3
 4class RandomRendererWidget(QgsRendererWidget):
 5  def __init__(self, layer, style, renderer):
 6    super().__init__(layer, style)
 7    if renderer is None or renderer.type() != "RandomRenderer":
 8      self.r = RandomRenderer()
 9    else:
10      self.r = renderer
11    # setup UI
12    self.btn1 = QgsColorButton()
13    self.btn1.setColor(self.r.syms[0].color())
14    self.vbox = QVBoxLayout()
15    self.vbox.addWidget(self.btn1)
16    self.setLayout(self.vbox)
17    self.btn1.colorChanged.connect(self.setColor1)
18
19  def setColor1(self):
20    color = self.btn1.color()
21    if not color.isValid(): return
22    self.r.syms[0].setColor(color)
23
24  def renderer(self):
25    return self.r

构造函数接收活动层的实例 (QgsVectorLayer ),全球风尚 (QgsStyle )和当前渲染器。如果没有渲染器或渲染器具有不同的类型,它将被替换为我们的新渲染器,否则我们将使用当前的渲染器(它已经具有我们需要的类型)。应该更新小部件内容以显示呈现器的当前状态。接受呈现器对话框后,小部件的 renderer() 方法被调用以获取当前的呈现器-它将被分配给层。

最后一个缺失的部分是渲染器元数据和注册表中的注册,否则使用渲染器加载层将不起作用,用户将无法从渲染器列表中选择它。让我们完成我们的RandomRender示例

 1from qgis.core import (
 2  QgsRendererAbstractMetadata,
 3  QgsRendererRegistry,
 4  QgsApplication
 5)
 6
 7class RandomRendererMetadata(QgsRendererAbstractMetadata):
 8
 9  def __init__(self):
10    super().__init__("RandomRenderer", "Random renderer")
11
12  def createRenderer(self, element):
13    return RandomRenderer()
14
15  def createRendererWidget(self, layer, style, renderer):
16    return RandomRendererWidget(layer, style, renderer)
17
18rrmetadata = RandomRendererMetadata()
QgsApplication.rendererRegistry().addRenderer(rrmetadata)

与符号层类似,抽象元数据构造器等待呈现器名称、用户可见的名称以及可选的呈现器图标的名称。这个 createRenderer() 方法将一个 QDomElement 实例,该实例可用于从DOM树恢复呈现器的状态。这个 createRendererWidget() 方法创建配置小部件。它不一定要存在,也可以返回 None 如果渲染器没有附带图形用户界面。

若要将图标与呈现器相关联,可以在 QgsRendererAbstractMetadata 构造函数作为第三个(可选)参数-RandomRendererMetadata中的基类构造函数 __init__() 功能变成了

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

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

6.9. 进一步的主题

TODO:

  • 创建/修改符号

  • 使用样式 (QgsStyle )

  • 使用色带 (QgsColorRamp )

  • 浏览符号图层和渲染器注册表