ベクターレイヤを使う

このセクションではベクタレイヤに対して行える様々な操作について紹介していきます.

ベクターレイヤの反復処理

Iterating over the features in a vector layer is one of the most common tasks. Below is an example of the simple basic code to perform this task and showing some information about each feature. the layer variable is assumed to have a QgsVectorLayer object

iter = layer.getFeatures()
for feature in iter:
    # retrieve every feature with its geometry and attributes
    # fetch geometry
    geom = feature.geometry()
    print "Feature ID %d: " % feature.id()

    # show some information about the feature
    if geom.type() == QGis.Point:
      x = geom.asPoint()
      print "Point: " + str(x)
    elif geom.type() == QGis.Line:
      x = geom.asPolyline()
      print "Line: %d points" % len(x)
    elif geom.type() == QGis.Polygon:
      x = geom.asPolygon()
      numPts = 0
      for ring in x:
        numPts += len(ring)
      print "Polygon: %d rings with %d points" % (len(x), numPts)
    else:
      print "Unknown"

    # fetch attributes
    attrs = feature.attributes()

    # attrs is a list. It contains all the attribute values of this feature
    print attrs

Attributes can be referred by index.

idx = layer.fieldNameIndex('name')
print feature.attributes()[idx]

選択されたフィーチャへの反復処理

Convenience methods.

For the above cases, and in case you need to consider selection in a vector layer in case it exist, you can use the features() method from the built-in Processing plugin, as follows:

import processing
features = processing.features(layer)
for feature in features:
    # do whatever you need with the feature

このメソッドは、フィーチャの選択がない場合はレイヤー中のすべてのフィーチャを、フィーチャの選択がある場合は選択されているフィーチャを反復処理します。

地物を選択する必要のみある場合、 ベクタレイヤから :func: selectedFeatures メソッドを使用できます。

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

一部のフィーチャへの反復処理

もし所定の範囲内に含まれフィーチャのように、レイヤ中の所定のフィーチャにのみ処理を行いたい場合、 QgsFeatureRequest オブジェクトを getFeatures() に加えます。下記が例になります。

request=QgsFeatureRequest()
request.setFilterRect(areaOfInterest)
for f in layer.getFeatures(request):
    ...

このリクエストは各フィーチャからどの情報を取得するかを定義するために使用することも出来ます。反復処理はすべてのフィーチャを返しますが、各フィーチャの特定の情報のみ返します。

# Only return selected fields
request.setSubsetOfAttributes([0,2])
# More user friendly version
request.setSubsetOfAttributes(['name','id'],layer.pendingFields())
# Don't return geometry objects
request.setFlags(QgsFeatureRequest.NoGeometry)

ベクターレイヤの修正

Most vector data providers support editing of layer data. Sometimes they support just a subset of possible editing actions. Use the capabilities() function to find out what set of functionality is supported

caps = layer.dataProvider().capabilities()

以下で述べているどのベクターレイヤを編集するメソッドを使った場合も、元となるデータソース(ファイルやデータベースなど)に直接変更が反映されます。もし一時的な変更だけをしたいだけであれば、 編集バッファでの修正 方法について説明している次のセクションまでスキップしてください。

フィーチャの追加

Create some QgsFeature instances and pass a list of them to provider’s addFeatures() method. It will return two values: result (true/false) and list of added features (their ID is set by the data store)

if caps & QgsVectorDataProvider.AddFeatures:
  feat = QgsFeature()
  feat.addAttribute(0, 'hello')
  feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(123, 456)))
  (res, outFeats) = layer.dataProvider().addFeatures([feat])

フィーチャの削除

To delete some features, just provide a list of their feature IDs

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

フィーチャの修正

It is possible to either change feature’s geometry or to change some attributes. The following example first changes values of attributes with index 0 and 1, then it changes the feature’s geometry

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.fromPoint(QgsPoint(111,222))
  layer.dataProvider().changeGeometryValues({ fid : geom })

フィールドの追加または削除

To add fields (attributes), you need to specify a list of field definitions. For deletion of fields just provide a list of field indexes.

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

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

