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. 属性に関する情報を取得する

クラス :QgsVectorLayer <qgis.core.QgsVectorLayer> オブジェクトに対して fields() を呼び出すことでベクタレイヤに関連するフィールドに関する情報を取得することができます:

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

QgsVectorLayer クラスの displayField()mapTipTemplate() メソッドは、 表示名プロパティ タブで使用するフィールドとテンプレートについての情報を提供します。

ベクタレイヤを読み込むと、常にフィールドが Display Name としてQGISによって選択され、HTML Map Tip はデフォルトで空になっています。これらのメソッドを使用すると、簡単に両方を取得することができます:

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

注釈

表示名 をフィールドから式に変更する場合、 displayField() の代わりに displayExpression() を使用しなければなりません。

6.2. ベクタレイヤの反復処理

ベクタレイヤの地物を反復することは、最も一般的なタスクの1つです。以下は、このタスクを実行するためのシンプルな基本コードの例で、各地物に関するいくつかの情報を表示します。変数 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)

選択色を変更するには、次の例のように、 QgsMapCanvassetSelectionColor() メソッドを使うことができます:

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

また、属性はインデックスで参照することもできます。これは名前を使うより少し速いです。例えば、2番目の属性を取得する場合:

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. 一部の地物の反復処理

もし、レイヤ内のあるエリア内の地物のサブセットを反復処理したい場合、 getFeatures() 呼び出しに QgsFeatureRequest> オブジェクトを追加する必要があります。以下はその例です:

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)

;meth:setLimit() <qgis.core.QgsFeatureRequest.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'

ベクタレイヤを編集する以下の方法はいずれも、変更が直接、レイヤの裏にあるデータストア(ファイルやデータベースなど)にコミットされます。一時的な変更をしたいだけの場合にどうすればよいかの説明は、次のセクション ベクタレイヤを編集バッファで修正する でしているので、以下を飛ばしてそちらに進んでください。

注釈

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() メソッドにそれらのリストを渡します。このメソッドは、結果 (True または False) と追加された地物のリスト (それらのIDはデータストアによって設定されます)mp2つの値を返します。

地物の属性を設定するには、 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 })

Tip

ジオメトリのみの編集にはQgsVectorLayerEditUtilsクラスをお勧めします

もし、ジオメトリを変更するだけなら、ジオメトリを編集(移動、頂点の挿入または移動など)するのに便利なメソッドを提供する QgsVectorLayerEditUtils を使うことを検討できるかもしれません。

6.4.4. ベクタレイヤを編集バッファで修正する

QGISアプリケーションでベクタを編集する場合、まず特定のレイヤの編集モードを開始し、次にいくつかの変更を行い、最後に変更をコミット(またはロールバック)する必要があります。コミットするまでは、変更内容はすべて書き込まれず、レイヤのインメモリ編集バッファに残ります。この機能はプログラムでも使用できます。これは、データプロバイダの直接利用を補完する、ベクタレイヤ編集の別の方法です。ベクタレイヤ編集用のGUIツールを提供する場合、このオプションを使用します。これは、コミット/ロールバックするかどうかをユーザーが決定でき、元に戻す/やり直すが使用できるようになるからです。変更がコミットされると、編集バッファのすべての変更がデータプロバイダーに保存されます。

メソッドはすでに見たプロバイダにおけるものとよく似ていますが、プロバイダではなく 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)

取り消し/やり直しを適切に機能させるためには、上記のメソッド呼び出しを undo コマンドでラップしなければなりません。取り消し/やり直し機能が不要で、変更を即座に保存したい場合は、 データプロバイダを使って編集 したほうが手軽でしょう。

取り消し機能を使用するには次のように行います。

 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() メソッドの呼び出しによって、コマンドはアンドゥスタックにプッシュされ、ユーザーがGUIから取り消し/やり直しをすることができるようになります。変更の最中に何か不具合が生じたときは、 destroyEditCommand() メソッドによってコマンドは削除され、コマンドがアクティブな間に行われたすべての変更はロールバックされます。

次の例に示すように、よりセマンティックなコードブロックにコミットとロールバックをラップする with edit(layer) 文も使用できます。

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

Tip

with に基づくコマンドを使って変更を直接保存する

with edit(layer): を使うと、最後に commitChanges() を呼び出して自動的に変更がコミットされます。例外が発生した場合は、全ての変更を rollBack() します。ベクタレイヤを編集バッファで修正する を参照してください。

