ベクターレイヤを使う

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

ベクターレイヤの重ねあわせ

レイヤの特徴の調べ方についていかに述べていきます. レイヤの特徴を読み込むため, select() によって読み込みを初期化してから nextFeature() を呼び出します:

provider = vlayer.dataProvider()

feat = QgsFeature()
allAttrs = provider.attributeIndexes()

# start data retreival: fetch geometry and all attributes for each feature
provider.select(allAttrs)

# retreive every feature with its geometry and attributes
while provider.nextFeature(feat):

  # fetch geometry
  geom = feat.geometry()
  print "Feature ID %d: " % feat.id() ,

  # show some information about the feature
  if geom.vectorType() == QGis.Point:
    x = geom.asPoint()
    print "Point: " + str(x)
  elif geom.vectorType() == QGis.Line:
    x = geom.asPolyline()
    print "Line: %d points" % len(x)
  elif geom.vectorType() == 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 map of attributes
  attrs = feat.attributeMap()

  # attrs is a dictionary: key = field index, value = QgsFeatureAttribute
  # show all attributes and their values
  for (k,attr) in attrs.iteritems():
    print "%d: %s" % (k, attr.toString())

select() によって自由にデータを呼び出せます. 4つの引数をオプションとして指定可能です:

  1. fetchAttributes

    呼び出される属性リスト. デフォルト: カラリスト

  2. rect

    空間フィルター. もし領域が選択されないと (QgsRectangle()), 全てのフィーチャが呼び出されます. デフォルト: 未選択

  3. fetchGeometry

    ジオメトリのフィーチャが呼び出されるかどうか. デフォルト: True

  4. useIntersect

    空間フィルターを使う際, 正しくインターセクションされているかどうか, 境界条件が適切か確認されます. これは正しくフィーチャが特定または選択されているかを確認するために必要です. デフォルト: False

いくつかの例:

# fetch features with geometry and only first two fields
provider.select([0,1])

# fetch features with geometry which are in specified rect, attributes won't be retreived
provider.select([], QgsRectangle(23.5, -10, 24.2, -7))

# fetch features without geometry, with all attributes
allAtt = provider.attributeIndexes()
provider.select(allAtt, QgsRectangle(), False)

フィールドインデックスを名前から作成するにはプロバイダーの fieldNameIndex() 関数を使用して下さい:

fldDesc = provider.fieldNameIndex("DESCRIPTION")
if fldDesc == -1:
  print "Field not found!"

ベクターレイヤの修正

ほとんどのベクターデータのプロバイダーはレイヤの編集をサポートしています. 一部の編集のみを認めている場合もあります. どんな機能が利用できるのかは capabilities() を使って調べることができます.

caps = layer.dataProvider().capabilities()

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

フィーチャの追加

QgsFeature インスタンスをいくつか作り、インスタンスの配列をプロバイダの addFeatures() に通します。これは二つの値を返します: 結果(true/false)と追加されたフィーチャの配列(これらのIDはデータストアによってセットされています):

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

フィーチャの削除

フィーチャを削除するには、フィーチャのIDの配列を渡すだけです:

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

フィーチャの修正

フィーチャのジオメトリの変更も属性の変更もどちらも可能です。次のサンプルは最初にインデックス 0 と 1 の属性の値を変更し、その後にフィーチャのジオメトリを変更しています:

fid = 100   # ID of the feature we will modify

if caps & QgsVectorDataProvider.ChangeAttributeValues:
  attrs = { 0 : QVariant("hello"), 1 : QVariant(123) }
  layer.dataProvider().changeAttributeValues({ fid : attrs })

if caps & QgsVectorDataProvider.ChangeGeometries:
  geom = QgsGeometry.fromPoint(QgsPoint(111,222))
  layer.dataProvider().changeGeometryValues({ fid : geom })

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

フィールド(属性)を追加するには、フィールドの定義の配列を指定する必要があります。フィールドを削除するにはフィールドのインデックスの配列を渡すだけです

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

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

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

レイヤーが編集モードかどうかを知るには isEditing() を使います — 編集機能は編集モードが有効になっているときだけ動きます。編集機能はこのように使います:

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