QGISアプリケーションでベクターを編集するには、個々のレイヤを編集モードにしてから編集を行って最後に変更をコミット(もしくはロールバック)します。全ての変更はそれらをコミットするまでは書き込まれません — これらはメモリ上の編集バッファに居続けます。これらの機能はプログラムで扱うことができます — これはデータプロバイダを直接使う方法を補完するベクターレイヤを編集する別の方法です。ベクターレイヤの編集機能をもったGUIツールを提供する際にこのオプションを使えば、ユーザにコミット/ロールバックをするのを決めさせられ、またundo/redoのような使い方をさせることができます。変更をコミットする時に、編集バッファの全ての変更はデータプロバイダに保存されます。

To find out whether a layer is in editing mode, use isEditing() — the editing functions work only when the editing mode is turned on. Usage of editing functions

# 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 given value (QVariant)
layer.changeAttributeValue(fid, fieldIndex, value)

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

In order to make undo/redo work properly, the above mentioned calls have to be wrapped into undo commands. (If you do not care about undo/redo and want to have the changes stored immediately, then you will have easier work by editing with data provider.) How to use the undo functionality

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

The beginEditCommand() will create an internal “active” command and will record subsequent changes in vector layer. With the call to endEditCommand() the command is pushed onto the undo stack and the user will be able to undo/redo it from GUI. In case something went wrong while doing the changes, the destroyEditCommand() method will remove the command and rollback all changes done while this command was active.

編集モードを始めるには startEditing() メソッドを使い、編集を止めるには commitChanges()rollback() を使います — しかしながら通常はこれらのメソッドは使う必要がなく、この機能はユーザによって始められるでしょう。

空間インデックスを使う

Spatial indexes can dramatically improve the performance of your code if you need to do frequent queries to a vector layer. Imagine, for instance, that you are writing an interpolation algorithm, and that for a given location you need to know the 10 closest points from a points layer, in order to use those point for calculating the interpolated value. Without a spatial index, the only way for QGIS to find those 10 points is to compute the distance from each and every point to the specified location and then compare those distances. This can be a very time consuming task, especially if it needs to be repeated for several locations. If a spatial index exists for the layer, the operation is much more effective.

Think of a layer without a spatial index as a telephone book in which telephone numbers are not ordered or indexed. The only way to find the telephone number of a given person is to read from the beginning until you find it.

