How to create a QGIS PDF report with a few lines of python
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.pyShare on Twitter Share on Facebook
Comments
How to create a QGIS PDF report with a few lines o 9 years, 9 months ago
[…] http://kartoza.com/how-to-create-a-qgis-pdf-report-with-a-few-lines-of-python/ […]
Link | ReplyComment awaiting approval 5 years ago
Comment awaiting approval 5 years ago
Comment awaiting approval 5 years ago
Patrick Sunter 9 years, 5 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 | ReplyTim Sutton 9 years, 5 months ago
Hi @patricksunter:disqus - glad it was useful to you!
Link | ReplyAlec Walker 9 years, 4 months ago
Hi Tim
Link | ReplyMany 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.
Tim Sutton 9 years, 4 months ago
Hi
Link | ReplyRegarding 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
Ben Mayo 9 years, 4 months ago
Hi Tim,
Link | ReplyAs 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
Ben Mayo 9 years, 4 months ago
Hi Alec,
Link | ReplyI've been having the same problems on my Mac when running the script on PyDev...running the script inside QGIS gave me a solution.
Raj 8 years, 11 months ago
Hi Tim,
Link | ReplyThanks 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)
Tim Sutton 8 years, 11 months ago
Thanks so much Raj - hopefully your comments and code snippet will help others reading this!
Link | ReplyRegards
Tim
Simon Pickett 8 years, 4 months ago
Hi Raj and Tim,
Link | ReplyI 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)
Tim Sutton 8 years, 4 months ago
Hi
Link | ReplyThank 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.
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 | ReplyTim Sutton 8 years, 4 months ago
See my notes above on how to run from the command line....
Link | ReplySimon Pickett 8 years, 4 months ago
Hi Tim,
Link | ReplyThanks for the reply, the cmd error read: "Must construct a QApplication before a QPaintDevice". I will try your suggestion, many thanks, Simon
New Comment