適切に undo/redo が動くようにするには、上記で言及しているコマンドを undo コマンドでラップする必要があります。(もし undo/redo を気にしないで、逐一変更を保存するのであれば、 データプロバイダでの編集 で簡単に実現できるでしょう。) undo機能はこのように使います:

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

beginEndCommand() は内部的に “アクティブな” コマンドを作成して、この後に続くベクターレイヤの変更を記録し続けます。 endEditCommand() を呼び出すことでundoスタックにコマンドがプッシュされ、ユーザがGUIからコマンドの undo/redo が可能になります。変更をしている途中でなにか問題が発生した場合は、 destroyEditCommand() メソッドでコマンドを削除してコマンドがアクティブであった時に行った全ての変更をロールバックするでしょう。

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

空間インデックスを使う

TODO:

空間インデックスについて

  1. 空間インデックスを作成する — 以下のコードは空のインデックスを作成する:

    index = QgsSpatialIndex()
    
  2. フィーチャにインデックスを追加する — インデックスは QgsFeature のオブジェクトを受け取り、これらをデータの内部構造に追加します。このオブジェクトは手動で作ることもできますし、プロバイダの nextFeature() を事前に呼んで取り出したものを使うこともできます:

    index.insertFeature(feat)
    
  3. 空間インデックスに何かしらの値が入れられると検索ができるようになります:

    # 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 そしてその他)をサポートしています。

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

  • 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番目のパラメータはテキストの文字コードを指定します。いくつかのドライバは正確に処理するためにこれが必要です - shapefilesもそうです — しかしながら国際化された文字を使わなければ文字コードについてはあまり気にする必要はないでしょう。今回 None を置いている4番目のパラメータは出力する CRS が指定できます – もし QgsCoordinateReferenceSystem の正しいインスタンスが渡されれば、レイヤはその CRS に変換します。

    正しいドライバの名前は supported formats by OGR を参考にしてください — “Code” カラムの値をドライバの名前として渡して下さい。オプションとして選択したフィーチャだけをエクスポートするかどうかをセットする事ができ、作成のため追加のドライバを指定するオプションを通すか、出力で属性を作らないように伝えるかをします — 全てのシンタックスはドキュメントを見て下さい。

  • フィーチャをディレクトリで選択:

    # define fields for feature attributes
    fields = { 0 : QgsField("first", QVariant.Int),
               1 : QgsField("second", QVariant.String) }
    
    # create an instance of vector file writer, it 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 some features
    fet = QgsFeature()
    fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(10,10)))
    fet.addAttribute(0, QVariant(1))
    fet.addAttribute(1, QVariant("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" .

QGIS 1.7 から 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"

次のサンプルコードはメモリープロバイダーを作成してデータ投入をしている様子です:

# 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.setAttributeMap( { 0 : QVariant("Johny"),
                       1 : QVariant(20),
                       2 : QVariant(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:", pr.fieldCount()
print "features:", pr.featureCount()
e = pr.extent()
print "extent:", e.xMin(),e.yMin(),e.xMax(),e.yMax()

# iterate over features
f = QgsFeature()
pr.select()
while pr.nextFeature(f):
  print "F:",f.id(), f.attributeMap(), f.geometry().asPoint()

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

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

QGIS v1.4ではオリジナルの実装の限界を超えるために新しいベクタのレンダリングスタックが導入されました。私達はこれを新しいシンボロジもしくは symbology-ng (new generation)と呼び、オリジナルのレンダリングスタックは古いシンボロジと呼んでいます。ベクタレイヤーごとに新しいシンボロジも古いシンボロジも使うことができますが、一度に両方を使ったり、どちらも使わないということはできません。全てのレイヤーの共通の設定ではなく、いくつかのレイヤーは新しいシンボロジを使って、他は古いシンボロジを使います。QGISにはユーザがレイヤーを読み込んだ時に標準で使うシンボロジを指定するオプションがあります。古いシンボロジは QGIS v1.x リリースの間は互換性のために残していますが、我々は QGIS v2.0 で削除することを計画しています。

現在どちらの実装を使っているかはこのように調べます:

if layer.isUsingRendererV2():
  # new symbology - subclass of QgsFeatureRendererV2 class
  rendererV2 = layer.rendererV2()
else:
  # old symbology - subclass of QgsRenderer class
  renderer = layer.renderer()

注意: もし古いバージョン(例えば QGIS < 1.4)をサポートすることを計画しているのであれば、最初に isUsingRendererV2() メソッドが存在しているかをチェックします — もし無かったら、古いシンボロジだけ存在するということです:

if not hasattr(layer, 'isUsingRendererV2'):
  print "You have an old version of QGIS"

新しいシンボロジはカスタマイズの将来性が高いため、ここからはこれを主に扱っていきます。

新しいシンボロジ

前のセクションでレンダリングについて扱っていますね、ちょっと見てみましょう:

print "Type:", rendererV2.type()

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

タイプ

クラス

詳細

singleSymbol QgsSingleSymbolRendererV2

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

categorizedSymbol QgsCategorizedSymbolRendererV2

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

graduatedSymbol QgsGraduatedSymbolRendererV2

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

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

レンダラーの中身をテキストフォームにダンプすることできます — デバッグ時に役に立つでしょう:

print rendererV2.dump()

単一シンボルレンダラ

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

カテゴリ分けシンボルレンダラ

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

カテゴリの配列を取得するには:

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

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

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

連続値シンボルレンダラ

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

レンダラで使われている領域の多くの情報を見つけるには:

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

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

もし連続値シンボルレンダラを作ろうとしているのであれば次のスニペットの例で書かれているようにします(これはシンプルな二つのクラスを作成するものを取り上げています):

from qgis.core import  (QgsVectorLayer,
                        QgsMapLayerRegistry,
                        QgsGraduatedSymbolRendererV2,
                        QgsSymbolV2,
                        QgsRendererRangeV2)

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)

シンボルを扱ってみましょう

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

  • QgsMarkerSymbolV2 - ポイントのフィーチャ用

  • QgsLineSymbolV2 - ラインのフィーチャ用

  • QgsFillSymbolV2 - ポリゴンのフィーチャ用

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

(例えばレンダラから)シンボルのインスタンスを持っていればそれの中身を調べる事ができます: type() メソッドはそれ自身がマーカか、ラインか、シンボルで満たさたものかを返します。 dump() メソッドはシンボルの簡単な説明を返します。シンボルレイヤーの配列を取得するにはこのようにします:

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

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

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

シンボルレイヤを使ってみましょう

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

次のようにシンボルレイヤクラスを与えてシンプルレイヤを作成して、シンボルレイヤのタイプの完全なリストを取得することができます。

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

出力:

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

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

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

カスタムレイヤータイプを作成する

あなたがデータをどうレンダーするかをカスタマイズしたいと考えているとします。あなたはあなたが思うままにフィーチャを描画する独自のシンボルレイヤクラスを作ることができます。次の例は指定した半径で赤い円を描画するマーカを示しています:

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)のリストを受け取ります。

普通はユーザに外観をカスタマイズさせるためにシンボルレイヤータイプの属性を設定するGUIを追加すると使いやすくなります: 上記の例であればユーザは円の半径をセットできます。次のコードは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でレイヤーの属性を編集できないなど。

私達はシンボルレイヤーのメタデータを作る必要があります:

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 を返します。

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

カスタムレンダラの作成

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

次のコードは二つのマーカーシンボルを作成して全てのフィーチャからランダムに一つ選ぶ簡単なカスタムレンダラです:

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() 関数はレンダラーのコピーを返すでしょう。

シンボルレイヤー同様、レンダラの設定をGUIからいじることができます。これは QgsRendererV2Widget の派生クラスとなります。次のサンプルコードではユーザが最初のシンボルのシンボルをセットするボタンを作成しています(訳注: サンプルを見ると色を変更しているので原文が間違っていると思われる):

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() メソッドが呼び出されます。

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

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 を返すことができます。

レンダラにアイコンを関連付けるには QgsRendererV2AbstractMetadata のコンストラクタの三番目の引数(オプション)に指定することができます — RandomRendererMetadata の __init__() 関数の中の基本クラスのコンストラクタはこうなります:

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

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

さらに詳しいトピック

TODO:
  • シンボルの作成や修正

  • スタイルの使い方(QgsStyleV2)

  • カラーランプの使い方 (QgsVectorColorRampV2)

  • ルールベースのレンダラ

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

古いシンボロジ

シンボルはフィーチャの色、サイズ、そしてその他のプロパティを決定します。レンダラは個々のフィーチャが使っているシンボルを決めるレイヤーに関連付けられています。ここには4つの使えるレンダラがあります:

  • 単一シンボルレンダラ (QgsSingleSymbolRenderer) — 全てのフィーチャは同じシンボルでレンダーされます。

  • 単一の値のレンダラ (QgsUniqueValueRenderer) — 属性の値からそれぞれのフィーチャのシンボルが選ばれます

  • 連続値シンボルレンダラ (QgsGraduatedSymbolRenderer) — シンボルがフィーチャのサブグループ (クラス) に適応されていて、数値のフィールドで計算されます。

  • 連続した色のレンダラ (QgsContinuousSymbolRenderer)

ポイントシンボルの作り方:

sym = QgsSymbol(QGis.Point)
sym.setColor(Qt.black)
sym.setFillColor(Qt.green)
sym.setFillStyle(Qt.SolidPattern)
sym.setLineWidth(0.3)
sym.setPointSize(3)
sym.setNamedPointSymbol("hard:triangle")

setNamedPointSymbol() メソッドはシンボルの shape を決定します。二つのクラスがあります: ハードコードされたシンボル (hard: というprefixをつけています) と SVGのシンボル (svg: というprefixをつけています)です。次のハードコードされたシンボルが利用できます: circle, rectangle, diamond, pentagon, cross, cross2, triangle, equilateral_triangle, star, regular_star, arrow.

SVGシンボルの作り方:

sym = QgsSymbol(QGis.Point)
sym.setNamedPointSymbol("svg:Star1.svg")
sym.setPointSize(3)

SVGシンボルは色の設定ができません.

ラインシンボルの作り方:

TODO

塗りつぶしシンボルの作り方:

TODO

単一シンボルレンダラのを作る:

sr = QgsSingleSymbolRenderer(QGis.Point)
sr.addSymbol(sym)

レイヤにレンダラを割り当てる:

layer.setRenderer(sr)

単一の値のレンダラを作る:

TODO

連続階調のシンボルレンダラを作る:

# Set the numeric field and the number of classes to be generated
fieldName = "My_Field"
numberOfClasses = 5

# Get the field index based on the field name
fieldIndex = layer.fieldNameIndex(fieldName)

# Create the renderer object to be associated to the layer later
renderer = QgsGraduatedSymbolRenderer( layer.geometryType() )

# Here you may choose the renderer mode from EqualInterval/Quantile/Empty
renderer.setMode( QgsGraduatedSymbolRenderer.EqualInterval )

# Define classes (lower and upper value as well as a label for each class)
provider = layer.dataProvider()
minimum = provider.minimumValue( fieldIndex ).toDouble()[ 0 ]
maximum = provider.maximumValue( fieldIndex ).toDouble()[ 0 ]

for i in range( numberOfClasses ):
    # Switch if attribute is int or double
    lower = ('%.*f' % (2, minimum + ( maximum - minimum ) / numberOfClasses * i ) )
    upper = ('%.*f' % (2, minimum + ( maximum - minimum ) / numberOfClasses * ( i + 1 ) ) )
    label = "%s - %s" % (lower, upper)
    color = QColor(255*i/numberOfClasses, 0, 255-255*i/numberOfClasses)
    sym = QgsSymbol( layer.geometryType(), lower, upper, label, color )
    renderer.addSymbol( sym )

# Set the field index to classify and set the created renderer object to the layer
renderer.setClassificationField( fieldIndex )

layer.setRenderer( renderer )