% vim: set textwidth=78 autoindent:

\section{Erstellung von PyQGIS Anwendungen}

% when the revision of a section has been finalized,
% comment out the following line:
% \updatedisclaimer

Eines der Ziele von QGIS ist es nicht nur eine Anwendung bereitzustellen, sondern
eine Sammlung von Bibliotheken, die zur Entwicklung neuer Anwendungen benutzt werden
können. Dieses Ziel wurde durch die Umgestaltung der Bibliotheken nach dem Release 0.8
realisiert. Seit dem Release 0.9 ist die Entwicklung unabhängiger Anwendungen durch die
Verwendung von C++ oder Python möglich. Wir empfehlen QGIS 1.0.0 oder höher als Basis
für Python Anwendungen, da seit dieser Version eine stabile, konstistente API
vorliegt.

In diesem Kapitel werden wir einen kurzer Blick auf das Verfahren zur Erstellung
unabhängiger Python Anwendungen werfen. Der QGIS Blog enthält einige Beispiele
für die Erstellung von PyQGIS\footnote{Eine Anwendung unter Verwendung von Python und QGIS Referenzen}
Anwendungen. Wir werden eines dieser Beispiele benutzen um einen
Einblick in die Entwicklung von Anwendungen zu kriegen.

Unsere Anwendung soll folgende Punkte erfüllen:

\begin{itemize}
\item Einen Vektor Layer laden
\item Pan
\item rein und raus Zoomen
\item Auf die gesamte Ausdehnung des Layers zoomen
\item Bestimmen eigener Farben, wenn der Layer geladen ist
\end{itemize}

Das ist sind ziemlich geringe Anforderungen. Wir starten mit der Gestaltung des
GUI mit der Hilfe von Qt Designer.

\subsection{Gestaltung des GUI}

Da wir eine kleine Anwendung erstellen, werden wir ebenso mit dem GUI verfahren.
Mit dem Qt Designer erstellen wir ein einfaches Hauptfenster ohne Menü oder Toolbars.
Wir erhalten eine freie Fläche auf der wir arbeiten können. Um das Hauptfenster
zu erstellen:

\begin{enumerate}
\item Erstelle ein Verzeichnis für die Entwicklung der Anwendung und wechsel in dieses
\item Starte den Qt Designer
\item Das \qtdialog{Neues Formular} Fenster sollte erscheinen. Sollte dies nicht der Fall sein,
wähle \qtdropmenuopt{Neu...} unter \qtmainmenuopt{Datei} in der Menüleiste.
\item Wähle \qtdropmenuopt{Main Window} aus der \qtdropmenuopt{templates/forms} Liste
\item Klicke \qtdropmenuopt{Neu von Vorlage}
\item Passe die Größe des neuen Fensters entsprechend an
\item Finde den Eintrag \qtdropmenuopt{Frame} in der Liste
(unter \qtdropmenuopt{Containers}) und ziehe diesen in das eben erstellte Fenster
\item Klicke ausserhalb des Frames um das Hauptfenster zu selektieren
\item Klicke auf das \qtdropmenuopt{Objekte tabellarisch anordnen} Tool im
Menü \mainmenuopt{Formular}. Die Größe des Frames wird nun an das Hauptfenster angepasst und füllt dieses komplett aus.
\item Speicher dies im Menü \mainmenuopt{Datei} als \usertext{mainwindow.ui}
\item Qt Designer \qtdropmenuopt{Beenden}
\end{enumerate}

Kompiliere nun das erstelle Formular mit PyQt:

\begin{verbatim}
   pyuic4 -o mainwindow_ui.py mainwindow.ui
\end{verbatim}

Dieser Befehl erstellt den Python Quellcode für das Hauptfenster GUI.
Als nächstes müssen wir den Anwendungscode erstellen, um die freie Fläche des
Hauptfensters mit sinnvollen Tools zu füllen.

\subsection{Erstellung des Hauptfensters}

Jetzt sind wir bereit, die Klasse \classname{MainWindow}, die die wirkliche Arbeit
verrichtet, zu schreiben.
Da einige Zeilen Quellcode nötig sind, werden wir die Klasse in Einzelteile betrachten,
beginnend mit dem Import Bereich und den Umgebungseinstellungen:

