3

I have a QGIS project that has an Atlas print composer that I can use to generate multiple images (JPG) of each of my departments.

Now I would want to generate all those images but directly from a Linux server which does not have any graphical server (it is a headless server, like many Linux servers).

Is this possible?

1 Answer 1

4

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.

4
  • do you have a solution for qgis3?
    – AndreasK
    Dec 6, 2018 at 22:14
  • @AndreasK I only tested this with Docker image kartoza/qgis-desktop:2.18... what error do you have with qgis3?
    – Anthony O.
    Dec 7, 2018 at 10:08
  • Why you didn't used Qgis server as it's able to create atlas from a project.
    – SIGIS
    Jan 20, 2021 at 9:36
  • @SIGIS I tested first to use Qgis server at the time I was searching for a solution to this problem, but at that time at least, I remember that some features (canvas? composer?) were not available in the server implementation in order to create such atlas... perhaps this is not the case anymore? In this reply I found that the Mask plugin (as well as others) was only compatible with Qgis (and not server, as far as I remember).
    – Anthony O.
    Jan 21, 2021 at 14:10

Your Answer

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

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