How to create a QGIS PDF report with a few lines of python
Posted by: Gavin Fleming | in FOSSGIS, QGIS | 9 years, 10 months ago | 17 comments

Sometimes you want to automatically generate a report to reflect the latest state of your data. For example you may be capturing spatial data into a PostGIS database and want a snapshot of that every few hours expressed as a pdf report. This example shows you how you can quickly generate a pdf based on a QGIS project (.qgs file) and a QGIS template (.qpt file).


# coding=utf-8

# A simple demonstration of to generate a PDF using a QGIS project
# and a QGIS layout template.
#
# This code is public domain, use if for any purpose you see fit.
# Tim Sutton 2015

import sys
from qgis.core import (
    QgsProject, QgsComposition, QgsApplication, QgsProviderRegistry)
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge
from PyQt4.QtCore import QFileInfo
from PyQt4.QtXml import QDomDocument


gui_flag = True
app = QgsApplication(sys.argv, gui_flag)

# Make sure QGIS_PREFIX_PATH is set in your env if needed!
app.initQgis()

# Probably you want to tweak this
project_path = 'project.qgs'

# and this
template_path = 'template.qpt'

def make_pdf():
    canvas = QgsMapCanvas()
    # Load our project
    QgsProject.instance().read(QFileInfo(project_path))
    bridge = QgsLayerTreeMapCanvasBridge(
        QgsProject.instance().layerTreeRoot(), canvas)
    bridge.setCanvasLayers()

    template_file = file(template_path)
    template_content = template_file.read()
    template_file.close()
    document = QDomDocument()
    document.setContent(template_content)
    composition = QgsComposition(canvas.mapSettings())
    # You can use this to replace any string like this [key]
    # in the template with a new value. e.g. to replace
    # [date] pass a map like this {'date': '1 Jan 2012'}
    substitution_map = {
        'DATE_TIME_START': 'foo',
        'DATE_TIME_END': 'bar'}
    composition.loadFromTemplate(document, substitution_map)
    # You must set the id in the template
    map_item = composition.getComposerItemById('map')
    map_item.setMapCanvas(canvas)
    map_item.zoomToExtent(canvas.extent())
    # You must set the id in the template
    legend_item = composition.getComposerItemById('legend')
    legend_item.updateLegend()
    composition.refreshItems()
    composition.exportAsPDF('report.pdf')
    QgsProject.instance().clear()


make_pdf()



(See here for any updates we may publish for this example)

Using this approach you can generate all kinds of useful outputs without ever needing to open QGIS each time you generate the report. Simply create the needed project and template files and then run it like this:

python generate_pdf.py
Current rating: 3.5

Comments

How to create a QGIS PDF report with a few lines o 9 years, 10 months ago

[…] http://kartoza.com/how-to-create-a-qgis-pdf-report-with-a-few-lines-of-python/ […]

Link | Reply
Currently unrated

Comment awaiting approval 5 years, 1 month ago

Comment awaiting approval 5 years, 1 month ago

Comment awaiting approval 5 years, 1 month ago

Patrick Sunter 9 years, 6 months ago

Hi Tim - just wanted to say thanks heaps for putting this code snippet online. I was finding this quite difficult to get this kind of functionality set up properly, the official QGIS Python API cookbook docs are a bit out of date, and your code snippet proved a better working base than anyone else's! :)

Link | Reply
Currently unrated

Tim Sutton 9 years, 6 months ago

Hi @patricksunter:disqus - glad it was useful to you!

Link | Reply
Currently unrated

Alec Walker 9 years, 5 months ago

Hi Tim

Many thanks for posting this code - it looks like exactly what I need, however my pdf map output doesn't have any map layers nor has it updated the legend. I've added the relevant id's to my template and have even added an extra line: "map_item.updateItem()" to try to force it to refresh the map.

The PDF output has all of the required items in it and has correctly updated a couple of test fields via the substitution map, it just has an empty map and two legend items that I think I originally renamed in the map composer rather than the full set of layers that are in the parent map.

I'm running this via a .bat file on Win7, which is used to set up the PYTHONPATH etc. QGIS is 2.8 Wien.

I do get a couple of warnings (I don't think they're errors):

"QGraphisScene::AddItem: item has already been added to this scence." I interpret this as a superfluous command rather than an error but it may have upset something.

"libpng warning: iCCP: known incorrect sRGB profile". Again, I don't think is likely to be an issue, just a warning. The two png images I've added to the composer render fine in the PDF output.

I'd prefer to debug this in eclipse/pydev but am getting nowhere trying to configure the paths - but that's another issue.

Any ideas? This is my first foray into QGIS python so it's a steep learning curve.

Link | Reply
Currently unrated

Tim Sutton 9 years, 5 months ago

Hi


Regarding the warnings you are seeing, I don't think they are critical. Can I suggest that you try to debug your code from inside the QGIS python console first - so that you can identify whether the issue is running the script from the command line in windows. Unfortunately I don't use windows much so I would really suggest taking your query over to the QGIS developer mailing list where your question will be exposed to a wider spread of developers.


Regards


Tim

Link | Reply
Currently unrated

Ben Mayo 9 years, 4 months ago

Hi Tim,


As Alec, I can't get my map to print. Legends, map borders, headings all print to PDF fine, but the map canvas (.shp vector mapping) itself doesn't appear. I'm running QGIS 2.6 on Mac.


I can PDF from Composer itself fine.


Any suggestions greatly appreciated.


Ben

Link | Reply
Currently unrated

Ben Mayo 9 years, 4 months ago

Hi Alec,


I've been having the same problems on my Mac when running the script on PyDev...running the script inside QGIS gave me a solution.

