8

Is it possible to integrate external Python editors (such as KDevelop) with QGIS, so that it would be possible to run functions in qgis.core, qgis.utils etc. outside of the QGIS Python Console?

Following the guidelines on the QGIS website (http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/intro.html), I tried this, but it just returns 1 and nothing else:

import sys
sys.path.append('/usr/share/qgis/python')
import qgis.core
import qgis.utils

app = qgis.core.QgsApplication([], True)
qgis.core.QgsApplication.initQgis()
qgis.utils.iface.addVectorLayer("testing.shp", "anewlayer", "ogr") 
aLayer = qgis.utils.iface.activeLayer()
print aLayer.name()

Such as:

$ LD_LIBRARY_PATH=/usr/lib64/qgis/ python qgis-test.py && echo "OK" || echo "Died"
Died

I am running openSUSE Tumbleweed, 64-bit.

GreatEmerald
  • 327
  • 1
  • 8

1 Answers1

8

I use following intro for stand-alone applications:

# the_app.py
import os
import sys

from qgis.core import *
from PyQt4.QtGui import *     

def main():
    QgsApplication.setPrefixPath(os.environ['QGIS_PREFIX'], True)
    QgsApplication.initQgis()

    # app specific code
    ...

    QgsApplication.exitQgis()
    sys.exit(result)

if __name__ == '__main__':
    main()

When the app do not need a GUI (eg do some geoprocessing), replace the ellipsis with something like that:

# app specific code (console)
print 'starting test'
layer1 = QgsVectorLayer('LineString', 'anewlayer', 'memory')
print layer.isValid()
QgsMapLayerRegistry.instance().addMapLayer(layer1, False)
print 'layer added to map layer registry'
aLayer = QgsMapLayerRegistry.instance().mapLayersByName('anewlayer')[0]
print aLayer.name()

To work with one or more layers you do not have to add them to map layer registry, just reference them by their variable name (here layer1).

When you use a GUI, for example a mapper window, this would be a python class derived from QMainWindow, typically designed with QtDesigner. The ellipsis have to be replaced with code as in the next example:

# app specific code (GUI)
app = QApplication(sys.argv)

# create gui window
window = Main()
window.show() 

result = app.exec_()
app.deleteLater()

To test this approach without actually having a GUI try this modified console version:

# app specific code (GUI ready)
app = QApplication(sys.argv)

layer1 = QgsVectorLayer('LineString', 'anewlayer', 'memory')
QgsMapLayerRegistry.instance().addMapLayer(layer1, False)
aLayer = QgsMapLayerRegistry.instance().mapLayersByName('anewlayer')[0]
print aLayer.name()

app.qApp.quit()

result = app.exec_()
app.deleteLater()

It is almost the same as the console version (as there are actually no graphical objects), but regard the quit() which stops the application.

To start such app on Linux:

#!/bin/sh
export PYTHONPATH="/usr/share/qgis/python"
export LD_LIBRARY_PATH="/usr/lib64/qgis"
export QGIS_PREFIX="/usr"
python the_app.py

And on a Windows machine:

SET OSGEO4W_ROOT=D:\OSGeo4W64
SET QGISNAME=qgis
SET QGIS=%OSGEO4W_ROOT%\apps\%QGISNAME%
SET QGIS_PREFIX=%QGIS%

CALL %OSGEO4W_ROOT%\bin\o4w_env.bat

SET PATH=%PATH%;%QGIS%\bin
SET PYTHONPATH=%QGIS%\python;%PYTHONPATH%

python the_app.py

A very basic GUI may look like this:

# hand-made window with simple toolbar
class Ui_DemoWindow(object):
    def setupUi(self, window):
        window.setWindowTitle('Demo')

        self.centralWidget = QWidget(window)
        self.centralWidget.setFixedSize(640, 480)
        window.setCentralWidget(self.centralWidget)
        window.move(0, 0)

        self.layout = QVBoxLayout()    
        self.layout.setContentsMargins(0, 0, 0, 0)    
        self.centralWidget.setLayout(self.layout)

        self.toolBar = QToolBar(window)
        window.addToolBar(Qt.TopToolBarArea, self.toolBar)

        # quit action
        self.actionQuit = QAction('Quit', window)
        self.actionQuit.setShortcut(QKeySequence.Quit)

        # do something, here call function to create some geometries
        self.actionCreateGeom = QAction('Geometry', window)

        # link action to GUI elements
        self.toolBar.addAction(self.actionQuit)
        self.toolBar.addSeparator()
        self.toolBar.addAction(self.actionCreateGeom)


