Here is what I finally did (also answered an other question with the script, but not the headless part).
Inspired by:
Save Print/Map QGIS composer view as PNG/PDF using Python (without changing anything in visible layout)?
How to export a configurated Atlas with a python script / command line?
QGIS: Automatisation de la génération d'un Atlas avec script python
Search for "[qgis] standalone script" on gis stackexchange
How to run a simple python script for QGIS from outside (e.g. Sublime Text)?
Using PyQGIS in custom applications
Generate a QGIS map PDF using python
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
QGIS export “save as image” automate with python?
I wrote the following exportAtlas.py
python script:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from qgis.core import QgsApplication, QgsProject, QgsComposition
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge
from PyQt4.QtCore import QFile, QFileInfo, QByteArray, QTextStream
from PyQt4.QtXml import QDomDocument
import os
import sys
projectPath = sys.argv[1]
renderingPath = sys.argv[2]
def printAtlas(projectPath, renderingPath):
# 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
canvas = QgsMapCanvas()
QgsProject.instance().read(QFileInfo(projectAsFile))
bridge = QgsLayerTreeMapCanvasBridge(
QgsProject.instance().layerTreeRoot(), canvas)
bridge.setCanvasLayers()
#Lets try load that composer template we just extracted
composition = QgsComposition(canvas.mapSettings())
composition.loadFromTemplate(composerAsDocument, {})
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()
On a headless server, one could use Xvfb to start a virtual X11 this way (before executing the previous python script): Xvfb :0 -ac -listen tcp -screen 0 2560x1440x24
.
The python script can be called this way: DISPLAY=:0 python exportAtlas.py '/path/to/the/project.qgs' '/path/to/the/folder/where/to/generate/images/and/pdfs/'
In case the following error appears
>>> import qgis.core
ImportError: No module named qgis.core
like said in the documentation, you should define the PYTHONPATH
environment variable to point to the python
folder inside QGIS home. In my case it was:
export PYTHONPATH=/usr/share/qgis/python
I was able to script all of this using a Docker container with the following Dockerfile
:
#FROM kartoza/qgis-desktop:2.18.20
FROM kartoza/qgis-desktop:2.18
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y xvfb
ENV DISPLAY=:0
COPY exportAtlas.py /usr/local/bin/
then built it (docker build -t exportAtlas .
) and ran it:
docker run -it --rm -v '/path/to/the/project/folder:/project' -v '/path/to/the/folder/where/to/generate/images/and/pdfs/:/exports' exportAtlas bash -c "PYTHONPATH=/usr/share/qgis/python python /usr/local/bin/exportAtlas.py '/project/qgis_project_filename.qgs' /exports/"
The problem with this script is that it doesn't take into consideration a layer which uses the Mask plugin, even if I unzip & install the zip to /usr/share/qgis/python/plugins
.
I've fixed this in another answer.