waigani's diary

QGISを中心にFOSS4Gをいじくる

QGIS APIで印刷その1

自動印刷

某所からそういう方向に行くなと突っ込まれるのですが、印刷機能関係調べてます。
例えば、ベクトルレイヤー中にある図形それぞれを中心として、指定の縮尺で出力図を一括で作成したい、といった使い方です。

ゴリゴリ書いていくとすると

下記のようなPDF出力のクラスを作ってみました。
図形を1つ渡してあげると、指定のディレクトリに図形の外接長方形を描画範囲としたPDFを出力します。

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import math

class printPDF():

    #共通で使う設定は__init__の中で
    def __init__(self, canvas, dir):
        self.canvas = canvas
        self.render = canvas.mapRenderer()
        self.dir = dir
        
        #最後に設定を戻すので取っておく
        self.originalDpi = self.render.outputDpi()
        self.originalSize = self.render.outputSize()
        
        #出力デバイスの定義 A4 LandscapeのPDFで出力
        self.printer = QPrinter(QPrinter.ScreenResolution)
        self.printer.setResolution(300)
        self.printer.setOutputFormat(QPrinter.PdfFormat)
        self.printer.setPageSize(QPrinter.A4)
        self.printer.setDocName("printer test")
        self.printer.setOrientation(QPrinter.Landscape)
        
        #出力デバイスから、描画領域を取得
        self.originX = self.printer.pageRect().left()
        self.originY = self.printer.pageRect().top()
        self.drawableWidth = self.printer.pageRect().width() - self.originX
        self.drawableHeight = self.printer.pageRect().height() - self.originY
        
        #枠と地図画像の間のスペース*1
        self.canvas.refresh()
        
        #指定図形のidをファイル名としています
        self.printer.setOutputFileName(dir+str(feat.id())+r".pdf")
        
        #出力デバイス(self.printe)を指定して、描画のためのインスタンス作成
        self.paint = QPainter(self.printer)
        
        #枠の描画
        self.paint.setPen(Qt.gray)
        self.paint.drawRect(self.originX, self.originY, self.drawableWidth, self.drawableHeight)
        
        #地図画像の描画部分
        #画像インスタンスを作成しておきます
        self.img = QImage(QSize(self.drawableWidth-self.spacingW*2,self.drawableHeight-self.spacingH*2), QImage.Format_ARGB32)
        self.img.setDotsPerMeterX(self.printer.logicalDpiX()/25.4*1000.0)
        self.img.setDotsPerMeterY(self.printer.logicalDpiY()/25.4*1000.0)
        self.img.fill(0)
        
        #地図画像描画のためのインスタンス作成
        #出力先は画像インスタンス
        self.mapPaint = QPainter()
        self.mapPaint.begin(self.img)
        
        self.render.setOutputSize(QSize(self.drawableWidth-self.spacingW*2, self.drawableHeight-self.spacingH*2), (self.printer.logicalDpiX()+self.printer.logicalDpiY())/2)
        self.render.render(self.mapPaint)
        self.mapPaint.end()
        
        self.paint.drawImage(self.originX+self.spacingW, self.originY+self.spacingH, self.img)
        
        #タイトルの描画
        #四角く白抜きしています
        self.paint.setFont(self.titleFont)
        self.paint.setBrush(QBrush(Qt.white))
        self.paint.drawRect(self.titleRect)
        self.paint.setPen(Qt.black)
        self.paint.drawText(self.titleRect, Qt.AlignCenter, self.title)
        
        self.paint.end()
        
        self.render.setOutputSize(self.originalSize, self.originalDpi)

実行する際は

pythonコンソールから実行できます。
あらかじめ前述のクラスを設定の上で、図形を1つずつ渡してあげます。

iface = qgis.utils.iface
canvas = iface.mapCanvas()
#出力先を C:\pdfディレクトリとしておきます
dir = r"C:\pdf\\"

#初期設定をしておく
pMap = printPDF(canvas, dir)

#各図形の外接長方形を出力範囲としてPDF作成
feat = QgsFeature()
layer = iface.activeLayer()
for i in range(layer.featureCount()):
    layer.featureAtId(i,feat)
    pMap.drawMap(feat)

実行結果

枠、タイトル、指定図形にフォーカスしたQGISで表示されている地図、が描画された単純なPDFが出力されます。

もう少し賢くしたい

低レベルなクラスを使ってゴリゴリ書いていけばどうにかなりそうには思えるのですが、あまり賢く無いので他の方法を探し中です。
コンポーザーマネージャーのGUI見ていると、ちゃんと便利なクラスが用意されていそうな気がしますし。

QGIS API DocumentationのMapComposerの項を試すべきですね。

ここまで書いていて、PyQGI DocumentationにOutput Using Map composerという項があるのに気づきました。書きかけのようですが、composerの各種アイテムが簡単に配置可能かやってみようと思います。

*1:1mm=0.03937 inch)*300dpi = 約12dot) self.spacingW = 12 self.spacingH = 12 #タイトル関係の設定値 self.title = u"印刷テスト" self.titleFontSize = 24 self.titleFontFamily = "Arial" self.titleFont = QFont(self.titleFontFamily, self.titleFontSize) self.titleMetrics = QFontMetrics(self.titleFont, self.printer) self.titleHeight = self.titleMetrics.height() self.titleWidth = self.titleMetrics.width(self.title) self.titleRect = QRect(self.originX, self.originY, self.titleWidth, self.titleHeight) #指定図形毎の外接矩形に合わせて描画範囲を変える部分と、実際の描画はdrawMap()の中で def drawMap(self, feat): #指定図形の外接矩形にcanvasをセット #refreshはお好みで self.canvas.setExtent(feat.geometry().boundingBox(