PyQGisCookbook--使用矢量图层(七)


使用矢量图层

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

这里的大部分工作都是基于QgsVectorLayer该类的方法。

检索有关属性的信息

您可以检索通过调用一个载体层相关的字段信息fields()一对QgsVectorLayer对象:

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

遍历矢量层

对向量层中的Feature进行遍历是最常见的任务之一。下面是执行此任务的简单基本代码示例,并显示了每个Feature有关的一些信息。假定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)

选择Feature

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

有时,以编程方式选择Feature或更改默认颜色可能很有用。

要选择所有Feature,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") )

要将Feature添加到给定图层的选定Feature列表中,可以调用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])

遍历所选Feature

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

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

遍历Feature子集

如果要遍历图层中给定的Feature子集(例如给定区域内的Feature),则必须向层中的getFeatures()添加一个QgsFeatureRequest对象。这是一个例子:

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

request = QgsFeatureRequest().setFilterRect(areaOfInterest)

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

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

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

使用setLimit()可以限制请求的Feature数。这是一个例子:

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

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

# 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'

通过使用以下任何一种矢量图层编辑方法,所做的更改将直接提交给基础数据存储(文件,数据库等)。如果您只想进行临时更改,请跳至下一部分,该部分说明如何使用编辑缓冲区进行修改

注意

如果您运行在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()

添加Feature

创建一些QgsFeature实例,并将它们的列表传递给数据文件对象的 addFeatures()方法。它将返回两个值:结果(true / false)和添加的Feature列表(其ID由数据存储区设置)。

要设置Feature的属性,您可以通过传递QgsFields对象来初始化Feature (可以从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])

删除Feature

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

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

修改Feature

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

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)

为了使撤消/重做正常工作,必须将上述调用包装到撤消命令中。(如果您不关心撤消/重做,并希望立即存储所做的更改,那么使用数据提供程序进行编辑将使您的工作更加轻松 。)

这是使用撤消功能的方法:

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

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

您还可以使用-statement将提交和回滚包装到更具语义的代码块中,如以下示例所示:with edit(layer)

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

最后将自动呼叫commitChanges()。如果发生任何异常,它将进行rollBack()所有更改。如果内部出现问题commitChanges()(方法返回False时),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()

小费

使用 基于with命令的命令直接保存更改

使用所做的更改将在最后自动提交。如果发生任何异常,它将进行 所有更改。请参见使用编辑缓冲区修改矢量层with edit(layer):commitChanges()rollBack()

使用空间索引

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

可以将没有空间索引的图层视为电话簿,在其中不对电话号码进行排序或索引。查找给定人员的电话号码的唯一方法是从头开始查找,直到找到为止。

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

  • 使用以下QgsSpatialIndex() 类创建空间索引:

    index = QgsSpatialIndex()
    
  • 向索引添加Feature-索引获取QgsFeature对象并将其添加到内部数据结构中。您可以手动创建对象,也可以使用上一个调用提供者 getFeatures()方法的对象。

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

    index = QgsSpatialIndex(layer.getFeatures())
    
  • 一旦空间索引中填充了一些值,就可以进行一些查询

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

创建矢量图层

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

  •  QgsVectorFileWriter类:一个方便将矢量文件写入磁盘的类,可以使用QgsVectorFileWriter::writeAsVectorFormat()静态调用保存整个矢量层,也可以创建该类的实例再调用 addFeature()方法。此类支持OGR支持的所有矢量格式(GeoPackage,Shapefile,GeoJSON,KML等)。

  • QgsVectorLayer类:实例化的数据源,解析数据源所提供的路径(URL)连接并访问该数据。它可以用来创建临时,基于存储器的层(memory)和连接到OGR数据集( ogr),数据库(postgresspatialitemysqlmssql)和其它数据源(wfsgpxdelimitedtext...)。

QgsVectorFileWriter举例

# Write to a GeoPackage (default)
error = QgsVectorFileWriter.writeAsVectorFormat(layer,
                                                "/path/to/folder/my_data",
                                                "")
if error[0] == QgsVectorFileWriter.NoError:
    print("success!")
# Write to an ESRI Shapefile format dataset using UTF-8 text encoding
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)字符,则指定正确的编码很重要。