class Main(QMainWindow, Ui_DemoWindow):

    def __init__(self):    
        QMainWindow.__init__(self)
        self.setupUi(self)

        self.canvas = QgsMapCanvas()
        self.layout.addWidget(self.canvas)

        self.connect(self.actionQuit, SIGNAL('triggered()'), self.quit) 
        self.connect(self.actionCreateGeom, SIGNAL('triggered()'), self.some_geoms) 

    # quick and dirty: create layer with some features, add layer
    # to map canvas, and zoom canvas to full view
    def some_geoms(self):
        layer = QgsVectorLayer('LineString', 'anewlayer', 'memory')
        # add some features
        prov = layer.dataProvider()
        feats = []
        feat = QgsFeature()
        feat.setGeometry(QgsGeometry().fromPolyline([QgsPoint(-1,1), QgsPoint(1, -1)]))
        feats.append(feat)
        prov.addFeatures(feats)
        layer.updateExtents()

        QgsMapLayerRegistry.instance().addMapLayer(layer)
        self.canvas.setLayerSet([QgsMapCanvasLayer(layer)])
        self.canvas.zoomToFullExtent()    

    def quit(self):
        qApp.quit() 

How its look after pressing button Geometry:

demo

Starting with this simple example you may wish to implement the legend widget, property dialogs, layer dialogs, selection and editing behaviour, and so on.

Detlev
  • 4,608
  • 19
  • 26
  • Using your code as-is, it doesn't crash any more, but it does result in the script simply idling there forever. It cannot even be stopped by sending SIGINT, it has to be stopped with SIGKILL. Am I still missing something? – GreatEmerald Jan 20 '16 at 08:52
  • You may run a stand-alone app WITH or WITHOUT a GUI. When you have a GUI, then the GUI gets the control until it ends with a quit(). Initialization of such GUI class would replace the #app specific code in my example. If you do not have a GUI then you cannot use GUI related methods, like activeLayer(). I edit my answer to include an example. – Detlev Jan 20 '16 at 10:22
  • I tried that (copy-pasted the first part and inserted the GUI block instead of the eliipsis), and got this error: Traceback (most recent call last): File "test.py", line 25, in <module> main() File "test.py", line 15, in main window = Main() NameError: global name 'Main' is not defined – GreatEmerald Jan 22 '16 at 09:36
  • @GreatEmerald Main() should be just an example. If you have build a GUI with QtDesigner or from scratch then insert this class name instead. Since I dont know your exact app environment I showed the principles – Detlev Jan 22 '16 at 11:56
  • Oh, OK. So I'm looking at the wrong example, then. Using the "GUI Ready" example, it seems to work (when I remove qApp from app.qApp.quit(), since that doesn't exist) as I get the right output, but it doesn't seem to connect or start a visible QGIS instance, so I don't see the layer in it. Also, once again it just gets stuck and I need to kill it, it doesn't actually quit. – GreatEmerald Jan 22 '16 at 15:17
  • @GreatEmerald No, this script does not start a visible QGIS instance, since the visible part is the GUI. If you want to show the layer, you have to create a window, insert a layout, and add a mapcanvas. But this would be another question, since you initially asked for running QGIS functions. If you like show me your code so I can have a look together with a description what you want to do. You find my email address on my github profile detlevn. – Detlev Jan 22 '16 at 17:01
  • It's in the question: I want not only to be able to call qgis.core functions, but also qgis.utils.iface functions, like qgis.utils.iface.addVectorLayer("testing.shp", "anewlayer", "ogr"). – GreatEmerald Jan 25 '16 at 16:03
  • @GreatEmerald As in http://gis.stackexchange.com/questions/29580/how-to-run-a-simple-python-script-for-qgis-from-outside-e-g-sublime-text explaned iface is only available in QGIS itself. For a standalone application you have to build all interface stuff from scratch using the API (Qgis and Qt). To help you starting I extend the answer to include a very basic Main() class to show some of this API calls. – Detlev Jan 25 '16 at 17:46
  • I followed the above step but when I run the python file containing pyqgis, it works in pycharm but fails in terminal with ImportError: No module named qgis.core error. Could someone please help? – wondim Jan 18 '19 at 21:42
  • 1
    @wondim Please have a look at https://gis.stackexchange.com/questions/40375/fixing-importerror-no-module-named-qgis-core – Detlev Jan 19 '19 at 06:17
  • @Detlev using /usr/bin/python infront of running the file helped! Thank you! – wondim Jan 22 '19 at 18:38