6.5. 空間インデックスを使う

空間インデックスは、頻繁にベクタレイヤに問い合わせをする必要がある場合、コードのパフォーマンスを劇的に改善します。例えば、補間アルゴリズムを書いていて、補間値の計算に使用するために与えられた位置に対して最も近い10点をポイントレイヤから求める必要がある、と想像してください。空間インデックスが無いと、QGISがこれらの10ポイントを求める方法は、すべてのポイントから指定の場所への距離を計算してそれらの距離を比較することしかありません。これは、いくつかの場所について繰り返す必要がある場合は特に、非常に時間のかかる処理となります。もし空間インデックスがレイヤに作成されていれば、処理はもっと効率的になります。

空間インデックスの無いレイヤは、電話番号が順番に並んでいない、もしくは索引の無い電話帳と思ってください。所定の人の電話番号を見つける唯一の方法は、巻頭からその番号を見つけるまで読むだけです。

空間インデックスは、QGISベクタレイヤに対してデフォルトでは作成されていませんが、簡単に作成できます。しなければいけないことはこうです:

  • QgsSpatialIndex クラスを使用して空間インデックスを作成します:

    index = QgsSpatialIndex()
    
  • インデックスに地物を追加します --- インデックスは 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 空間インデックスを使うこともできます。このインデックスは 標準QgsSpatialIndex と似ていますが:

  • 単独のポイント地物 だけ をサポートします

  • is static (no additional features can be added to the index after the construction)

  • とても高速です!

  • 追加の地物要求を必要とせず、元の地物のポイントを直接検索することができます

  • 真の 距離ベース の検索に対応しています。つまり検索点からある半径にあるすべての点を返します

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) やデータベース (postgres, spatialite, mysql, mssql) など (wfs`, ``gpx, delimitedtext...)へ接続するために使用することが出来ます。

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 の有効なインスタンスが第4パラメータとして渡された場合、レイヤはそのCRSに変換されます。

有効なドライバ名については、 supportedFiltersAndFormats() メソッドを呼ぶか、‘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 クラスによってサポートされているすべてのデータプロバイダのうちから、ここではメモリレイヤに焦点をあてましょう。メモリプロバイダは主にプラグインやサードパーティ製アプリの開発者に使われることを意図しています。ディスクにデータを格納することをしないため、開発者はなんらかの一時的なレイヤのための手っ取り早いバックエンドとしてこれを使うことができます。

このプロバイダは属性フィールドの型として string、int、double をサポートします。

メモリプロバイダは空間インデックスもサポートしています。これはプロバイダの createSpatialIndex() 関数を呼び出すことによって有効になります。空間インデックスが作成されると、複数の地物にわたって行う処理を、より小さな領域内でより速く行うことができます。これはあらためて地物すべてを走査する必要がなく、指定された領域内のみを走査すればよいからです。

メモリプロバイダは QgsVectorLayer コンストラクタにプロバイダ文字列として "memory" を渡すと作ることができます。

コンストラクタはレイヤのジオメトリタイプを定義するURIも必要とします。これは "Point""LineString""Polygon""MultiPoint""MultiLineString""MultiPolygon"``"None"``のうちのひとつです。

URIではメモリプロバイダの座標参照系、属性フィールド、URI内でのメモリプロバイダのインデックスも指定できます。構文は、

crs=definition

座標参照系を指定します。定義には QgsCoordinateReferenceSystem.createFromString() で受け入れられる形式のいずれかを使用できます。

index=yes

プロバイダが空間インデックスを使うように指定します。

field=name:type(length,precision)

レイヤの属性を指定します。属性は名前を持ち、オプションとして型(integer, double, string)、長さ、および精度を持ちます。フィールドの定義は複数あってかまいません。

次のサンプルは全てのこれらのオプションを含んだURLです:

"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. ベクタレイヤの表現(シンボロジ)

ベクタレイヤがレンダリングされるとき、データの表現はレイヤに関連付けられた レンダラーシンボル によって決定されます。シンボルは地物の視覚的表現を処理するクラスで、レンダラはそれぞれの地物でどのシンボルが使われるかを決定します。

指定したレイヤのレンダラは、以下のように取得することができます:

renderer = layer.renderer()

この参照を利用して、少しだけ探索してみましょう:

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

QGISのコアライブラリには、いくつかの既知のレンダラータイプが用意されています:

タイプ

クラス

詳細

singleSymbol

QgsSingleSymbolRenderer

単一シンボル。全ての地物を同じシンボルでレンダリングします

categorizedSymbol

QgsCategorizedSymbolRenderer

カテゴリごとに違うシンボルを使って地物をレンダリングします

graduatedSymbol

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() に該当するシンボルのインスタンスを渡して呼び出すことで変更できます。ポイントラインポリゴン レイヤのシンボルは、対応するクラス QgsMarkerSymbol, QgsLineSymbol, QgsFillSymbolcreateSimple() 関数を呼び出して作成することができます。

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())
{'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 という基本クラスがあり、3つの派生クラスがあります:

**すべてのシンボルは1つ以上のシンボルレイヤ**( 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() メソッドはシンボルレイヤクラスを一意に特定します。基本的でデフォルトのものは SimpleMarker, SimpleLine, SimpleFill というシンボルレイヤクラスです。

シンボルレイヤクラスで作成できるシンボルレイヤの種類は、以下のコードで全て把握することができます:

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() はれレンダリングのために呼ばれます。点の座標はすでに出力座標に変換されています。

ポリラインとポリゴンにとって、違いはレンダリング方法だけです: renderPolyline() はラインのリストを受け取り、 renderPolygon() は最初のパラメータとして外側のリングの点のリスト、第2のパラメータとして内側のリングのリスト(またはなし)を受け取ります。

普通はユーザーに外観をカスタマイズさせるためにシンボルレイヤータイプの属性を設定するGUIを追加すると使いやすくなります: 上記の例であればユーザーは円の半径を設定できます。次のコードはそのようなウィジェットの実装となります:

 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 にこれらの新しいクラスを知らせる方法です。これはレジストリにシンボルレイヤーを追加すれば完了です。レジストリに追加しなくてもシンボルレイヤーを使うことはできますが、いくつかの機能が動かないでしょう: 例えばカスタムシンボルレイヤーを使ってプロジェクトファイルを読み込んだり、GUIでレイヤーの属性を編集できないなど。

シンボルレイヤーのメタデータを作る必要があるでしょう

 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)

親クラスのコンストラクタにレイヤーのタイプ(レイヤーが返すものと同じ)とシンボルのタイプ(marker/line/fill)を渡す必要があります。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() メソッドは、レンダラーのコピーを返す必要があります。

シンボルレイヤーと同様に、レンダラーの設定用のGUIを付けることが可能です。これは 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 <qgis.core.QgsVectorLayer>`)、グローバルスタイル (QgsStyle <qgis.core.QgsStyle>`) と現在のレンダラのインスタンスを受け取ります。レンダラーがない場合、またはレンダラーのタイプが異なる場合は、新しいレンダラーに置き換えられ、そうでない場合は、現在のレンダラー(必要なタイプを既に持っている)を使用します。ウィジェットのコンテンツは、レンダラーの現在の状態を示すように更新する必要があります。レンダラダイアログが受け入れられると、ウィジェットの renderer() メソッドが呼び出されて現在のレンダラーを取得します --- それがレイヤーに割り当てられることになります。

最後のちょっとした作業はレンダラーのメタデータとレジストリへの登録です。これらをしないとレンダラーのレイヤーの読み込みは動かず、ユーザーはレンダラーのリストから選択できないでしょう。では、私達の RandomRenderer の例を終わらせましょう

 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() メソッドは、設定ウィジェットを作成します。レンダラーにGUIが付属していない場合は、存在する必要はありませんし、None を返すこともできます。

アイコンをレンダラーに関連付けるには、 QgsRendererAbstractMetadata コンストラクタで第3引数(オプション)として指定します --- RandomRendererMetadata __init__() 関数にある基底クラスのコンストラクタは次のようになります

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

アイコンはメタデータクラスの setIcon() メソッドを使って後で関連付けることもできます。アイコンはファイルから読み込むこともできますし、Qt resource (PyQt5はPython用の .qrcコンパイラを含んでいます)からも読み込むことができます。

6.9. より詳しいトピック

TODO:

  • シンボルの作成や修正

  • スタイルの操作 (QgsStyle)

  • カラーランプの操作 (QgsColorRamp)

  • シンボルレイヤーとレンダラーのレジストリを調べる方法