QGIS API: 小技集

筆者が遭遇したQGIS APIの小技集です。


レイヤーにSRSを設定する

TIFF + TFWのレイヤーだとSRSが設定されていなかったりします。
本体ではラスタレイヤーのプロパティから設定できます。

全般タブ

全般タブにSRS情報があり、デフォルトではEPSG:4326になっている

デフォルトのSRSはメニューの[Settings]-[Options]から、[CRS]タブで設定できる

SRS選択画面

変更ボタンを押すとSRS選択画面が出る

APIでは以下のようにします。

	# 平面直角座標系(新成果)を設定する場合
	# 変数zone_noに系番号が入っているとする
	srs = QgsCoordinateReferenceSystem(2442+zone_no, QgsCoordinateReferenceSystem.EpsgCrsId)
	layer.setCrs(srs)

詳細はQGIS API 空間参照、座標変換を参照してください。

QgsCoordinateReferenceSystem()関数の第2引数は、
デフォルトではPostgisCrsIdになっているので、
EPSGで指定したい場合はこうします。


ベクタレイヤーの全ての要素を消去する

要素の消去ではまず消去する要素を選択状態にする必要があります。
なので、以下のようにします。

	# 編集開始
	layer.startEditing()

	# 何も選択されていない状態から、
	# 選択を反転して全て選択状態にする
	layer.invertSelection()

	# 選択された要素を削除
	layer.deleteSelectedFeatures()

	# 編集を確定
	layer.commitChanges()

画面内に表示される要素を検索する

現在の画面に表示されるであろう要素をPostGISから検索する小技です。
以下の例は、図郭データmap_frameから、表示範囲にかかる図郭を検索しています。

後述のQgsVectorLayer::setSubsetString()での条件セットや、
QgsVectorLayer::select()関数で選択矩形を利用する方法などもあります。

	# Canvasの表示範囲を取得する
	extent = self.canvas.extent()

	# sql文を作成
	# この例ではSRIDは平面直角座標9系に設定
	sql = """select location from map_frame where
	ST_Intersects(map_frame.the_geom, GeomFromEWKT('SRID=2451;POLYGON(("""
	  + str(extent.asPolygon()) + """))'));"""

	# psycopg2でSQLを発行する
	# 変数connにデータベースへの接続情報が入っている
	cur = conn.cursor()
	cur.execute(sql)
	rows = cur.fetchall()

選択要素の表示色を設定する

本体では、メニューの[Sttings]-[Project properties]で設定できます。
全てのプロジェクトで変更するなら[Settings]-[Options]の[General]タブで設定できます。
なお、ここでの選択色とはidentifyツールでの選択ではなく、
例えば属性テーブルから選択した場合などの選択色です。


プロジェクトプロパティ画面

プロジェクトプロパティ画面

APIでは以下のようにします。

	# QgsVectorLayerオブジェクトからQgsRendererオブジェクトを取得
	renderer = layer.renderer()

	# 選択色を赤に設定
	renderer.setSelectionColor(QColor.fromRgb(255,0,0))


Renderer V2では仕様が変わっています。
近いうちにしっかり整理したいと思います。


座標が定義されていない画像を表示すると

APIに限らず本体でもそうですが、例えば幅100pix、高さ200pixの画像を表示すると、
左上が(0, 0)、右下が(100, -200)に表示されます。
したがって、Y座標の符号を反転すればマウスの位置から画像座標を取得することができます。

v1.5から採用されている新Georeferencerがこの機能を利用してるので、
すでに座標を持っている画像をGeoreferencerに読み込む場合はあらかじめ位置情報を消去する必要があります。


現在選択されているレイヤーの種別を調べるには

プラグインでは現在選択されているレイヤーに対して操作を行うことが多いですが、
レイヤーがベクタレイヤー/ラスタレイヤーの場合だけ操作を行うという場合がほとんどでしょう。
そこで、あらかじめ種別を調べるには以下のようにします。

	# self.canvasにQgsMapCanvas変数を保持しているものとして
	layer = self.canvas().currentLayer()
	if ( layer.type() != QgsMapLayer.VectorLayer ):
		return

レイヤー種別を保持しているのはQgsMapLayer::LayerType変数でpublic属性ですが、
少なくともPythonの場合はこれを直接参照しても正しい結果を得ることができません。