\begin{verbatim}
1 # Basiert auf:
2 #   Original C++ Tutorial 2 von Tim Sutton
3 #   Überführung zu Python von Martin Dobias
4 #   mit Verbesserungen von Gary Sherman für die FOSS4G2007
5 # Lizensiert unter den Bedingungen der GNU GPL 2
6
7 from PyQt4.QtCore import *
8 from PyQt4.QtGui import *
9 from qgis.core import *
10 from qgis.gui import *
11 import sys
12 import os
13 # Import unseres GUIs
14 from mainwindow_ui import Ui_MainWindow
15
16 # Umgebungsvariable QGISHOME muss auf das Installationsverzeichnis
17 # 1.0 gesetzt werden bevor die Anwendung gestartet wird
18 qgis_prefix = os.getenv("QGISHOME")
\end{verbatim}

Einiges hiervon sollte uns von unserem Plugin bekannt vorkommen, besonders
die PyQt4 und QGIS Importe. Speziell zu erwähnen ist der Import unseres GUIs
in Zeile 14 und der Import der CORE Bibliothek in Zeile 9.

Unsere Anwendung muss wissen, wo sie die QGIS Installation finden kann.
Aus diesem Grund setzen wir die Umgebungvariable \usertext{QGISHOME} auf das
Installationverzeichnis von QGIS 1.x. In Zeile 20 speichern wir diesen Wert
für den späteren Gebrauch.

Als nächstes müssen wir die Klasse \classname{MainWindow} erstellen, welche
die gesamte Logik unserer Anwendung enthält.