Link | Reply
Currently unrated

Raj 9 years ago

Hi Tim,
Thanks for your code. I was having a similar problem. I could not print a map to PDF. It would print labels, legends and other featuers created with in the composer but not an actual map. However, if we add make_pdf() method inside a class and call the class from the main menu it will work. Here's a minor modification to the code. Hope this works with others who were having similar issues. Please use correct indentation while copying the code.

import sys
from qgis.core import (QgsProject, QgsComposition, QgsApplication, QgsProviderRegistry)
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge
from PyQt4.QtCore import QFileInfo
from PyQt4.QtXml import QDomDocument

class PdfMaker():

def make_pdf(self):

project_path = 'c:/Test/Qgis/TestQgs1.qgs'
template_path = 'c:/Test/Qgis/TestPrint.qpt'

self.canvas = QgsMapCanvas()
# Load our project
QgsProject.instance().read(QFileInfo(project_path))
bridge = QgsLayerTreeMapCanvasBridge(QgsProject.instance().layerTreeRoot (), self.canvas )
bridge.setCanvasLayers()

template_file = file(template_path)
template_content = template_file.read()
template_file.close()

document = QDomDocument()
document.setContent(template_content)
composition = QgsComposition(self.canvas.mapSettings())

composition.loadFromTemplate(document, {})

# You must set the id in the template

map_item = composition.getComposerItemById('printMe')
map_item.setMapCanvas(self.canvas)
map_item.zoomToExtent(self.canvas.extent())

# You must set the id in the template
legend_item = composition.getComposerItemById('Legend1')
legend_item.updateLegend()

composition.refreshItems()

composition.exportAsPDF('c:/Test/Qgis/report1a.pdf')
QgsProject.instance().clear()

def main (argv):

app = QgsApplication(sys.argv, True)
QgsApplication.setPrefixPath('C:/OSGeo4W/apps/qgis', True)
QgsApplication.initQgis()

PdfCreate = PdfMaker()
PdfCreate.make_pdf()

if __name__ == "__main__":
main(sys.argv)

Link | Reply
Currently unrated

Tim Sutton 9 years ago

Thanks so much Raj - hopefully your comments and code snippet will help others reading this!

Regards

Tim

Link | Reply
Currently unrated

Simon Pickett 8 years, 4 months ago

Hi Raj and Tim,

I was having the same issue with blank pdfs, so I tried your edited code Raj.

However I am getting this error message "Must construct a QApplication before a QPaintDevice" Any ideas how to solve this are much obliged! Im using a batch file to run the script on a Windows 7 machine, QGIS version 2.12.3,

thanks,

Simon

import sys
from qgis.core import (QgsProject, QgsComposition, QgsApplication, QgsProviderRegistry)
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge
from PyQt4.QtCore import QFileInfo
from PyQt4.QtXml import QDomDocument

class PdfMaker():

def make_pdf(self):

project_path = 'Z:/Path to file/Export Template.qgs'
template_path = 'Z:/Path to file/Blank Template Map Export.qpt'

self.canvas = QgsMapCanvas()
# Load our project
QgsProject.instance().read(QFileInfo(project_path))
bridge = QgsLayerTreeMapCanvasBridge(QgsProject.instance().layerTreeRoot (), self.canvas )
bridge.setCanvasLayers()

template_file = file(template_path)
template_content = template_file.read()
template_file.close()

document = QDomDocument()
document.setContent(template_content)
composition = QgsComposition(self.canvas.mapSettings())

composition.loadFromTemplate(document, {})

# You must set the id in the template
map_item = composition.getComposerItemById('map')
map_item.setMapCanvas(self.canvas)
map_item.zoomToExtent(self.canvas.extent())

# You must set the id in the template
legend_item = composition.getComposerItemById('legend')
legend_item.updateLegend()
composition.refreshItems()

composition.exportAsPDF('Did thiswork.pdf')
QgsProject.instance().clear()

def main (argv):
app = QgsApplication(sys.argv, True)
QgsApplication.setPrefixPath('C:/Program Files/QGIS Lyon/bin', True)
QgsApplication.initQgis()

PdfCreate = PdfMaker()
PdfCreate.make_pdf()

if __name__ == "__main__":
main(sys.argv)

Link | Reply
Currently unrated

Tim Sutton 8 years, 4 months ago

Hi

Thank you for your message and apologies for the slow reply. I tested the code above here in OSX, modifying only the paths. It all works fine for me. Do you have some specific error messages? You need to take care that your runtime environment is found properly. In OSX I used a simple bash file like this to run the script (saved as printmap.sh):

#!/bin/bash

export QGIS_PREFIX_PATH=/Applications/QGIS.app/contents/MacOS
export PYTHONPATH=$PYTHONPATH:/Applications/QGIS.app/contents/Resources/python:/Applications/QGIS.app/Contents/Resources/python/plugins/
python printmap.py

Then chmod +x printmap.sh
Then run it : ../printmap.sh

If you still can't run it, I suggest to share your specific error messages.

Link | Reply
Currently unrated

Tim Sutton 8 years, 4 months ago

Did you ensure that you named the map item 'map' in your composer template? See my comments above about setting up your environment in OSX nicely...

Link | Reply
Currently unrated

Tim Sutton 8 years, 4 months ago

See my notes above on how to run from the command line....

Link | Reply
Currently unrated

Simon Pickett 8 years, 4 months ago

Hi Tim,

Thanks for the reply, the cmd error read: "Must construct a QApplication before a QPaintDevice". I will try your suggestion, many thanks, Simon

Link | Reply
Currently unrated

New Comment

required

required (not published)

optional

required

Have a question? Get in touch!