2

I created a QGIS standalone script (custom Application) that enables me to automatically export Atlas as images.

The problem is that in my QGIS project, I'm using a Mask plugin and the generated images don't take into consideration this plugin (even if I install it on /usr/share/qgis/python/plugins) when executing my script in a standalone way whereas when I manually export Atlas as images using QGIS desktop and the same project, the plugin is activated and the mask is done correctly in the generated images.

I also tried to use the API sys.path.append('/usr/share/qgis/python/plugins') just after the qgs.initQgis() and also QgsApplication.setPrefixPath("/usr/lib/qgis", True) (tested also with /usr/share/qgis andn /usr folder) before the QgsApplication([], True) without more success.

Is there a specific API to use when executing standalone scripts so that QGIS engine enables layers plugins?

I also created an issue on the Mask plugin project in case.

1 Answer 1

2

According to a reply to "Accessing QGIS plugins in Python?" question, one have to look at the source code of the plugin and understand how it is initialized and try to reproduce it in the python code.

Another reply to "how to load the QGIS openlayers_plugin in standalone python script (not the QGIS embedded python console)" confirms this:

This is never, well maybe with some hacks, going to work well. Plugins normally use the QgisInterface object which gives access to the QGIS interface and methods. You don't have this object in your standalone script. Most plugins, if not all, are not designed to run outside of QGIS like this.

This replies confirms that this QgisInterface is created via a C++ embedded python interpreter and I found it was created directly from the C++ code.

So inspired by an other reply, I tried to create a fake QgisInterface, then init the plugin (thanks to this hint) with it and looks at its initGui(self) method to only keep the non UI related parts required to make the plugin work.

Regarding the Mask plugin, here is the full standalone python code to generate images from an Atlas composer:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from qgis.core import QgsApplication, QgsProject, QgsComposition, QgsMapLayerRegistry
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge
from PyQt4.QtCore import QFile, QFileInfo, QByteArray, QTextStream, QSettings
from PyQt4.QtXml import QDomDocument
import os
import sys

sys.path.append('/usr/share/qgis/python/plugins') # need to add the path to the QGIS plugin, else the class of the plugin will not be found
from mask.aeag_mask import aeag_mask

projectPath = sys.argv[1]
renderingPath = sys.argv[2]

class FakeInterface:
    def __init__(self, canvas):
        self.canvas = canvas
    def activeLayer(self): # used by multiple places in https://github.com/aeag/mask/blob/1.5/mask/aeag_mask.py
        return None
    def mapCanvas(self): # used by multiple places in https://github.com/aeag/mask/blob/1.5/mask/aeag_mask.py
        return self.canvas
    def activeComposers(self): # used by https://github.com/aeag/mask/blob/1.5/mask/aeag_mask.py#L403
        pass

class FakeComposer:
    def __init__(self, composition):
        self.compo = composition
    def composition(self):
        return self.compo

class FakeQAction:
    def __init__(self):
        pass
    def setEnabled(self, value):
        pass