QgsMapTool派生クラスのコンストラクタ

QgsMapTool派生クラスでコンストラクタ(__init__())を定義した場合は、
親クラスのコンストラクタを明示的に呼び出さないとエラーになってしまいます(Pythonなら当然か、、、)。

class MyTool(QgsMapTool):

    def __init__(self, canvas):
        QgsMapTool.__init__(self, canvas)

ツールバーを追加するには

プラグインから複数のツールボタンを登録したい場合は新しくツールバーを追加してまとめるようにするべきです。
この場合は以下のようにします。

	def __init__(self, iface):
		self.iface = iface

	def initGui(self):
		self.action_01 = QAction(QIcon(":/icon/i-man.png"), "1", self.iface.mainWindow())
		self.action_02 = QAction(QIcon(":/icon/i-man.png"), "2", self.iface.mainWindow())
		self.action_03 = QAction(QIcon(":/icon/i-man.png"), "3", self.iface.mainWindow())

				・・・中略・・・

		# ツールバーを作成
		self.toolBar = self.iface.addToolBar("churen")
		self.toolBar.setObjectName("churen")

		# ツールバーにボタンを追加
		self.toolBar.addAction(self.action_01)
		self.toolBar.addAction(self.action_02)
		self.toolBar.addAction(self.action_03)

				・・・中略・・・

	def unload(self):
		# ツールバーを削除
		del self.toolBar


実行結果(役満)

実行結果(役満)

このようなアイコンにしてしまうとどのボタンがどの機能なのかわからなくなってしまうので気をつけましょう。


ベクタレイヤーに対してスタイルを適用するには

ベクタレイヤーの表示スタイルは.qmlファイルとして保存することができます。
例として以下のようなコードにしたとします。

C++コード
   1:
   2: bool WndMain::loadVectorLayer( QString &strUri, QString &strBaseName, QString &strQml )
   3: {
   4: 	bool bFlag;
   5:
   6: 	QgsVectorLayer *pqvl = new QgsVectorLayer( strUri, strBaseName, "ogr" );
   7: 	if ( pqvl == NULL )
   8: 	{
   9: 		QMessageBox::warning( this, tr("ERROR"), tr( "ベクタレイヤーの読み込みに失敗しました [%1]" )
   				.arg( strUri ) );
  10: 		return false;
  11: 	}
  12:
  13: 	pqvl->loadNamedStyle( strQml, bFlag );
  14: 	if ( !bFlag )
  15: 	{
  16: 		QMessageBox::warning( this, tr("ERROR"), tr("スタイルが無効です [%1]").arg( strQml ) );
  17: 		return false;
  18: 	}
  19:
  20: 	QgsMapLayerRegistry::instance()->addMapLayer( pqvl, true );
  21: 	m_lstLayers.append( QgsMapCanvasLayer( pqvl, true ) );
  22:
  23: 	return bFlag;
  24: }

上記の13行目がスタイル読み込み関数です。
第2引数のbFlagは結構重要で、スタイルファイルが無い場合のほか、
カテゴリわけスタイルなのにスタイルの基準となるフィールドが無かった場合など、
データとスタイルに整合が取れていない場合にもfalseを返します。
このチェックを怠ると赤バツが出るので注意しましょう。


縮尺依存表示を適用するには

つまり、マップキャンバスの縮尺がある範囲内にある場合のときのみ表示するように設定することを指します。
以下のようにします。

C++コード
   1:
   2: 	// layerはQgsMapLayerの派生クラス(QgsRasterLayer、QgsVectorLayer等)のオブジェクト
   3: 	// 以下の例は縮尺1/10,000以上のときのみ表示する設定
   4:
   5: 	layer->toggleScaleBasedVisibility( true );
   6: 	layer->setMinimumScale( 0.0f );
   7: 	layer->setMaximumScale( 10000.0f );

凡例情報を取得するには

プラグインの場合は使用しないと思われますが、ベクタレイヤーのレイヤーシンボル情報を取得することができます。
取得されるのは色やポリゴンであれば網掛け設定、ポイントであればシンボルを表すアイコンと文字列です。
カテゴリごとにシンボルを割り当てている場合などは特に重要になります。