空間インデックスは、QGISのベクトルレイヤにはデフォルトでは作成されていません、しかし作成するのは簡単です。以下が必要な手順です。

  1. create spatial index — the following code creates an empty index

    index = QgsSpatialIndex()
    
  2. add features to index — index takes QgsFeature object and adds it to the internal data structure. You can create the object manually or use one from previous call to provider’s nextFeature()

    index.insertFeature(feat)
    
  3. once spatial index is filled with some values, you can do some queries

    # returns array of feature IDs of five nearest features
    nearest = index.nearestNeighbor(QgsPoint(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 クラスを使ってベクターレイヤファイルを書き出す事ができます。これはOGRがサポートするいかなるベクターファイル(shapefiles, GeoJSON, KML そしてその他)をサポートしています。

ベクターレイヤをエクスポートする方法は二つあります:

  • from an instance of QgsVectorLayer

    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_shapes.shp", "CP1250", None, "ESRI Shapefile")
    
    if error == QgsVectorFileWriter.NoError:
      print "success!"
    
    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_json.json", "utf-8", None, "GeoJSON")
    if error == QgsVectorFileWriter.NoError:
      print "success again!"
    

    3番目のパラメータは出力の際の文字エンコーディングを指定します。いくつかのドライバーでは、正しい動作のために指定が必要なります - shapeファイルはそのうちの一つです — しかしながら、あなたが様々な国の文字列を扱わない場合、エンコーディングには多くの注意を必要としません。 None としてある4番目のパラメータには、出力の空間参照系を指定する事が出来ます — もし有効な QgsCoordinateReferenceSystem インスタンスを与えられた場合、 レイヤーはその空間参照系に投影されます。

    For valid driver names please consult the supported formats by OGR — you should pass the value in the “Code” column as the driver name. Optionally you can set whether to export only selected features, pass further driver-specific options for creation or tell the writer not to create attributes — look into the documentation for full syntax.

  • directly from features

    # define fields for feature attributes. A list of QgsField objects is needed
    fields = [QgsField("first", QVariant.Int),
              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", "CP1250", fields, QGis.WKBPoint, None, "ESRI Shapefile")
    
    if writer.hasError() != QgsVectorFileWriter.NoError:
      print "Error when creating shapefile: ", writer.hasError()
    
    # add a feature
    fet = QgsFeature()
    fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(10,10)))
    fet.setAttributes([1, "text"])
    writer.addFeature(fet)
    
    # delete the writer to flush features to disk (optional)
    del writer
    

メモリープロバイダー

メモリープロバイダーはプラグインやサードパーティアプリケーション開発者に主に使われるでしょう。これはディスクにデータを保存せず、開発者がテンポラリなレイヤーの高速なバックエンドとして使えるようになります。

プロバイダは文字列と int と double をサポートします。

メモリープロバイダーは空間インデックスもサポートしていて、プロバイダーの createSpatialIndex() を呼ぶことで有効になります。一度空間インデックスを作成したら小さい領域内でフィーチャのiterateが高速にできるようになります(これ以降は全てのフィーチャを順にたどる必要がなくなり、指定した短形内で収まります)。

メモリープロバイダーは QgsVectorLayer のコンストラクタに "memory" をプロバイダーの文字列として与えると作成されます。

コンストラクタはレイヤーのジオメトリの種類に指定したURLを与えることができます。この種類は次のものです: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" .

URIではメモリープロバイダーの座標参照系、属性フィールド、インデックスを指定することが出来ます。構文は、

crs=definition

座標参照系を指定し、この定義は QgsCoordinateReferenceSystem.createFromString() で受け付ける事ができるどんな値でも置くことができます。

index=yes

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

field=name:type(length,precision)

レイヤーの属性を指定します。属性は名前を持ち、オプションとして種類(integer, double, string)、長さと正確性を持ちます。複数のフィールドの定義を置くことになるでしょう。

The following example of a URI incorporates all these options

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

The following example code illustrates creating and populating a memory provider

# 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)])

# add a feature
fet = QgsFeature()
fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(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()

Finally, let’s check whether everything went well

# show some stats
print "fields:", len(pr.fields())
print "features:", pr.featureCount()
e = layer.extent()
print "extent:", e.xMin(),e.yMin(),e.xMax(),e.yMax()

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

ベクタレイヤーの外観(シンボロジ)

ベクタレイヤーがレンダリングされるとき、データの外観はレイヤーによって関連付けられた レンダラーシンボル によって決定されます。シンボルはフィーチャの仮想的な表現を描画するクラスで、レンダラーはシンボルが個々のフィーチャで使われるかを決定します。

指定したレイヤのレンダラーは下記のように得ることが出来ます

renderer = layer.rendererV2()

And with that reference, let us explore it a bit

print "Type:", rendererV2.type()

次の表は QGIS コアライブラリに存在するいくつかのよく知られたレンダラーです:

タイプ

クラス

詳細

singleSymbol QgsSingleSymbolRendererV2

全てのフィーチャを同じシンボルでレンダーします

categorizedSymbol QgsCategorizedSymbolRendererV2

カテゴリごとに違うシンボルを使ってフィーチャをレンダーします

graduatedSymbol QgsGraduatedSymbolRendererV2

それぞれの範囲の値によって違うシンボルを使ってフィーチャをレンダーします

カスタムレンダラーのタイプになることもあるので、上記のタイプになるとは思い込まないでください。 QgsRendererV2Registry シングルトンを検索して現在利用可能なレンダラーを見つけることもできます。

It is possible to obtain a dump of a renderer contents in text form — can be useful for debugging

print rendererV2.dump()

Single Symbol Renderer

レンダリングが使っているシンボルは symbol() メソッドで取得することができ、 setSymbol() メソッドで変更することができます(C++開発者へメモ: レンダラーはシンボルのオーナーシップをとります)。

Categorized Symbol Renderer

分類するのに使われる属性名を検索したりセットしたりすることができます: classAttribute() メソッドと setClassAttribute() メソッドを使います。

To get a list of categories

for cat in rendererV2.categories():
  print "%s: %s :: %s" % (cat.value().toString(), cat.label(), str(cat.symbol()))

value() はカテゴリを区別にするのに使う値で、 label() はカテゴリの詳細に使われるテキストで、 symbol() メソッドは割り当てられているシンボルを返します。

レンダラはたいていオリジナルのシンボルと識別をするためにカラーランプを保持しています: sourceColorRamp() メソッドと sourceSymbol() メソッドから呼び出せます。

Graduated Symbol Renderer

このレンダラは先ほど暑かったカテゴリ分けシンボルのレンダラととても似ていますが、クラスごとの一つの属性値の代わりに領域の値として動作し、そのため数字の属性のみ使うことができます。

To find out more about ranges used in the renderer

for ran in rendererV2.ranges():
  print "%f - %f: %s %s" % (
      ran.lowerValue(),
      ran.upperValue(),
      ran.label(),
      str(ran.symbol())
     )

属性名の分類を調べるために classAttribute() をまた使うことができ、 sourceSymbol() メソッドと sourceColorRamp() メソッドも使うことができます。さらに作成された領域の測定する mode() メソッドもあります: 等間隔や変位値、その他のメソッドと一緒に使います。

If you wish to create your own graduated symbol renderer you can do so as illustrated in the example snippet below (which creates a simple two class arrangement)

from qgis.core import *

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 = QgsSymbolV2.defaultSymbol(myVectorLayer.geometryType())
mySymbol1.setColor(myColour)
mySymbol1.setAlpha(myOpacity)
myRange1 = QgsRendererRangeV2(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 = QgsSymbolV2.defaultSymbol(
     myVectorLayer.geometryType())
mySymbol2.setColor(myColour)
mySymbol2.setAlpha(myOpacity)
myRange2 = QgsRendererRangeV2(myMin, myMax, mySymbol2 myLabel)
myRangeList.append(myRange2)
myRenderer = QgsGraduatedSymbolRendererV2('', myRangeList)
myRenderer.setMode(QgsGraduatedSymbolRendererV2.EqualInterval)
myRenderer.setClassAttribute(myTargetField)

myVectorLayer.setRendererV2(myRenderer)
QgsMapLayerRegistry.instance().addMapLayer(myVectorLayer)

Working with Symbols

シンボルを表現するには、 QgsSymbolV2 ベースクラス由来の三つの派生クラスを使います:

  • QgsMarkerSymbolV2 — for point features
  • QgsLineSymbolV2 — for line features
  • QgsFillSymbolV2 — for polygon features

全てのシンボルは一つ以上のシンボルレイヤーから構成されます (QgsSymbolLayerV2 の派生クラスです)。シンボルレイヤーは実際にレンダリングをして、シンボルクラス自信はシンボルレイヤのコンテナを提供するだけです。

Having an instance of a symbol (e.g. from a renderer), it is possible to explore it: type() method says whether it is a marker, line or fill symbol. There is a dump() method which returns a brief description of the symbol. To get a list of symbol layers

for i in xrange(symbol.symbolLayerCount()):
  lyr = symbol.symbolLayer(i)
  print "%d: %s" % (i, lyr.layerType())

シンボルが使っている色を得るには color() メソッドを使い、 setColor() でシンボルの色を変えます。マーカーシンボルは他にもシンボルのサイズと回転角をそれぞれ size() メソッドと angle() メソッドで取得することができ、ラインシンボルは width() メソッドでラインの幅を返します。

サイズと幅は標準でミリメートルが使われ、角度は 度 が使われます。

Working with Symbol Layers

前に述べたようにシンボルレイヤ(QgsSymbolLayerV2 のサブクラスです) はフィーチャの外観を決定します。一般的に使われるいくつかの基本となるシンボルレイヤのクラスがあります。これは新しいシンボルレイヤの種類を実装を可能とし、それによってフィーチャがどのようにレンダーされるかを任意にカスタマイズできます。 layerType() メソッドはシンボルレイヤクラスの一意に識別します — 基本クラスは標準で SimpleMarker 、 SimpleLine 、 SimpleFill がシンボルレイヤのタイプとなります。

You can get a complete list of the types of symbol layers you can create for a given symbol layer class like this

from qgis.core import QgsSymbolLayerV2Registry
myRegistry = QgsSymbolLayerV2Registry.instance()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbolV2.Marker):
  print item

Output

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

QgsSymbolLayerV2Registry クラスは利用可能な全てのシンボルレイヤタイプのデータベースを管理しています。

シンボルレイヤのデータにアクセスするには、 properties() メソッドを使い、これは表現方法を決定しているプロパティの辞書のキー値を返します。それぞれのシンボルレイヤタイプはそれが使っている特定のプロパティの集合を持っています。さらに、共通して使えるメソッドとして color(), size(), angle(), width() がそれぞれセッターと対応して存在します。もちろん size と angle はマーカーシンボルレイヤだけで利用可能で、 width はラインシンボルレイヤだけで利用可能です。

Creating Custom Symbol Layer Types

Imagine you would like to customize the way how the data gets rendered. You can create your own symbol layer class that will draw the features exactly as you wish. Here is an example of a marker that draws red circles with specified radius

class FooSymbolLayer(QgsMarkerSymbolLayerV2):

  def __init__(self, radius=4.0):
    QgsMarkerSymbolLayerV2.__init__(self)
    self.radius = radius
    self.color = QColor(255,0,0)

  def layerType(self):
    return "FooMarker"

  def properties(self):
    return { "radius" : str(self.radius) }

  def startRender(self, context):
    pass

  def stopRender(self, context):
    pass

  def renderPoint(self, point, context):
    # Rendering depends on whether the symbol is selected (Qgis >= 1.5)
    color = context.selectionColor() if context.selected() else self.color
    p = context.renderContext().painter()
    p.setPen(color)
    p.drawEllipse(point, self.radius, self.radius)

  def clone(self):
    return FooSymbolLayer(self.radius)

layerType() メソッドはシンボルレイヤーの名前を決定し、全てのシンボルレイヤーの中で一意になります。プロパティは属性の持続として使われます。 clone() メソッドは全ての全く同じ属性を含んだシンボルレイヤーのコピーを返さなくてはなりません。最後にレンダリングのメソッドについて: startRender() はフィーチャが最初にレンダリングされる前に呼び出され、 stopRender() はレンダリングが終わったら呼び出されます。そして renderPoint() メソッドでレンダリングを行います。ポイントの座標は出力対象の座標に常に変換されます。

ポリラインとポリゴンではレンダリングのメソッドが違うだけです: (ポリラインでは)それぞれのラインの配列を受け取る renderPolyline() を使います。 renderPolygon() は最初のパラメータを外輪としたポイントのリストと、2つ目のパラメータに内輪(もしくは None)のリストを受け取ります。

Usually it is convenient to add a GUI for setting attributes of the symbol layer type to allow users to customize the appearance: in case of our example above we can let user set circle radius. The following code implements such widget

class FooSymbolLayerWidget(QgsSymbolLayerV2Widget):
  def __init__(self, parent=None):
    QgsSymbolLayerV2Widget.__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()"))

このwidgetはシンボルプロパティのダイアログに組み込むことができます。シンボルプロパティのダイアログでシンボルレイヤータイプを選択したときにこれはシンボルレイヤーのインスタンスとシンボルレイヤー widget のインスタンスを作成します。そしてwidgetをシンボルレイヤーを割り当てるために setSymbolLayer() メソッドを呼び出します。このメソッドでwidgetがシンボルレイヤーの属性を反映するようUIを更新します。 symbolLayer() 関数はシンボルが使ってるプロパティダイアログがシンボルレイヤーを再度探すのに使われます。

いかなる属性の変更時でも、プロパティダイアログにシンボルプレビューを更新させるために widget は changed() シグナルを発生します。

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

We will have to create metadata for the symbol layer

class FooSymbolLayerMetadata(QgsSymbolLayerV2AbstractMetadata):

  def __init__(self):
    QgsSymbolLayerV2AbstractMetadata.__init__(self, "FooMarker", QgsSymbolV2.Marker)

  def createSymbolLayer(self, props):
    radius = float(props[QString("radius")]) if QString("radius") in props else 4.0
    return FooSymbolLayer(radius)

  def createSymbolLayerWidget(self):
    return FooSymbolLayerWidget()

QgsSymbolLayerV2Registry.instance().addSymbolLayerType(FooSymbolLayerMetadata())

レイヤータイプ(レイヤーが返すのと同じもの)とシンボルタイプ(marker/line/fill)を親クラスのコンストラクタに渡します。 createSymbolLayer() は辞書の引数の props で指定した属性をもつシンボルレイヤーのインスタンスを作成をしてくれます。 (キー値は QString のインスタンスで、決して “str” のオブジェクトではないのに気をつけましょう) そして createSymbolLayerWidget() メソッドはこのシンボルレイヤータイプの設定 widget を返します。

最後にこのシンボルレイヤーをレジストリに追加します — これで完了です。

Creating Custom Renderers

もしシンボルがフィーチャのレンダリングをどう行うかをカスタマイズしたいのであれば、新しいレンダラーの実装を作ると便利かもしれません。いくつかのユースケースとしてこんなことをしたいのかもしれません: フィールドの組み合わせからシンボルを決定する、現在の縮尺に合わせてシンボルのサイズを変更するなどなど。

The following code shows a simple custom renderer that creates two marker symbols and chooses randomly one of them for every feature

import random

class RandomRenderer(QgsFeatureRendererV2):
  def __init__(self, syms=None):
    QgsFeatureRendererV2.__init__(self, "RandomRenderer")
    self.syms = syms if syms else [QgsSymbolV2.defaultSymbol(QGis.Point), QgsSymbolV2.defaultSymbol(QGis.Point)]

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

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

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

  def usedAttributes(self):
    return []

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

親クラスの QgsFeatureRendererV2 のコンストラクタはレンダラの名前(レンダラの中で一意になる必要があります)が必要です。 symbolForFeature() メソッドは個々のフィーチャでどのシンボルが使われるかを一つ決定します。 startRender()stopRender() それぞれシンボルのレンダリングの初期化/終了を処理します。 usedAttributes() メソッドはレンダラが与えられるのを期待するフィールド名のリストを返すことができます。最後に clone() 関数はレンダラーのコピーを返すでしょう。

Like with symbol layers, it is possible to attach a GUI for configuration of the renderer. It has to be derived from QgsRendererV2Widget. The following sample code creates a button that allows user to set symbol of the first symbol

class RandomRendererWidget(QgsRendererV2Widget):
  def __init__(self, layer, style, renderer):
    QgsRendererV2Widget.__init__(self, layer, style)
    if renderer is None or renderer.type() != "RandomRenderer":
      self.r = RandomRenderer()
    else:
      self.r = renderer
    # setup UI
    self.btn1 = QgsColorButtonV2("Color 1")
    self.btn1.setColor(self.r.syms[0].color())
    self.vbox = QVBoxLayout()
    self.vbox.addWidget(self.btn1)
    self.setLayout(self.vbox)
    self.connect(self.btn1, SIGNAL("clicked()"), self.setColor1)

  def setColor1(self):
    color = QColorDialog.getColor(self.r.syms[0].color(), self)
    if not color.isValid(): return
    self.r.syms[0].setColor(color);
    self.btn1.setColor(self.r.syms[0].color())

  def renderer(self):
    return self.r

コンストラクタはアクティブなレイヤー(QgsVectorLayer)とグローバルなスタイル(QgsStyleV2)と現在のレンダラのインスタンスを受け取ります。もしレンダラが無かったり、レンダラが違う種類のものだったら、コンストラクタは新しいレンダラに差し替えるか、そうでなければ現在のレンダラー(必要な種類を持つでしょう)を使います。widgetの中身はレンダラーの現在の状態を表示するよう更新されます。レンダラダイアログが受け入れられたときに、現在のレンダラを取得するために widget の renderer() メソッドが呼び出されます。

The last missing bit is the renderer metadata and registration in registry, otherwise loading of layers with the renderer will not work and user will not be able to select it from the list of renderers. Let us finish our RandomRenderer example

class RandomRendererMetadata(QgsRendererV2AbstractMetadata):
  def __init__(self):
    QgsRendererV2AbstractMetadata.__init__(self, "RandomRenderer", "Random renderer")

  def createRenderer(self, element):
    return RandomRenderer()
  def createRendererWidget(self, layer, style, renderer):
    return RandomRendererWidget(layer, style, renderer)

QgsRendererV2Registry.instance().addRenderer(RandomRendererMetadata())

シンボルレイヤーと同様に、abstract metadataのコンストラクタはレンダラの名前を受け取るのを期待して、この名前はユーザに見え、レンダラのアイコンの追加の名前となります。 createRenderer() メソッドには QDomElement のインスタンスを渡してレンダラの状態を DOM ツリーから復元するのに使います。 createRendererWidget() メソッドは設定のwidgetを作成します。これは必ず存在する必要はなく、もしレンダラがGUIからいじらないのであれば None を返すことができます。

To associate an icon with the renderer you can assign it in QgsRendererV2AbstractMetadata constructor as a third (optional) argument — the base class constructor in the RandomRendererMetadata __init__() function becomes

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

アイコンはあとからメタデータクラスの setIcon() を使って関連付けることもできます。アイコンはファイルから読み込むこと(上記を参考)も Qt のリソース から読み込むこともできます(PyQt4 はパイソン向けの .qrc コンパイラを含んでいます)。

Further Topics

TODO:
creating/modifying symbols working with style (QgsStyleV2) working with color ramps (QgsVectorColorRampV2) rule-based renderer (see this blogpost) exploring symbol layer and renderer registries