\begin{verbatim}
21 class MainWindow(QMainWindow, Ui_MainWindow):
22
23   def __init__(self):
24     QMainWindow.__init__(self)
25
26     # Notwendig für Qt4 zur Initialisierung der Benutzeroberfläche
27     self.setupUi(self)
28
29     # Setzt den Titel der Anwendung
30     self.setWindowTitle("QGIS Demo App")
31
32     # Erstellt den Map Canvas
33     self.canvas = QgsMapCanvas()
34     # Setzt die Hintergrundfarbe zu einem hellen blau
35     self.canvas.setCanvasColor(QColor(200,200,255))
36     self.canvas.enableAntiAliasing(True)
37     self.canvas.useQImageToRender(False)
38     self.canvas.show()
39
40     # Setzt unsere Dinge in das Hauptfenster unter Verwendung
41     # eines vertikalen box layouts
42     self.layout = QVBoxLayout(self.frame)
43     self.layout.addWidget(self.canvas)
44
45     # Erstellt die Aktionen für unsere Tools und verbindet diese mit den
46     # zugehörigen Methoden
47     self.actionAddLayer = QAction(QIcon("(qgis_prefix + \
       "/share/qgis/themes/classic/mActionAddLayer.png"),
48     \
49         "Add Layer", self.frame)
50     self.connect(self.actionAddLayer, SIGNAL("activated()"), self.addLayer)
51     self.actionZoomIn = QAction(QIcon("(qgis_prefix + \
       "/share/qgis/themes/classic/mActionZoomIn.png"), \
52         "Zoom In", self.frame)
53     self.connect(self.actionZoomIn, SIGNAL("activated()"), self.zoomIn)
54     self.actionZoomOut = QAction(QIcon("(qgis_prefix + \
       "/share/qgis/themes/classic/mActionZoomOut.png"), \
55         "Zoom Out", self.frame)
56     self.connect(self.actionZoomOut, SIGNAL("activated()"), self.zoomOut)
57     self.actionPan = QAction(QIcon("(qgis_prefix + \
       "/share/qgis/themes/classic/mActionPan.png"), \
58         "Pan", self.frame)
59     self.connect(self.actionPan, SIGNAL("activated()"), self.pan)
60     self.actionZoomFull = QAction(QIcon("(qgis_prefix + \
       "/share/qgis/themes/classic/mActionZoomFullExtent.png"), \
61     "Zoom Full Extent", self.frame)
62     self.connect(self.actionZoomFull, SIGNAL("activated()"),
63     self.zoomFull)
64
65     # Erstellt eine Toolbar
66     self.toolbar = self.addToolBar("Map")
67     # Fügt die Aktionen der Toolbar hinzu
68     self.toolbar.addAction(self.actionAddLayer)
69     self.toolbar.addAction(self.actionZoomIn)
70     self.toolbar.addAction(self.actionZoomOut);
71     self.toolbar.addAction(self.actionPan);
72     self.toolbar.addAction(self.actionZoomFull);
73
74     # Erstellt die Kartentools
75     self.toolPan = QgsMapToolPan(self.canvas)
76     self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
77     self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
\end{verbatim}

In den Zeilen 21 bis 27 sind die grundlegenden Deklarationen und Initialisierungen
der Klasse \classname{MainWindow} und der Aufbau der Benutzeroberfläche durch die
Methode \method{setupUi}. Dies ist für alle Anwendungen erforderlich.

Als nächstes setzen wir etwas interessanteres als \usertext{MainWindow} in den
Titel der Anwendung (Zeile 30). Sobald dies erledigt ist, können wir die
Benutzeroberfläche fertigstellen. Im Designer haben wir diese sehr spärlich
erstellt---nur ein Hauptfenster und ein Rahmen. Man hätte mit dem Designer ein
Menü und eine Toolbar hinzufügen können, aber wir machen das mit Python.

In den Zeilen 33 bis 38 erstellen wir den Map Canvas, setzen die Hintergrundfarbe
auf ein helles blau und aktivieren Antialiasing. Ebenso sagen wir, dass
\classname{QImage} nicht zum rendern benutzt werden soll (vertraut mir hierbei)
und lassen den Canvas sichtbar werden durch die Methode \method{show}.

Als nächstes definieren ein vertikales Box Layout innerhalb des Frames
und fügen den Map Canvas hinzu (Zeile 43). 

In den Zeilen 48 bis 63 werden die Aktionen und Verbindungen für die Tools
aus unserer Toolbar gesetzt. Für jedes Tool erstellen wir eine \classname{QAction}
mit dem Symbol, welches wir im QGIS classic theme definiert haben. Dann verbinden
wir das Signal \usertext{activated} des Tools mit der Methode unserer Klasse,
die die Aktion weiter verarbeitet. Dies ist vergleichbar mit dem Plugin Beispiel.

Sobald wir die Aktionen und Verbindungen erstellt haben, müssen wir diese zu der
Toolbar hinzufügen. In den Zeilen 66 bis 72 erstellen wir die Toolbar und fügen
ihr jedes einzelne Tool hinzu. Schließlich erstellen wir die drei Kartentools für die 
Anwendung (Zeilen 75 bis 77). Wir werden die Kartentools gleich benutzen, wenn wir die Methoden 
definieren, die unsere Anwendung funktionsfähig machen. Sehen wir uns nun die
Methoden für die Kartentools an.

\begin{verbatim}
78   # Definiert das Kartentool zum hinein zoomen 
79   def zoomIn(self):
80     self.canvas.setMapTool(self.toolZoomIn)
81
82   # Definiert das Kartentool zum heraus zoomen 
83   def zoomOut(self):
84     self.canvas.setMapTool(self.toolZoomOut)
85
86   # Definiert das Kartentool zum Panning
87   def pan(self):
88    self.canvas.setMapTool(self.toolPan)
89
90   # Zoomt zum vollen Kartenausschnitt
91   def zoomFull(self):
92     self.canvas.zoomFullExtent()
\end{verbatim}

Für jedes Kartentool brauchen wir eine Methode, die mit der von uns erstellten 
Verbindung übereinstimmt. In den Zeilen 79 bis 88 erstellen wir eine Methode für
jedes der drei Tools, die mit der Karte interagieren. Durch Anklicken in der Toolbar
wir das Tool aktiviert und die zugehörige Methode aufgerufen, die wiederum
dem Map Canvas über das aktuelle Tool informiert. Das aktive Tool reagiert auf 
die Klicks der Maus in dem Canvas.

Das \usertext{zoom to full extent} Tool ist kein Kartentool---es erfüllt seine
Aufgabe ohne ein Klick auf die Karte. Bei Aktivierung dieses Tools wird die 
Methode \method{zoomFullExtent} des Map Canvas aufgerufen (Zeile 92). Dies
vervollständigt die Implementierung aller Tools ausser einem---dem 
\usertext{Add Layer} Tool. %FIXME
Dieses schauen wir uns als nächstes an:

\begin{verbatim}
93   # Fügt ein OGR Layer zur Karte hinzu
94   def addLayer(self):
95     file = QFileDialog.getOpenFileName(self, "Open Shapefile", ".", "Shapefiles
96     (*.shp)")
97     fileInfo = QFileInfo(file)
98
99     # Fügt den Layer hinzu
100     layer = QgsVectorLayer(file, fileInfo.fileName(), "ogr")
101
102    if not layer.isValid():
103      return
104
105    # Ändert die Farbe des Layers auf grau
106    symbols = layer.renderer().symbols()
107    symbol = symbols[0]
108    symbol.setFillColor(QColor.fromRgb(192,192,192))
109
110    # Fügt Layer zur Registry hinzu
111    QgsMapLayerRegistry.instance().addMapLayer(layer);
112
113    # Setzt den aktuellen Extent auf den Extent unseres Layers
114    self.canvas.setExtent(layer.extent())
115
116    # Erstellt den Map Canvas Layer Set
117    cl = QgsMapCanvasLayer(layer)
118    layers = [cl]
119    self.canvas.setLayerSet(layers)
\end{verbatim}

In der Methode \method{addLayer} verwenden wir einen \classname{QFileDialog}
um den Namen des zu ladenden Shapefiles zu erhalten (Zeile 96).
Zu beachten ist, dass wir ein ``Filter'' definieren, so dass der Dialog uns
nur Dateien vom Typ \filename{.shp} anzeigt.

In Zeile 97 erstellen wir uns als nächstes ein \classname{QFileInfo} Objekt
aus dem Pfad des Shapefiles. Jetzt ist der Layer fertig für die Erstellung 
(Zeile 100). Der Layer erhält nach der Erstellung den Namen der Datei, welcher
über das \classname{QFileInfo} Objekt erfragt werden kann. Um sicher zu gehen, 
dass der Layer gültig ist und ohne Probleme geladen werden kann, wird dieser 
in Zeile 102 überprüft. Wenn die Prüfung fehlschlägt, wird die Weiterverarbeitung
nicht durchgeführt und der Layer nicht zum Map Canvas hinzugefügt.

Normalerweise werden Layer mit einer zufälligen Farbe hinzugefügt. Wir wollen 
nun die Farbe des Layers anpassen um die Anzeige angenehmer zu machen. Zudem
wissen wir, dass wir den \filename{world\_borders} Layer zur Karte hinzufügen
werden und dass dies gut aussehen wird auf unserem blauen Hintergrund. Um die 
Farbe zu ändern brauchen wir das zum Rendern verwendete Symbol und verwenden
dieses, um eine neue Füllfarbe zu setzen (Zeilen 106 bis 108).

Alles was nun noch erledigt werden muss, ist den Layer zu der Registry 
hinzuzufügen und einige andere haushälterischen Dinge zu erledigen (Zeilen
111 bis 119). Dies ist das Standardvorgehen um einen Layer hinzuzufügen und 
das Ergebnis ist der \filename{world\_borders} Layer auf einem hell blauen
Hintergrund. Das Einzige was nicht gemacht werden muss, ist das Ändern der
Ausdehnung auf den Layer, wenn mehr als ein Layer hinzugefügt werden soll.

Das ist das Herz der Anwendung und vervollständigt die Klasse \classname{MainWindow}.


\subsection{Fertigstellung}

Der restliche Code, der unten aufgeführt ist, erstellt das Objekt
\object{QgsApplication}, setzt den Pfad zur QGIS Installation, setzt die 
Methode \method{main} und startet dann die Anwendung. Erwähnenswert ist noch,
dass das Anwendungsfenster in die obere linke Ecke des Bildschirms verschoben wird.
Wir könnten die Qt API dazu nutzen das Fenster wieder auf dem Bildschirm zu
zentrieren. 

\begin{verbatim}
120 def main(argv):
121   # Erstelle die Qt Anwendung
122   app = QApplication(argv)
123
124   # Initialisiere QGIS Bibliotheken
125   QgsApplication.setPrefixPath(qgis_prefix, True)
126   QgsApplication.initQgis()
127
128   # Erstelle Hauptfenster
129   wnd = MainWindow()
130   # Bewege die Anwendung nach oben links
131   wnd.move(100,100)
132   wnd.show()
133
134   # Start!
135   retval = app.exec_()
136
137   # Beenden
138   QgsApplication.exitQgis()
139   sys.exit(retval)
140
141
142 if __name__ == "__main__":
143   main(sys.argv)
\end{verbatim}

\subsection{Starten der Anwendung}

Jetzt können wir die Anwendungen starten und sehen was passiert. Selbstverständlich
wie viele andere Entwickler, testest du laufend während der Entwicklung.
Bevor wir die Anwendung ausprobieren können, müssen wir einige Umgebungsvariablen
setzen.

\nix{}\osx{}
\begin{verbatim}
export LD_LIBRARY_PATH=$HOME/qgis/lib%$
export PYTHONPATH=$HOME/qgis/share/qgis/python
export QGISHOME=$HOME/qgis%$
\end{verbatim}

\win{}
\begin{verbatim}
set PATH=C:\qgis;%PATH%
set PYTHONPATH=C:\qgis\python
set QGISHOME=C:\qgis
\end{verbatim}

Wir gehen davon aus, dass
\begin{itemize}
\item\nix{}\osx{}QGIS unter home installiert
in 
\filename{qgis}.
\item\win{}QGIS installiert ist in \filename{C:\textbackslash qgis}.
\end{itemize}

Wenn die Anwendung startet, sollte es folgendermaßen aussehen:

%%FIXME
%\begin{figure}[ht]
%\begin{center}
%  \caption{Starten der neuen Demo Anwendung}\label{fig:demo_app_startup}%\smallskip
%  \includegraphics[scale=0.8]{cpp1_application}
%\end{center}
%\end{figure}

Um den Layer \filename{world\_borders} hinzuzufügen, klick auf 
das \usertext{Add Layer} Tool und navigiere zum Datenverzeichnis.
Wähle das Shapefile aus und Klicke auf \button{Open} um es zur
Karte hinzuzufügen. Unsere angepasste Füllfarbe ist zum Einsatz
gekommen und das Resultat sieht so aus:

%% FIXME
%\begin{figure}[ht]
%\begin{center}
%  \caption{Hinzufügen eines Layers zur Demo Anwendung}\label{fig:demo_app_done}%\smallskip
%  \includegraphics[scale=0.8]{cpp2_application}
%\end{center}
%\end{figure}

Die Erstellung von PyQGIS Anwendungen ist sehr einfach. In weniger als 150 Zeilen
Quellcode haben wir eine Anwendung die Shapefiles laden kann und eine navigierbare
Karte zur Verfügung stellt. Wenn du ein wenig mit der Karte experimentierst, wirst
du feststellen, dass manche der eingebauten Funktionen des Canvas ebenfalls
funktionieren, einschließlich des Mausrad-Scrollens und dem Panning indem man die
\keystroke{Space} Taste gedrückt hält und die Maus bewegt.

Einige anspruchsvolle Anwendungen wurden mit PyQGIS entwickelt und einige weitere
sind in Arbeit. Dies ist ziemlich eindrucksvoll in Anbetracht der Tatsache, dass
diese Entwicklungen erfolgt sind bevor das offizielle Release von QGIS 1.0. 
erschienen ist.

\begin{Tip}\caption{\textsc{Dokumentation für PyQGIS}}
\qgistip{Um ein Plugin oder eine PyQGIS-Anwendung zu schreiben, muss man sich
sowohl mit der QGIS API Doku (\url{http://doc.qgis.org}) als auch dem PyQt
Python Bindings Reference Guide befassen
(\url{http://www.riverbankcomputing.com/Docs/PyQt4/pyqt4ref.html}). 
Diese liefern Informationen über Klassen und Methoden, die notwendig sind, um
PyQGIS-Anwendung Leben einzuhauchen.
}
\end{Tip}