これらの情報を取得する関数は、V2レンダラの場合はQgsFeatureRendererV2::legendSymbologyItems()関数です
(旧レンダラの場合はわかりません、、)。

ところがこの関数を使用すると、少なくともWindowsの場合は赤バツが出ます。
(筆者が何か間違っているのだろうとは思います。でないとQGIS本体にも問題があるはず、、、)。
おそらくはDLLのメモリまたぎ問題が原因だろうと思われますが、リザーブしてもやはり落ちました。

で、とりあえずの回避策は、この関数の中身を外に持ってくるという方法です。
以下のようにします。

C++コード
   1:
   2: 	QgsVectorLayer *pLayer;
   3: 	QgsLegendSymbologyList listSymbol;	// 凡例情報のリスト
   4:
   5: 	// ベクタレイヤーの読み込みが成功したら↓
   6:
   7: 	QgsFeatureRendererV2 *pRenderer = pLayer->rendererV2();
   8:
   9: 	// カテゴリ分けシンボルの場合
  10: 	int count = dynamic_cast<QgsCategorizedSymbolRendererV2 *>(pRenderer)->categories().count();
  11:
  12: 	for ( int j = 0; j < count; j++ )
  13: 	{
  14: 		const QgsRendererCategoryV2& cat =
  				dynamic_cast<QgsCategorizedSymbolRendererV2 *>(pRenderer)->categories()[j];
  15: 		QPixmap pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( cat.symbol(), QSize( 16, 16 ) );
  16: 		listSymbol << qMakePair( cat.label(), pix );
  17: 	}

あとは、上記のlistSymbolを使ってツリーウィジェットに追加すればQGISと同様の凡例表示ができます。
もちろんテーブルウィジェットやリストウィジェットに使ってもいいでしょう。


QgsMapCanvas::setLayerSet()関数の引っ掛け問題

この関数の後、QgsMapCanvas::refresh()を呼び出す必要はありません。

setLayerSet()内でrefresh()は呼び出されます。
二重に呼び出すと場合によっては致命的に遅くなるので注意しましょう。


マップキャンバスを画像としてエクスポートするには

現在表示中のキャンバスを画像にエクスポートするにはQgsMapCanvas::seveAsImage()関数を使用します。

mapCanvas->saveAsImage( strFName, NULL );

第2引数はQPixmapオブジェクトへのポインタですが、NULLだと新規作成、
何かセットするとそのPixmapにレンダリングします。

上記のコードでは第3引数を指定していませんが、第3引数はフォーマットで、デフォルトはPNG形式です。

なお、この関数はvoid型で、例外をスローするわけでもなさそうなので、
書き込みエラーを捕まえることができません。

高解像度でエクスポートしたい場合

上記の関数では画像の解像度を設定できないので、高解像度でキャンバスをエクスポートしたい場合には使えません。
高解像度でキャンバスをエクスポートしたい場合にはコンポーザを利用します。

コンポーザを利用するのでタイトルやスケールバーなどを追加することもできるので、
ここではとりあえずマップキャンバスの内容とラベルを一つ書き込んでエクスポートする例を示します。

詳細については、
こちらに移動しました。


ベクタレイヤーの表示条件を設定するには

QgsVectorLayer::setSubsetString()を使用します。
引数には、たとえばid in ("2, 5, 8, 9 13")のようにSQL文のWhere句以下を指定します。

また、現在セットされている条件分を取得するにはQgsVectorLayer::subsetString()を使用します。
デフォルトでは空文字列で、この場合は全てのデータが表示されます。

条件フィルタを設定した場合、条件にかからなかった要素は”無かったこと”扱いされるということに注意してください。
したがって、全要素数やレイヤー範囲も変化します。


キャンバス描画の更新イベントを捕まえるには

キャンバスの更新が完了したタイミングで何かしたい場合は以下のようにします。
まずは、以下のようにシグナル-スロットをつなげます。

	QObject::connect( mapCanvas->mapRenderer(), SIGNAL(drawingProgress(int, int)),
		 this, SLOT(onUpdate(int, int)) );

スロット側で以下のようにします。

void WndMain::onUpdate( int current, int total )
{
	if ( current == total )
	{
		"何か処理"
	}
}