# Write to an ESRI GDB file
opts = QgsVectorFileWriter.SaveVectorOptions()
opts.driverName = "FileGDB"
# if no geometry
opts.overrideGeometryType = QgsWkbTypes.NullGeometry
opts.actionOnExistingFile = QgsVectorFileWriter.ActionOnExistingFile.CreateOrOverwriteLayer
opts.layerName = 'my_new_layer_name'
error = QgsVectorFileWriter.writeAsVectorFormat(layer=vlayer,
                                                fileName=gdb_path,
                                                options=opts)
if error[0] == QgsVectorFileWriter.NoError:
  print("success!")
else:
  print(error)

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

LIST_FIELD_NAME = 'xxxx'

class ESRIValueConverter(QgsVectorFileWriter.FieldValueConverter):

  def __init__(self, layer, list_field):
    QgsVectorFileWriter.FieldValueConverter.__init__(self)
      self.layer = layer
      self.list_field_idx = self.layer.fields().indexFromName(list_field)

  def convert(self, fieldIdxInLayer, value):
    if fieldIdxInLayer == self.list_field_idx:
      return QgsListFieldFormatter().representValue(layer=vlayer,
                                                    fieldIndex=self.list_field_idx,
                                                    config={},
                                                    cache=None,
                                                    value=value)
    else:
      return value

  def fieldDefinition(self, field):
    idx = self.layer.fields().indexFromName(field.name())
    if idx == self.list_field_idx:
      return QgsField(LIST_FIELD_NAME, QVariant.String)
    else:
      return self.layer.fields()[idx]

converter = ESRIValueConverter(vlayer, LIST_FIELD_NAME)
#opts is a QgsVectorFileWriter.SaveVectorOptions as above
opts.fieldValueConverter = converter

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

对于有效的驱动程序名称,请调用该supportedFiltersAndFormats方法或通过OGR查阅支持的格式 -您应在“代码”列中传递值作为驱动程序名称。

(可选)您可以设置是仅导出所选Feature,传递进一步的特定于驱动程序的选项进行创建还是告诉编写者不要创建属性。还有许多其他(可选)参数;有关详细信息,请参见QgsVectorFileWriter的文档。

直接来自Features

from qgis.PyQt.QtCore import QVariant

# define fields for feature attributes. A QgsFields object is needed
fields = QgsFields()
fields.append(QgsField("first", QVariant.Int))
fields.append(QgsField("second", QVariant.String))

""" create an instance of vector file writer, which will create the vector file.
Arguments:
1. path to new file (will fail if exists already)
2. encoding of the attributes
3. field map
4. geometry type - from WKBTYPE enum
5. layer's spatial reference (instance of
   QgsCoordinateReferenceSystem) - optional
6. driver name for the output file """

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

# add a feature
fet = QgsFeature()

fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
fet.setAttributes([1, "text"])
writer.addFeature(fet)

# delete the writer to flush features to disk
del writer

QgsVectorLayer举例

QgsVectorLayer类支持加载的各种数据源中,让我们关注基于内存的层。内存数据源主要供插件或第三方应用程序开发人员使用。它不将数据存储在磁盘上,允许开发人员将其用作某些临时层的快速后端。

数据源支持字符串,整数和双精度字段。

内存数据源还支持空间索引,这可以通过调用createSpatialIndex()函数来启用。一旦创建了空间索引,您将能够更快地遍历较小区域内的Feature(因为不必遍历所有Feature,而仅遍历指定矩形中的Feature)。

通过将"memory"字符串传递给QgsVectorLayer构造函数来创建内存提供数据源。

QgsVectorLayer构造也可以通过URI定义"Point""LineString""Polygon""MultiPoint", "MultiLineString""MultiPolygon""None"等几何类型。

URI还可以指定URI中的内存数据源的坐标参考系统,字段和索引。语法为:

crs =definition

指定坐标参考系统,其中定义可以是以下任何一种形式: QgsCoordinateReferenceSystem.createFromString

index=yes

指定数据源将使用空间索引

field=name:type(length,precision)

指定图层的属性。该属性具有名称,并可以选择类型(整数,双精度或字符串),长度和精度。可能有多个字段定义。

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

向量层的外观(符号)

渲染矢量层时,数据的外观由与该层关联的渲染器符号给出 。符号是用于描述Feature的视觉表示形式的类,而渲染器确定将哪些符号用于渲染Feature。

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