def printAtlas(projectPath, renderingPath):
    # Init Mask plugin
    canvas = QgsMapCanvas()
    fakeIface = FakeInterface(canvas)
    QSettings().setValue("locale/userLocale", "C") # Because Mask plugin uses QSettings().value("locale/userLocale")[0:2] which normally returns null in this script environment
    mask_plugin = aeag_mask(fakeIface) # Simulate the init call of the plugin from QGIS
    mask_plugin.act_aeag_mask = FakeQAction() # Because on_project_open uses self.act_aeag_mask.setEnabled(True)

    # Inspired by [Save Print/Map QGIS composer view as PNG/PDF using Python (without changing anything in visible layout)?](https://gis.stackexchange.com/a/164196/73088), [How to export a configurated Atlas with a python script / command line?](https://gis.stackexchange.com/q/272839/73088), [QGIS: Automatisation de la génération d'un Atlas avec script python](https://georezo.net/forum/viewtopic.php?pid=284842), [Search for "[qgis] standalone script" on gis stackexchange](https://gis.stackexchange.com/search?page=2&tab=Relevance&q=%5bqgis%5d%20standalone%20script), [How to run a simple python script for QGIS from outside (e.g. Sublime Text)?](https://gis.stackexchange.com/a/29597/73088), [Using PyQGIS in custom applications](https://docs.qgis.org/2.18/en/docs/pyqgis_developer_cookbook/intro.html#using-pyqgis-in-custom-applications), [Generate a QGIS map PDF using python](https://gist.github.com/timlinux/486793ad61db4c1dec9d), [How to create a QGIS PDF report with a few lines of python](http://kartoza.com/en/blog/how-to-create-a-qgis-pdf-report-with-a-few-lines-of-python/), [QGIS Server Plugin Filters: Add a new request to print a specific atlas feature](https://github.com/3liz/qgis-atlasprint/blob/master/filters/atlasprintFilter.py), [QGIS export “save as image” automate with python?](https://gis.stackexchange.com/a/213065/73088)
    #Getting project as Qfile and the first composer of the project as a QDomElement from the .qgs
    projectAsFile = QFile(projectPath)
    projectAsDocument = QDomDocument()
    projectAsDocument.setContent(projectAsFile)
    composerAsNode = projectAsDocument.elementsByTagName("Composer").at(0)
    # Only way to convert a QDomNode to a QDomDocument root, inspired by https://gis.stackexchange.com/a/164196/73088 & [Convert QDomElement to QDomDocument and vs](https://stackoverflow.com/q/18868993/535203) & read the documentation http://doc.qt.io/archives/qt-4.8/qtextstream.html & http://doc.qt.io/archives/qt-4.8/qdomnode.html .
    # Using a QByteArray because QString pointer can't be passed in python and QString is not available by default in QGIS python scripts [QGIS PyQt4 missing QString class](https://stackoverflow.com/q/28632169/535203)
    composerAsString = QByteArray()
    composerAsNode.save(QTextStream(composerAsString), 2)
    composerAsDocument = QDomDocument()
    composerAsDocument.setContent(composerAsString)

    #Now that we got all we can open our project
    QgsProject.instance().read(QFileInfo(projectAsFile))
    # Loading mask
    mask_plugin.registry = QgsMapLayerRegistry.instance()
    mask_plugin.on_project_open()

    bridge = QgsLayerTreeMapCanvasBridge(
        QgsProject.instance().layerTreeRoot(), canvas)
    bridge.setCanvasLayers()

    #Lets try load that composer template we just extracted
    composition = QgsComposition(canvas.mapSettings())
    composition.loadFromTemplate(composerAsDocument, {})
    # Connect Mask plugin
    mask_plugin.on_composer_added(FakeComposer(composition))

    atlas = composition.atlasComposition()
    composition.setAtlasMode(QgsComposition.ExportAtlas)
    print 'Found %d features to render.' % atlas.numFeatures()

    atlas.beginRender()
    for i in range(0, atlas.numFeatures()):
        print 'Rendering feature %d' % i
        atlas.prepareForFeature(i)
        filename = atlas.currentFilename()
        featureRenderingBasePath = os.path.join(renderingPath, "dep_%s" % filename)
        print '`-> %s.jpg' % featureRenderingBasePath
        #composition.exportAsPDF(featureRenderingBasePath + '.pdf')
        img = composition.printPageAsRaster(0)
        img.save(featureRenderingBasePath + '.jpg', 'jpg')
    atlas.endRender()

    #Some cleanup maybe?
    QgsProject.instance().clear()


# supply path to qgis install location
#QgsApplication.setPrefixPath("/usr", True) #already set in the right place "/usr" by default

# create a reference to the QgsApplication
# setting the second argument to True enables the GUI, which we need to do
# since this is a custom application
qgs = QgsApplication([], True)

# load providers
qgs.initQgis()

printAtlas(projectPath, renderingPath)

# When your script is complete, call exitQgis() to remove the provider and
# layer registries from memory
#qgs.exitQgis()
qgs.exit() # to avoid a SEGFAULT thanks to https://gis.stackexchange.com/a/153614/73088 and https://gis.stackexchange.com/questions/250933/using-exitqgis-in-pyqgis#comment441476_250933

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.