Renderer V1を使用するように変更するには

QGISのバージョン1.8(1.7?)以降は基本的にはRendererV2がデフォルトで使用されるようになっています。
おそらく今後はV1は廃止されるでしょうからV1は使用しないようにするべきですが、やむを得ずV1を使用するようにしたい場合は以下のようにします。

  QgsSymbologyV2Conversion.rendererV2toV1(layer)

筆者の場合

筆者がなぜこれを使用したかですが、QGIS 1.8でPythonプラグインを使用するときにQgsSimpleFillSymbolLayerV2が無いと怒られてしまいました。
他にも、QgsFillSymbolLayerV2の派生クラスは全滅でした。
そこでやむを得ずV1を使用するようにしました。今後は修正を期待したいと思います。


新規ファイルを作成するには

おそらく最近追加されたと思われるQgsVectorFileSaverクラスを使用します。

このクラスを用いてファイルを作成する方法は大きく分けて2種類あります。
一つ目はクラスのオブジェクトを作成する方法、2つ目はこのクラスのスタティック関数であるwriteAsVectorFormat()関数を利用する方法です。
この2つの方法はやや用途が異なります。前者は空の新規ファイルを作成したい場合、後者は現在表示されているレイヤーを別名で保存したい場合に使用します。
ここでは前者について説明します。

ベクタデータを新規に作成する場合は、データが保持する属性フィールドの定義、図形種別、空間参照が必ず必要です。
それに加えてQgsVectorFileSaverのコンストラクタはエンコーディングも要求します(あと、当然ながらファイル名)。
したがって、新規作成コードは以下のようになります。

   1:   fname = QFileDialog.getSaveFileName(self.iface.mapCanvas(), "New layer", ".", "*.shp")
   2:   if fname:
   3:
   4:     # マップキャンバスの空間参照を利用する場合
   5:     srs = QgsCoordinateReferenceSystem()
   6:     srs.createFromId(self.iface.mapCanvas().mapRenderer().destinationCrs().postgisSrid(),
   7:       QgsCoordinateReferenceSystem.PostgisCrsId)
   8:
   9:     # 属性フィールドを定義
  10:     fields = QgsFields()
  11:     fields.append(QgsField("date", QVariant.String))
  12:     fields.append(QgsField("spec", QVariant.String))
  13:     fields.append(QgsField("count", QVariant.Int))
  14:     fields.append(QgsField("method", QVariant.String))
  15:     fields.append(QgsField("bait", QVariant.String))
  16:     fields.append(QgsField("depth", QVariant.Double))
  17:
  18:     # このコンストラクタでファイルが作成される
  19:     writer = QgsVectorFileWriter(fname, "UTF-8", fields, QGis.WKBPoint, srs)
  20:
  21:     # もうオブジェクトを消去してよし
  22:     del writer

QgsVectorFileSaverのコンストラクタは、上記の他にもフォーマット指定やレイヤーオプション、データソースオプション等を引数に指定することができます。
ところが、筆者がテストしたところ、Pythonから呼び出すと最後の引数であるシンボルのエクスポートに引数を指定する事ができませんでした。


現在ロードされているレイヤーを取得するには

QGISにおいてレイヤーを読み込むというのはQgsMapLayerRegistryインスタンスに登録することを指します(多分)。
QgsMapLayerRegistryインスタンスはシングルトンで、QGISアプリケーションを起動すると作成されます。
したがって、現在ロードされているレイヤーの情報はこのインスタンスが持っており、以下のようにして取得します。

   1:   # 「ahoka」という名前のレイヤーを取得する場合
   2:   layers = QgsMapLayerRegistry.instance().mapLayersByName("ahoka")
   3:   if len(layers) != 0:
   4:     # とりあえず一つ目を取得
   5:     layer = layers[0]

レイヤー名は重複する可能性があるので、リストが帰ってきます。
2つ以上見つかってしまった場合はどうするのでしょう。。。


使用しているQGISのバージョンを取得するには

アプリケーションが使用しているQGISのバージョンは、
「2.18.12」といった番号を取得する場合はQGis::QGIS_VERSION
「Las Palmas」といった名称を取得する場合はQGis::QGIS_RELEASE_NAMEを使用します。
型はQString型です。


アーカイブ