Open anthony-o opened 6 years ago
After a long research and many tries, I've finally manage to simulate the Mask plugin QGIS init in a standalone script (after discovering it was not possible to init the plugins from such scripts using a single API: one should do this on his/her own).
I think it could be a good thing if you could create a specific method to init the Mask plugin from standalone scripts or to be able to do so using a QgsServerInterface
(that is to say using the QGIS server application). #70
Après une longue recherche et beaucoup d'essais, j'ai finalement réussi à simuler l'initialisation du plugin Mask par QGIS dans un script standalone (après avoir découvert qu'on ne peut pas initialiser les plugins depuis de tels scripts avec une API unique : on est obligés de le faire "manuellement").
Je pense que ça pourrait être une bonne chose si vous pouviez créer une méthode pour initialiser le plugin Mask depuis des scripts standalone ou via l'interface QGIS serveur QgsServerInterface
. #70
#!/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)
featureRenderingBasePath = os.path.join(renderingPath, str(format(i)))
#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
Thank you @anthony-o for this work. Sadly it no longer seems to work with Python 3 and recent versions of pyqgis. I don't suppose you have an update to this or other similar scripts?
Merci @anthony-o pour votre travail. Malheureusement il ne semble plus functionner avec Python 3 et des versions plus récentes de pyqgis. Vous n'auriez pas de mise à jour de ceci ou d'autres scripts semblables?
Following my last comment, I have managed to hack together a version of your script that works well enough for me, which is to export the entire atlas as a PDF.
Suite à mon dernier commentaire, j'ai réussi à bidouiller une version de votre script qui fonctionne assez bien pour moi, c'est à dire à exporter l'ensemble de l'atlas au format PDF.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from qgis.core import QgsApplication, QgsProject, QgsLayout, QgsReadWriteContext, QgsLayoutExporter
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge
from PyQt5.QtCore import QFile, QFileInfo, QByteArray, QTextStream, QSettings
from PyQt5.QtXml import QDomDocument
import sys
import os
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
sys.path.append('/home/jschultz/.local/share/QGIS/QGIS3/profiles/default/python/plugins/')
from mask.aeag_mask import aeag_mask
projectPath = sys.argv[1]
renderingPath = sys.argv[2]
class FakeSignal:
def connect(self, dummy):
pass
class FakeInterface:
def __init__(self, canvas):
self.canvas = canvas
self.layoutDesignerClosed = FakeSignal()
def activeLayer(self): # used by aeag_mask.py
return None
def mapCanvas(self): # used by aeag_mask.py
return self.canvas
class FakeQAction:
def __init__(self):
pass
def setEnabled(self, value):
pass
def setText(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(projectPath)
# Loading mask
mask_plugin.registry = QgsProject.instance()
mask_plugin.on_project_open()
bridge = QgsLayerTreeMapCanvasBridge(
QgsProject.instance().layerTreeRoot(), canvas)
bridge.setCanvasLayers()
#Lets try load that composer template we just extracted
layout = QgsLayout(QgsProject.instance())
layout.loadFromTemplate(composerAsDocument, QgsReadWriteContext())
# Connect Mask plugin
mask_plugin.on_layout_added("Layout 1")
manager = QgsProject.instance().layoutManager()
layout = manager.layoutByName("Layout 1")
atlas = layout.atlas()
exporter = QgsLayoutExporter(atlas.layout())
pdfsettings = QgsLayoutExporter.PdfExportSettings()
exporter.exportToPdf(atlas, renderingPath, pdfsettings)
#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
OK finally I am too tired to write in French.
I have created a repository to hold scripts that help solve problems such as this.
It draws heavily on the script that @anthony-o included above and the work I did on it.
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.Is there a specific API to use when executing standalone scripts so that QGIS engine uses the mask layer correctly?
I asked my question on Geographic Information Systems in case...
J'ai créé un script QGIS standalone qui me permet de faire un export Atlas automatique en tant qu'images.
Le problème c'est que mon projet utilise le plugin Mask et que les images générés ne font pas apparaître les masques en question (alors que quand je fais un export Atlas depuis QGIS desktop, les images contiennent bien mon masque).
Est-ce qu'il faut utiliser une API spécifiques quand on exécute un script standalone qui utilise un projet qui utilise le plugin mask pour l'activer ?
Ma question sur Geographic Information Systems au cas où...