renderer = layer.renderer()

并以此为参考,让我们对其进行一些探索

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

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

类型

描述

单一符号

QgsSingleSymbolRenderer

所有Feature使用相同的符号渲染

分类符号

QgsCategorizedSymbolRenderer

每个种类Feature使用不同的符号渲染

渐变符号

QgsGraduatedSymbolRenderer

对每个范围值的Feature使用不同的符号渲染

 

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

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

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

print(renderer.dump())

单一符号渲染器

您可以通过调用symbol()方法获取和setSymbol()方法进行来更改渲染的符号(C ++开发人员请注意:渲染器拥有该符号的所有权。)

您可以通过调用矢量层的setSymbol()修改符号。通过调用 QgsMarkerSymbolQgsLineSymbol和 QgsFillSymbol类的createSimple()来创建线多边形符号。

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

例如,您可以向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())
# 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()))

其中,value()是用于区分类别的值, label()是用于类别描述的文本,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具有三个派生类:

每个符号都包含一个或多个符号层(派生自类 QgsSymbolLayer)。符号层进行实际渲染,符号类本身仅充当符号层的容器。

通过一个符号实例(例如来自渲染器),可以对其进行探索:该type方法说明它是标记,线条还是填充符号。dump 方法可以返回该符号的简短描述。要获取符号图层列表:

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

使用color方法获得符号的颜色,setColor更改其颜色。另外使用size查询符号的大小,使用angle的方法旋转符号。对于线符号使用width方法获得线宽。

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

使用符号层

如前所述,符号层(QgsSymbolLayer的子类)确定Feature的外观。有几种通用的基本符号图层类。可以实现新的符号层类型,从而自定义Feature的任意显示。该layerType() 方法唯一地标识符号图层类-基本的和默认的符号图层类型是SimpleMarkerSimpleLine以及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类是管理所有可用的符号层类型的数据库。

要访问符号图层数据,请使用其QgsSymbolLaye::properties()方法,该方法返回确定外观的属性的键值字典。每个符号图层类型都有其使用的一组特定属性。此外,还有通用的方法colorsizeangle和 width,以及对应的设置方法。当然,大小和角度仅适用于标记符号层,而宽度仅适用于线符号层。

创建自定义符号层类型

想象一下,您想自定义数据呈现方式。您可以创建自己的符号图层类,以完全根据需要绘制Feature。这是一个以指定半径绘制红色圆圈的标记的示例

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方法确定符号层的名称;它在所有符号层中必须是唯一的。该properties方法用于属性的持久性。该clone 方法必须返回具有所有属性完全相同的符号层的副本。最后是渲染方法:startRender在渲染第一个Feature之前调用 ,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()方法可以返回此符号图层类型的设置小部件。

最后一步是将该符号层添加到注册表中。

创建自定义渲染

如果要自定义规则以选择用于渲染Feature的符号,则创建新的渲染器实现可能会很有用。有些情况您可能需要这样做,例:符号是由字段的组合确定的,符号的大小取决于当前的比例等。

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

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)),
      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))
    ]

  def symbolForFeature(self, feature, context):
    return random.choice(self.syms)

  def startRender(self, context, fields):
    super().startRender(context, fields)
    for s in self.syms:
      s.startRender(context, fields)

  def stopRender(self, context):
    super().stopRender(context)
    for s in self.syms:
      s.stopRender(context)

  def usedAttributes(self, context):
    return []

  def clone(self):
    return RandomRenderer(self.syms)

父类QgsFeatureRenderer 的构造函数需要一个渲染器名称(渲染器名称必须是唯一的)。symbolForFeature方法决定为Feature使用哪种符号。 startRenderstopRender方法分别在符号渲染的初始化/完成时调用。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.btn1.colorChanged.connect(self.setColor1)

  def setColor1(self):
    color = self.btn1.color()
    if not color.isValid(): return
    self.r.syms[0].setColor(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编译器)。

进一步的主题

TODO:

  • 创建/修改符号

  • 使用样式(QgsStyle

  • 使用色带(QgsColorRamp

  • 探索符号层和渲染器注册表

下一个   前一个

©版权所有2002-现在,QGIS项目 最近更新于2020年4月3日09:14。

使用Sphinx使用Read the Docs提供的主题构建。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值